diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 8e24765e2d..3878d68ccd 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -8,7 +8,7 @@ This file provides context-aware guidance for GitHub Copilot when working in the
**Supervision** is a Python library providing reusable computer vision utilities for working with object detection models (YOLO, SAM, etc.). It offers tools for detections processing, tracking, annotation, and dataset management.
-- **Languages**: Python 3.9+
+- **Languages**: Python 3.10+
- **Key Dependencies**: NumPy, OpenCV, SciPy
- **License**: MIT
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 1933fdc7b2..fc385fc757 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -29,7 +29,7 @@ jobs:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
runs-on: ${{ matrix.os }}
steps:
- name: 📥 Checkout the repository
diff --git a/README.md b/README.md
index 3f35d27cd5..313cf6e8d6 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@
## 💻 install
Pip install the supervision package in a
-[**Python>=3.9**](https://www.python.org/) environment.
+[**Python>=3.10**](https://www.python.org/) environment.
```bash
pip install supervision
diff --git a/docs/changelog.md b/docs/changelog.md
index 1303ab4a0a..000d79ea18 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -5,7 +5,13 @@ date_modified: 2026-04-30
# Changelog
-### UnReleased
+### UnReleased upcoming
+
+!!! failure "Python 3.9 Support Terminated"
+
+ With the `supervision-0.29.0` release, we are terminating official support for Python 3.9, which reached end-of-life in October 2025. The minimum supported Python version is now **3.10**.
+
+ Users on Python 3.9 should upgrade their environment before updating supervision.
- Added [#2267](https://github.com/roboflow/supervision/pull/2267): [`DetectionDataset.as_coco`](https://supervision.roboflow.com/latest/datasets/core/#supervision.dataset.core.DetectionDataset.as_coco) and `save_coco_annotations` now accept `starting_image_id` and `starting_annotation_id` parameters (both default to `1`, preserving existing behavior) and return a `(next_image_id, next_annotation_id)` tuple. Feed the returned values into the next split's call to produce globally unique COCO ids across train/valid/test exports. Fixes id collisions reported in [#768](https://github.com/roboflow/supervision/issues/768). **Note**: the return type changes from `None` to `tuple[int, int]` — callers that assert `result is None` must be updated.
diff --git a/examples/compact_mask/benchmark.py b/examples/compact_mask/benchmark.py
index 7a7a1fb3e8..052e1b4683 100644
--- a/examples/compact_mask/benchmark.py
+++ b/examples/compact_mask/benchmark.py
@@ -20,11 +20,11 @@
import math
import time
import tracemalloc
+from collections.abc import Callable
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
-from typing import Callable
import cv2
import numpy as np
diff --git a/examples/count_people_in_zone/inference_example.py b/examples/count_people_in_zone/inference_example.py
index 677af69391..2b21126671 100644
--- a/examples/count_people_in_zone/inference_example.py
+++ b/examples/count_people_in_zone/inference_example.py
@@ -135,7 +135,7 @@ def main(
target_video_path: str | None = None,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
-):
+) -> None:
"""
Counting people in zones with Inference and Supervision.
diff --git a/examples/count_people_in_zone/ultralytics_example.py b/examples/count_people_in_zone/ultralytics_example.py
index 35a09616f0..6caf3c237e 100644
--- a/examples/count_people_in_zone/ultralytics_example.py
+++ b/examples/count_people_in_zone/ultralytics_example.py
@@ -133,7 +133,7 @@ def main(
target_video_path: str | None = None,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
-):
+) -> None:
"""
Counting people in zones with YOLO and Supervision.
diff --git a/examples/heatmap_and_track/script.py b/examples/heatmap_and_track/script.py
index dcbbe12f92..5a8667ee1c 100644
--- a/examples/heatmap_and_track/script.py
+++ b/examples/heatmap_and_track/script.py
@@ -1,123 +1,121 @@
-from typing import Optional
-
-import cv2
-from ultralytics import YOLO
-
-import supervision as sv
-from supervision.assets import VideoAssets, download_assets
-
-
-def download_video() -> str:
- download_assets(VideoAssets.PEOPLE_WALKING)
- return VideoAssets.PEOPLE_WALKING.value
-
-
-def main(
- source_weights_path: str,
- source_video_path: Optional[str] = None,
- target_video_path: str = "output.mp4",
- confidence_threshold: float = 0.35,
- iou_threshold: float = 0.5,
- heatmap_alpha: float = 0.5,
- radius: int = 25,
- track_activation_threshold: float = 0.35,
- track_seconds: int = 5,
- minimum_matching_threshold: float = 0.99,
-) -> None:
- """
- Heatmap and Tracking with Supervision.
-
- Args:
- source_weights_path: Path to the source weights file
- source_video_path: Path to the source video file
- target_video_path: Path to the target video file
- confidence_threshold: Confidence threshold for the model
- iou_threshold: IOU threshold for the model
- heatmap_alpha: Opacity of the overlay mask, between 0 and 1
- radius: Radius of the heat circle
- track_activation_threshold: Detection confidence threshold for track activation
- track_seconds: Number of seconds to buffer when a track is lost
- minimum_matching_threshold: Threshold for matching tracks with detections
- """
- ### instantiate model
- model = YOLO(source_weights_path)
- source_video_path = source_video_path or download_video()
-
- ### heatmap config
- heat_map_annotator = sv.HeatMapAnnotator(
- position=sv.Position.BOTTOM_CENTER,
- opacity=heatmap_alpha,
- radius=radius,
- kernel_size=25,
- top_hue=0,
- low_hue=125,
- )
-
- ### annotation config
- label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
-
- ### get the video fps
- cap = cv2.VideoCapture(source_video_path)
- fps = int(cap.get(cv2.CAP_PROP_FPS))
- cap.release()
-
- ### tracker config
- byte_tracker = sv.ByteTrack(
- track_activation_threshold=track_activation_threshold,
- lost_track_buffer=track_seconds * fps,
- minimum_matching_threshold=minimum_matching_threshold,
- frame_rate=fps,
- )
-
- ### video config
- video_info = sv.VideoInfo.from_video_path(video_path=source_video_path)
- frames_generator = sv.get_video_frames_generator(
- source_path=source_video_path, stride=1
- )
-
- ### Detect, track, annotate, save
- with sv.VideoSink(target_path=target_video_path, video_info=video_info) as sink:
- for frame in frames_generator:
- result = model(
- source=frame,
- classes=[0], # only person class
- conf=confidence_threshold,
- iou=iou_threshold,
- # show_conf = True,
- # save_txt = True,
- # save_conf = True,
- # save = True,
- device=None, # use None = CPU, 0 = single GPU, or [0,1] = dual GPU
- )[0]
-
- detections = sv.Detections.from_ultralytics(result) # get detections
-
- detections = byte_tracker.update_with_detections(
- detections
- ) # update tracker
-
- ### draw heatmap
- annotated_frame = heat_map_annotator.annotate(
- scene=frame.copy(), detections=detections
- )
-
- ### draw other attributes from `detections` object
- labels = [
- f"#{tracker_id}"
- for class_id, tracker_id in zip(
- detections.class_id, detections.tracker_id
- )
- ]
-
- label_annotator.annotate(
- scene=annotated_frame, detections=detections, labels=labels
- )
-
- sink.write_frame(frame=annotated_frame)
-
-
-if __name__ == "__main__":
- from jsonargparse import auto_cli, set_parsing_settings
-
- set_parsing_settings(parse_optionals_as_positionals=True)
- auto_cli(main, as_positional=False)
+import cv2
+from ultralytics import YOLO
+
+import supervision as sv
+from supervision.assets import VideoAssets, download_assets
+
+
+def download_video() -> str:
+ download_assets(VideoAssets.PEOPLE_WALKING)
+ return VideoAssets.PEOPLE_WALKING.value
+
+
+def main(
+ source_weights_path: str,
+ source_video_path: str | None = None,
+ target_video_path: str = "output.mp4",
+ confidence_threshold: float = 0.35,
+ iou_threshold: float = 0.5,
+ heatmap_alpha: float = 0.5,
+ radius: int = 25,
+ track_activation_threshold: float = 0.35,
+ track_seconds: int = 5,
+ minimum_matching_threshold: float = 0.99,
+) -> None:
+ """
+ Heatmap and Tracking with Supervision.
+
+ Args:
+ source_weights_path: Path to the source weights file
+ source_video_path: Path to the source video file
+ target_video_path: Path to the target video file
+ confidence_threshold: Confidence threshold for the model
+ iou_threshold: IOU threshold for the model
+ heatmap_alpha: Opacity of the overlay mask, between 0 and 1
+ radius: Radius of the heat circle
+ track_activation_threshold: Detection confidence threshold for track activation
+ track_seconds: Number of seconds to buffer when a track is lost
+ minimum_matching_threshold: Threshold for matching tracks with detections
+ """
+ ### instantiate model
+ model = YOLO(source_weights_path)
+ source_video_path = source_video_path or download_video()
+
+ ### heatmap config
+ heat_map_annotator = sv.HeatMapAnnotator(
+ position=sv.Position.BOTTOM_CENTER,
+ opacity=heatmap_alpha,
+ radius=radius,
+ kernel_size=25,
+ top_hue=0,
+ low_hue=125,
+ )
+
+ ### annotation config
+ label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
+
+ ### get the video fps
+ cap = cv2.VideoCapture(source_video_path)
+ fps = int(cap.get(cv2.CAP_PROP_FPS))
+ cap.release()
+
+ ### tracker config
+ byte_tracker = sv.ByteTrack(
+ track_activation_threshold=track_activation_threshold,
+ lost_track_buffer=track_seconds * fps,
+ minimum_matching_threshold=minimum_matching_threshold,
+ frame_rate=fps,
+ )
+
+ ### video config
+ video_info = sv.VideoInfo.from_video_path(video_path=source_video_path)
+ frames_generator = sv.get_video_frames_generator(
+ source_path=source_video_path, stride=1
+ )
+
+ ### Detect, track, annotate, save
+ with sv.VideoSink(target_path=target_video_path, video_info=video_info) as sink:
+ for frame in frames_generator:
+ result = model(
+ source=frame,
+ classes=[0], # only person class
+ conf=confidence_threshold,
+ iou=iou_threshold,
+ # show_conf = True,
+ # save_txt = True,
+ # save_conf = True,
+ # save = True,
+ device=None, # use None = CPU, 0 = single GPU, or [0,1] = dual GPU
+ )[0]
+
+ detections = sv.Detections.from_ultralytics(result) # get detections
+
+ detections = byte_tracker.update_with_detections(
+ detections
+ ) # update tracker
+
+ ### draw heatmap
+ annotated_frame = heat_map_annotator.annotate(
+ scene=frame.copy(), detections=detections
+ )
+
+ ### draw other attributes from `detections` object
+ labels = [
+ f"#{tracker_id}"
+ for class_id, tracker_id in zip(
+ detections.class_id, detections.tracker_id
+ )
+ ]
+
+ label_annotator.annotate(
+ scene=annotated_frame, detections=detections, labels=labels
+ )
+
+ sink.write_frame(frame=annotated_frame)
+
+
+if __name__ == "__main__":
+ from jsonargparse import auto_cli, set_parsing_settings
+
+ set_parsing_settings(parse_optionals_as_positionals=True)
+ auto_cli(main, as_positional=False)
diff --git a/examples/speed_estimation/inference_example.py b/examples/speed_estimation/inference_example.py
index cb4f77c0df..21ec95b2a6 100644
--- a/examples/speed_estimation/inference_example.py
+++ b/examples/speed_estimation/inference_example.py
@@ -44,7 +44,7 @@ def main(
roboflow_api_key: str | None = None,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
-):
+) -> None:
"""
Vehicle Speed Estimation using Inference and Supervision.
diff --git a/examples/speed_estimation/ultralytics_example.py b/examples/speed_estimation/ultralytics_example.py
index 423e53acea..3678a55462 100644
--- a/examples/speed_estimation/ultralytics_example.py
+++ b/examples/speed_estimation/ultralytics_example.py
@@ -41,7 +41,7 @@ def main(
target_video_path: str,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
-):
+) -> None:
"""
Vehicle Speed Estimation using Ultralytics and Supervision.
diff --git a/examples/speed_estimation/yolo_nas_example.py b/examples/speed_estimation/yolo_nas_example.py
index 9fe4148baf..0100e38cb6 100644
--- a/examples/speed_estimation/yolo_nas_example.py
+++ b/examples/speed_estimation/yolo_nas_example.py
@@ -42,7 +42,7 @@ def main(
target_video_path: str,
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
-):
+) -> None:
"""
Vehicle Speed Estimation using YOLO-NAS and Supervision.
diff --git a/examples/time_in_zone/inference_stream_example.py b/examples/time_in_zone/inference_stream_example.py
index 3f659d1c06..e88a55fc04 100644
--- a/examples/time_in_zone/inference_stream_example.py
+++ b/examples/time_in_zone/inference_stream_example.py
@@ -15,7 +15,7 @@
class CustomSink:
- def __init__(self, zone_configuration_path: str, classes: list[int]):
+ def __init__(self, zone_configuration_path: str, classes: list[int]) -> None:
self.classes = classes
self.tracker = sv.ByteTrack(minimum_matching_threshold=0.5)
self.fps_monitor = sv.FPSMonitor()
diff --git a/examples/time_in_zone/rfdetr_file_example.py b/examples/time_in_zone/rfdetr_file_example.py
index bb6625aa99..2c9e4aa6e0 100644
--- a/examples/time_in_zone/rfdetr_file_example.py
+++ b/examples/time_in_zone/rfdetr_file_example.py
@@ -25,7 +25,7 @@ class ModelSize(Enum):
LARGE = "large"
@classmethod
- def list(cls):
+ def list(cls) -> list[str]:
return list(map(lambda c: c.value, cls))
@classmethod
@@ -44,7 +44,9 @@ def from_value(cls, value: ModelSize | str) -> ModelSize:
)
-def load_model(checkpoint: ModelSize | str, device: str, resolution: int):
+def load_model(
+ checkpoint: ModelSize | str, device: str, resolution: int
+) -> RFDETRBase | RFDETRLarge | RFDETRMedium | RFDETRNano | RFDETRSmall:
checkpoint = ModelSize.from_value(checkpoint)
if checkpoint == ModelSize.NANO:
diff --git a/examples/time_in_zone/rfdetr_naive_stream_example.py b/examples/time_in_zone/rfdetr_naive_stream_example.py
index 5f78bcd0fa..b517277474 100644
--- a/examples/time_in_zone/rfdetr_naive_stream_example.py
+++ b/examples/time_in_zone/rfdetr_naive_stream_example.py
@@ -25,7 +25,7 @@ class ModelSize(Enum):
LARGE = "large"
@classmethod
- def list(cls):
+ def list(cls) -> list[str]:
return list(map(lambda c: c.value, cls))
@classmethod
@@ -44,7 +44,9 @@ def from_value(cls, value: ModelSize | str) -> ModelSize:
)
-def load_model(checkpoint: ModelSize | str, device: str, resolution: int):
+def load_model(
+ checkpoint: ModelSize | str, device: str, resolution: int
+) -> RFDETRBase | RFDETRLarge | RFDETRMedium | RFDETRNano | RFDETRSmall:
checkpoint = ModelSize.from_value(checkpoint)
if checkpoint == ModelSize.NANO:
diff --git a/examples/time_in_zone/rfdetr_stream_example.py b/examples/time_in_zone/rfdetr_stream_example.py
index 7eff2cbc98..678faaf90d 100644
--- a/examples/time_in_zone/rfdetr_stream_example.py
+++ b/examples/time_in_zone/rfdetr_stream_example.py
@@ -21,7 +21,7 @@ class ModelSize(Enum):
LARGE = "large"
@classmethod
- def list(cls):
+ def list(cls) -> list[str]:
return [c.value for c in cls]
@classmethod
@@ -41,7 +41,9 @@ def from_value(cls, value: ModelSize | str) -> ModelSize:
)
-def load_model(checkpoint: ModelSize | str, device: str, resolution: int):
+def load_model(
+ checkpoint: ModelSize | str, device: str, resolution: int
+) -> RFDETRBase | RFDETRLarge | RFDETRMedium | RFDETRNano | RFDETRSmall:
checkpoint = ModelSize.from_value(checkpoint)
if checkpoint == ModelSize.NANO:
return RFDETRNano(device=device, resolution=resolution)
@@ -77,7 +79,7 @@ def adjust_resolution(checkpoint: ModelSize | str, resolution: int) -> int:
class CustomSink:
- def __init__(self, zone_configuration_path: str, classes: list[int]):
+ def __init__(self, zone_configuration_path: str, classes: list[int]) -> None:
self.classes = classes
self.tracker = sv.ByteTrack(minimum_matching_threshold=0.8)
self.fps_monitor = sv.FPSMonitor()
diff --git a/examples/time_in_zone/scripts/draw_zones.py b/examples/time_in_zone/scripts/draw_zones.py
index de7a7bab6d..e52d0602c3 100644
--- a/examples/time_in_zone/scripts/draw_zones.py
+++ b/examples/time_in_zone/scripts/draw_zones.py
@@ -119,7 +119,9 @@ def redraw_polygons(image: np.ndarray) -> None:
)
-def save_polygons_to_json(polygons, target_path):
+def save_polygons_to_json(
+ polygons: list[list[tuple[int, int]]], target_path: str | os.PathLike[str]
+) -> None:
data_to_save = polygons if polygons[-1] else polygons[:-1]
with open(target_path, "w") as f:
json.dump(data_to_save, f)
diff --git a/examples/time_in_zone/ultralytics_stream_example.py b/examples/time_in_zone/ultralytics_stream_example.py
index b18462b339..75935680cb 100644
--- a/examples/time_in_zone/ultralytics_stream_example.py
+++ b/examples/time_in_zone/ultralytics_stream_example.py
@@ -18,7 +18,7 @@
class CustomSink:
- def __init__(self, zone_configuration_path: str, classes: list[int]):
+ def __init__(self, zone_configuration_path: str, classes: list[int]) -> None:
self.classes = classes
self.tracker = sv.ByteTrack(minimum_matching_threshold=0.8)
self.fps_monitor = sv.FPSMonitor()
diff --git a/examples/traffic_analysis/inference_example.py b/examples/traffic_analysis/inference_example.py
index 85c1ff4a8c..c6bd739e0a 100644
--- a/examples/traffic_analysis/inference_example.py
+++ b/examples/traffic_analysis/inference_example.py
@@ -103,7 +103,7 @@ def __init__(
)
self.detections_manager = DetectionsManager()
- def process_video(self):
+ def process_video(self) -> None:
frame_generator = sv.get_video_frames_generator(
source_path=self.source_video_path
)
diff --git a/examples/traffic_analysis/ultralytics_example.py b/examples/traffic_analysis/ultralytics_example.py
index 9db82a65ee..76d916e69d 100644
--- a/examples/traffic_analysis/ultralytics_example.py
+++ b/examples/traffic_analysis/ultralytics_example.py
@@ -100,7 +100,7 @@ def __init__(
)
self.detections_manager = DetectionsManager()
- def process_video(self):
+ def process_video(self) -> None:
frame_generator = sv.get_video_frames_generator(
source_path=self.source_video_path
)
diff --git a/pyproject.toml b/pyproject.toml
index 563f9c6d0f..2d37cf4fcc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,7 +23,7 @@ maintainers = [
authors = [
{ name = "Roboflow et al.", email = "develop@roboflow.com" },
]
-requires-python = ">=3.9"
+requires-python = ">=3.10"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
@@ -33,7 +33,6 @@ classifiers = [
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
@@ -80,12 +79,12 @@ dev = [
]
docs = [
"mike>=2",
- "mkdocs-git-committers-plugin-2>=2.4.1; python_version>='3.9' and python_version<'4'",
+ "mkdocs-git-committers-plugin-2>=2.4.1; python_version>='3.10' and python_version<'4'",
"mkdocs-git-revision-date-localized-plugin>=1.2.4",
"mkdocs-jupyter>=0.24.3",
"mkdocs-material[imaging]>=9.7",
"mkdocstrings>=0.25.2,<0.31",
- "mkdocstrings-python>=1.10.9,<2", # todo: breaking changes in 2.x
+ "mkdocstrings-python>=1.10.9,<2", # todo: breaking changes in 2.x
]
build = [
"build>=0.10,<1.5",
@@ -101,7 +100,7 @@ packages.find.include = [ "supervision*" ]
# exclude = [ "docs*", "tests*", "examples*" ]
[tool.ruff]
-target-version = "py39"
+target-version = "py310"
line-length = 88
indent-width = 4
# Exclude a variety of commonly ignored directories.
@@ -184,7 +183,7 @@ quiet-level = 3
ignore-words-list = "STrack,sTrack,strack"
[tool.mypy]
-python_version = "3.9"
+python_version = "3.10"
ignore_missing_imports = false
explicit_package_bases = true
strict = true
@@ -200,6 +199,7 @@ overrides = [
"tests.*",
"examples.*",
], ignore_errors = true },
+ { module = "deprecate", ignore_missing_imports = true },
]
[tool.pytest]
diff --git a/src/supervision/annotators/core.py b/src/supervision/annotators/core.py
index d754ac79ca..c2fd8fc9df 100644
--- a/src/supervision/annotators/core.py
+++ b/src/supervision/annotators/core.py
@@ -2,7 +2,7 @@
from functools import lru_cache
from math import sqrt
-from typing import Any, cast, overload
+from typing import cast, overload
import cv2
import numpy as np
@@ -110,7 +110,7 @@ def __init__(
border_radius: int = 0,
smart_position: bool = False,
max_line_length: int | None = None,
- ):
+ ) -> None:
"""
Initializes the _BaseLabelAnnotator.
@@ -148,7 +148,7 @@ def _adjust_labels_in_frame(
resolution_wh: tuple[int, int],
labels: list[str],
label_properties: npt.NDArray[np.float32],
- ) -> npt.NDArray[np.uint8]:
+ ) -> npt.NDArray[np.float32]:
"""
Adjusts the position of labels to ensure they stay within the frame boundaries.
@@ -183,7 +183,10 @@ def _adjust_labels_in_frame(
adjusted_properties[:, :4], resolution_wh
)
- return adjusted_properties
+ return cast(
+ npt.NDArray[np.float32],
+ np.asarray(adjusted_properties, dtype=np.float32),
+ )
class BoxAnnotator(BaseAnnotator):
@@ -196,7 +199,7 @@ def __init__(
color: Color | ColorPalette | str = ColorPalette.DEFAULT,
thickness: int = 2,
color_lookup: ColorLookup = ColorLookup.CLASS,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -283,7 +286,7 @@ def __init__(
color: Color | ColorPalette | str = ColorPalette.DEFAULT,
thickness: int = 2,
color_lookup: ColorLookup = ColorLookup.CLASS,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -374,7 +377,7 @@ def __init__(
color: Color | ColorPalette | str = ColorPalette.DEFAULT,
opacity: float = 0.5,
color_lookup: ColorLookup = ColorLookup.CLASS,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -485,7 +488,7 @@ def __init__(
color: Color | ColorPalette | str = ColorPalette.DEFAULT,
thickness: int = 2,
color_lookup: ColorLookup = ColorLookup.CLASS,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -558,7 +561,7 @@ def annotate(
for polygon in mask_to_polygons(mask=mask):
scene = draw_polygon(
scene=scene,
- polygon=polygon,
+ polygon=polygon.astype(np.int32),
color=color,
thickness=self.thickness,
)
@@ -576,7 +579,7 @@ def __init__(
color: Color | ColorPalette | str = ColorPalette.DEFAULT,
opacity: float = 0.5,
color_lookup: ColorLookup = ColorLookup.CLASS,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -673,7 +676,7 @@ def __init__(
opacity: float = 0.8,
kernel_size: int = 40,
color_lookup: ColorLookup = ColorLookup.CLASS,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -754,7 +757,10 @@ def annotate(
color_bgr = color.as_bgr()
colored_mask[mask] = color_bgr
- colored_mask = cv2.blur(colored_mask, (self.kernel_size, self.kernel_size))
+ colored_mask = cast(
+ npt.NDArray[np.uint8],
+ cv2.blur(colored_mask, (self.kernel_size, self.kernel_size)),
+ )
colored_mask[fmask] = [0, 0, 0]
gray = cv2.cvtColor(colored_mask, cv2.COLOR_BGR2GRAY)
alpha = self.opacity * gray / gray.max()
@@ -776,7 +782,7 @@ def __init__(
start_angle: int = -45,
end_angle: int = 235,
color_lookup: ColorLookup = ColorLookup.CLASS,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -874,7 +880,7 @@ def __init__(
thickness: int = 4,
corner_length: int = 15,
color_lookup: ColorLookup = ColorLookup.CLASS,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -968,7 +974,7 @@ def __init__(
color: Color | ColorPalette | str = ColorPalette.DEFAULT,
thickness: int = 2,
color_lookup: ColorLookup = ColorLookup.CLASS,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -1064,7 +1070,7 @@ def __init__(
color_lookup: ColorLookup = ColorLookup.CLASS,
outline_thickness: int = 0,
outline_color: Color | ColorPalette | str = Color.BLACK,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -1179,7 +1185,7 @@ def __init__(
border_radius: int = 0,
smart_position: bool = False,
max_line_length: int | None = None,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -1278,7 +1284,7 @@ def annotate(
if self.smart_position:
xyxy = label_properties[:, :4]
- xyxy = spread_out_boxes(xyxy)
+ xyxy = cast(npt.NDArray[np.float32], spread_out_boxes(xyxy))
label_properties[:, :4] = xyxy
label_properties = self._adjust_labels_in_frame(
@@ -1301,7 +1307,7 @@ def _get_label_properties(
self,
detections: Detections,
labels: list[str],
- ) -> Any:
+ ) -> npt.NDArray[np.float32]:
label_properties = []
anchors_coordinates: npt.NDArray[np.int32] = detections.get_anchors_coordinates(
anchor=self.text_anchor
@@ -1495,7 +1501,7 @@ def __init__(
border_radius: int = 0,
smart_position: bool = False,
max_line_length: int | None = None,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -1594,7 +1600,7 @@ def annotate(
if self.smart_position:
xyxy = label_properties[:, :4]
- xyxy = spread_out_boxes(xyxy)
+ xyxy = spread_out_boxes(xyxy) # type: ignore[assignment]
label_properties[:, :4] = xyxy
label_properties = self._adjust_labels_in_frame(
@@ -1615,7 +1621,7 @@ def annotate(
def _get_label_properties(
self, draw: ImageDraw.ImageDraw, detections: Detections, labels: list[str]
- ) -> Any:
+ ) -> npt.NDArray[np.float32]:
label_properties = []
anchor_coordinates: npt.NDArray[np.int32] = detections.get_anchors_coordinates(
@@ -1762,7 +1768,7 @@ def __init__(
icon_resolution_wh: tuple[int, int] = (64, 64),
icon_position: Position = Position.TOP_CENTER,
offset_xy: tuple[int, int] = (0, 0),
- ):
+ ) -> None:
"""
Args:
icon_resolution_wh: The size of drawn icons.
@@ -1854,11 +1860,10 @@ def _load_icon(self, icon_path: str) -> npt.NDArray[np.uint8]:
raise FileNotFoundError(
f"Error: Couldn't load the icon image from {icon_path}"
)
- icon = cast(
+ return cast(
npt.NDArray[np.uint8],
letterbox_image(image=icon, resolution_wh=self.icon_resolution_wh),
)
- return icon
class BlurAnnotator(BaseAnnotator):
@@ -1866,7 +1871,7 @@ class BlurAnnotator(BaseAnnotator):
A class for blurring regions in an image using provided detections.
"""
- def __init__(self, kernel_size: int | None = None):
+ def __init__(self, kernel_size: int | None = None) -> None:
"""
Args:
kernel_size: The size of the average pooling kernel used for blurring.
@@ -1920,7 +1925,8 @@ def annotate(
return scene
image_height, image_width = scene.shape[:2]
clipped_xyxy: npt.NDArray[np.int32] = clip_boxes(
- xyxy=detections.xyxy, resolution_wh=(image_width, image_height)
+ xyxy=cast(npt.NDArray[np.float32], detections.xyxy),
+ resolution_wh=(image_width, image_height),
).astype(int)
for x1, y1, x2, y2 in clipped_xyxy:
@@ -1932,7 +1938,7 @@ def annotate(
if self.kernel_size is not None
else calculate_dynamic_kernel_size(x1, y1, x2, y2)
)
- roi = cv2.blur(roi, (kernel_size, kernel_size))
+ roi = cast(npt.NDArray[np.uint8], cv2.blur(roi, (kernel_size, kernel_size)))
scene[y1:y2, x1:x2] = roi
return scene
@@ -1957,7 +1963,7 @@ def __init__(
thickness: int = 2,
smooth: bool = False,
color_lookup: ColorLookup = ColorLookup.CLASS,
- ):
+ ) -> None:
"""
Args:
color: The color to draw the trace, can be
@@ -2038,7 +2044,7 @@ def annotate(
)
filtered_detections: Detections = detections[
detections.tracker_id != PENDING_TRACK_ID
- ] # type: ignore
+ ]
self.trace.put(filtered_detections)
for detection_idx in range(len(filtered_detections)):
@@ -2065,8 +2071,20 @@ def annotate(
try:
x, y = unique_xy[:, 0], unique_xy[:, 1]
tck, _u = splprep([x, y], s=20)
- xy_new = splev(np.linspace(0, 1, 100), tck)
- spline_points = np.stack(xy_new, axis=1).astype(np.int32)
+ x_new, y_new = splev(
+ np.linspace(0, 1, 100),
+ cast(
+ tuple[
+ npt.NDArray[np.float64],
+ npt.NDArray[np.float64],
+ int,
+ ],
+ tck,
+ ),
+ )
+ spline_points = np.stack((x_new, y_new), axis=1).astype(
+ np.int32
+ )
except ValueError:
spline_points = unique_xy.astype(np.int32)
else:
@@ -2098,7 +2116,7 @@ def __init__(
kernel_size: int = 25,
top_hue: int = 0,
low_hue: int = 125,
- ):
+ ) -> None:
"""
Args:
position: The position of the heatmap. Defaults to
@@ -2162,7 +2180,7 @@ def annotate(self, scene: ImageType, detections: Detections) -> ImageType:
if self.heat_mask is None:
self.heat_mask = np.zeros(scene.shape[:2], dtype=np.float32)
- mask = np.zeros(scene.shape[:2])
+ mask: npt.NDArray[np.float32] = np.zeros(scene.shape[:2], dtype=np.float32)
for xy in detections.get_anchors_coordinates(self.position):
x, y = int(xy[0]), int(xy[1])
cv2.circle(
@@ -2173,18 +2191,25 @@ def annotate(self, scene: ImageType, detections: Detections) -> ImageType:
thickness=-1, # fill
)
self.heat_mask = mask + self.heat_mask
- temp = self.heat_mask.copy()
- temp = self.low_hue - temp / temp.max() * (self.low_hue - self.top_hue)
- temp = temp.astype(np.uint8)
+ heat_values = self.heat_mask.copy()
+ max_heat_value = heat_values.max()
+ if max_heat_value == 0:
+ return scene
+ heat_values = self.low_hue - heat_values / max_heat_value * (
+ self.low_hue - self.top_hue
+ )
+ heat_values = heat_values.astype(np.uint8)
if self.kernel_size is not None:
- temp = cv2.blur(temp, (self.kernel_size, self.kernel_size))
+ heat_values = cv2.blur(heat_values, (self.kernel_size, self.kernel_size))
hsv = np.full(scene.shape, 255, dtype=np.uint8)
- hsv[..., 0] = temp
- temp = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
- mask = cv2.cvtColor(self.heat_mask.astype(np.uint8), cv2.COLOR_GRAY2BGR) > 0
- scene[mask] = cv2.addWeighted(temp, self.opacity, scene, 1 - self.opacity, 0)[
- mask
- ]
+ hsv[..., 0] = heat_values
+ heat_bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
+ mask_bool = (
+ cv2.cvtColor(self.heat_mask.astype(np.uint8), cv2.COLOR_GRAY2BGR) > 0
+ )
+ scene[mask_bool] = cv2.addWeighted(
+ heat_bgr, self.opacity, scene, 1 - self.opacity, 0
+ )[mask_bool]
return scene
@@ -2193,7 +2218,7 @@ class PixelateAnnotator(BaseAnnotator):
A class for pixelating regions in an image using provided detections.
"""
- def __init__(self, pixel_size: int | None = None):
+ def __init__(self, pixel_size: int | None = None) -> None:
"""
Args:
pixel_size: The size of the pixelation. If not set, a dynamic size is
@@ -2247,7 +2272,8 @@ def annotate(
return scene
image_height, image_width = scene.shape[:2]
clipped_xyxy: npt.NDArray[np.int32] = clip_boxes(
- xyxy=detections.xyxy, resolution_wh=(image_width, image_height)
+ xyxy=cast(npt.NDArray[np.float32], detections.xyxy),
+ resolution_wh=(image_width, image_height),
).astype(int)
for x1, y1, x2, y2 in clipped_xyxy:
@@ -2297,7 +2323,7 @@ def __init__(
color_lookup: ColorLookup = ColorLookup.CLASS,
outline_thickness: int = 0,
outline_color: Color | ColorPalette | str = Color.BLACK,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -2416,7 +2442,7 @@ def __init__(
thickness: int = 2,
color_lookup: ColorLookup = ColorLookup.CLASS,
roundness: float = 0.6,
- ):
+ ) -> None:
"""
Args:
color: The color or color palette to use for
@@ -2554,7 +2580,7 @@ def __init__(
position: Position = Position.TOP_CENTER,
color_lookup: ColorLookup = ColorLookup.CLASS,
border_thickness: int | None = None,
- ):
+ ) -> None:
"""
Args:
height: The height in pixels of the percentage bar.
@@ -2747,7 +2773,7 @@ def __init__(
border_color: Color | ColorPalette | str = ColorPalette.DEFAULT,
border_thickness: int = 2,
border_color_lookup: ColorLookup = ColorLookup.CLASS,
- ):
+ ) -> None:
"""
Args:
position: The anchor position for placing the cropped and scaled
@@ -2910,7 +2936,7 @@ def __init__(
color: Color = Color.BLACK,
opacity: float = 0.5,
force_box: bool = False,
- ):
+ ) -> None:
"""
Args:
color: The color to use for annotating detections.
@@ -2999,7 +3025,7 @@ def __init__(
label_2: str = "",
label_overlap: str = "",
label_scale: float = 1.0,
- ):
+ ) -> None:
"""
Args:
color_1: Color of areas only present in the first set of
@@ -3136,7 +3162,7 @@ def _mask_from_xyxy(
return mask
resolution_wh = scene.shape[1], scene.shape[0]
- polygons = xyxy_to_polygons(detections.xyxy)
+ polygons = xyxy_to_polygons(cast(npt.NDArray[np.float32], detections.xyxy))
for polygon in polygons:
polygon_mask = polygon_to_mask(polygon, resolution_wh=resolution_wh)
@@ -3153,7 +3179,9 @@ def _mask_from_obb(
resolution_wh = scene.shape[1], scene.shape[0]
- for polygon in detections.data[ORIENTED_BOX_COORDINATES]:
+ for polygon in np.asarray(
+ detections.data[ORIENTED_BOX_COORDINATES], dtype=np.float32
+ ):
polygon_mask = polygon_to_mask(polygon, resolution_wh=resolution_wh)
mask |= polygon_mask.astype(np.bool_)
return mask
diff --git a/src/supervision/annotators/utils.py b/src/supervision/annotators/utils.py
index cfeaf98194..f55e91be0a 100644
--- a/src/supervision/annotators/utils.py
+++ b/src/supervision/annotators/utils.py
@@ -3,7 +3,7 @@
import re
import textwrap
from enum import Enum
-from typing import Any
+from typing import cast
import numpy as np
import numpy.typing as npt
@@ -155,7 +155,7 @@ def resolve_color(
return get_color_by_index(color=color, idx=idx)
-def wrap_text(text: Any, max_line_length: int | None = None) -> list[str]:
+def wrap_text(text: object, max_line_length: int | None = None) -> list[str]:
"""
Wrap `text` to the specified maximum line length, respecting existing
newlines. Falls back to str() if `text` is not already a string.
@@ -254,9 +254,9 @@ def get_labels_text(
def snap_boxes(
- xyxy: np.ndarray[Any, np.dtype[np.float32]],
+ xyxy: npt.NDArray[np.float32],
resolution_wh: tuple[int, int],
-) -> np.ndarray[Any, np.dtype[np.float32]]:
+) -> npt.NDArray[np.float32]:
"""
Shifts `label` bounding boxes into the frame so that they are fully contained
within the given resolution, prioritizing the top/left edge.
@@ -297,7 +297,7 @@ def snap_boxes(
```
"""
- result = np.copy(xyxy)
+ result: npt.NDArray[np.float32] = np.array(xyxy, dtype=np.float32, copy=True)
width, height = resolution_wh
# X-axis (prioritize left edge)
@@ -316,7 +316,7 @@ def snap_boxes(
bottom_shift = height - result[bottom_overflow, 3]
result[bottom_overflow, 1:4:2] += bottom_shift[:, np.newaxis]
- return result.astype(np.float32) # type: ignore
+ return cast(npt.NDArray[np.float32], result.astype(np.float32, copy=False))
class Trace:
@@ -330,9 +330,9 @@ def __init__(
self.max_size = max_size
self.anchor = anchor
- self.frame_id = np.array([], dtype=int)
- self.xy = np.empty((0, 2), dtype=np.float32)
- self.tracker_id = np.array([], dtype=int)
+ self.frame_id: npt.NDArray[np.int_] = np.array([], dtype=int)
+ self.xy: npt.NDArray[np.float32] = np.empty((0, 2), dtype=np.float32)
+ self.tracker_id: npt.NDArray[np.int_] = np.array([], dtype=int)
def put(self, detections: Detections) -> None:
frame_id: npt.NDArray[np.int_] = np.full(
@@ -364,11 +364,11 @@ def put(self, detections: Detections) -> None:
self.current_frame_id += 1
- def get(self, tracker_id: int) -> np.ndarray[Any, np.dtype[np.float32]]:
- filtered: np.ndarray[Any, np.dtype[np.float32]] = (
- self.xy[self.tracker_id == tracker_id].copy().astype(np.float32, copy=False)
+ def get(self, tracker_id: int) -> npt.NDArray[np.float32]:
+ xy: npt.NDArray[np.float32] = np.asarray(
+ self.xy[self.tracker_id == tracker_id], dtype=np.float32
)
- return filtered
+ return xy
def hex_to_rgba(hex_color: str) -> tuple[int, int, int, int]:
diff --git a/src/supervision/dataset/core.py b/src/supervision/dataset/core.py
index fdadce68c6..3bd0833649 100644
--- a/src/supervision/dataset/core.py
+++ b/src/supervision/dataset/core.py
@@ -6,6 +6,7 @@
from dataclasses import dataclass
from itertools import chain
from pathlib import Path
+from typing import cast
import cv2
import numpy as np
@@ -105,7 +106,7 @@ def _get_image(self, image_path: str) -> npt.NDArray[np.uint8]:
image = cv2.imread(image_path)
if image is None:
raise ValueError(f"Could not read image from path: {image_path}")
- return image
+ return cast(npt.NDArray[np.uint8], image)
def __len__(self) -> int:
return len(self._images_in_memory) or len(self.image_paths)
@@ -749,7 +750,7 @@ def _get_image(self, image_path: str) -> npt.NDArray[np.uint8]:
image = cv2.imread(image_path)
if image is None:
raise ValueError(f"Could not read image from path: {image_path}")
- return image
+ return cast(npt.NDArray[np.uint8], image)
def __len__(self) -> int:
return len(self._images_in_memory) or len(self.image_paths)
diff --git a/src/supervision/dataset/formats/coco.py b/src/supervision/dataset/formats/coco.py
index d13440bd22..4581760a09 100644
--- a/src/supervision/dataset/formats/coco.py
+++ b/src/supervision/dataset/formats/coco.py
@@ -1,7 +1,7 @@
import warnings
from datetime import datetime
from pathlib import Path
-from typing import TYPE_CHECKING, Any, Union, cast
+from typing import TYPE_CHECKING, TypeAlias, TypedDict, cast
import numpy as np
import numpy.typing as npt
@@ -11,6 +11,7 @@
map_detections_class_id,
)
from supervision.detection.core import Detections
+from supervision.detection.utils._typing import DetectionDataType
from supervision.detection.utils.converters import (
mask_to_rle,
polygon_to_mask,
@@ -22,10 +23,44 @@
if TYPE_CHECKING:
from supervision.dataset.core import DetectionDataset
-CocoDict = dict[str, Any]
+class _TypeCocoCategory(TypedDict):
+ id: int
+ name: str
+ supercategory: str
-def coco_categories_to_classes(coco_categories: list[CocoDict]) -> list[str]:
+
+class _TypeCocoImage(TypedDict, total=False):
+ id: int
+ file_name: str
+ width: int
+ height: int
+ license: int
+ date_captured: str
+
+
+_TypeCocoRLE: TypeAlias = dict[str, list[int] | str | bytes]
+
+
+class _TypeCocoAnnotation(TypedDict, total=False):
+ id: int
+ image_id: int
+ category_id: int
+ bbox: list[float]
+ area: float
+ segmentation: list[list[float]] | _TypeCocoRLE
+ iscrowd: int
+
+
+class _TypeCocoFile(TypedDict):
+ categories: list[_TypeCocoCategory]
+ images: list[_TypeCocoImage]
+ annotations: list[_TypeCocoAnnotation]
+
+
+def coco_categories_to_classes(
+ coco_categories: list[_TypeCocoCategory],
+) -> list[str]:
return [
category["name"]
for category in sorted(coco_categories, key=lambda category: category["id"])
@@ -33,7 +68,7 @@ def coco_categories_to_classes(coco_categories: list[CocoDict]) -> list[str]:
def build_coco_class_index_mapping(
- coco_categories: list[CocoDict], target_classes: list[str]
+ coco_categories: list[_TypeCocoCategory], target_classes: list[str]
) -> dict[int, int]:
source_class_to_index = {
category["name"]: category["id"] for category in coco_categories
@@ -44,7 +79,7 @@ def build_coco_class_index_mapping(
}
-def classes_to_coco_categories(classes: list[str]) -> list[CocoDict]:
+def classes_to_coco_categories(classes: list[str]) -> list[_TypeCocoCategory]:
return [
{
"id": class_id,
@@ -56,19 +91,16 @@ def classes_to_coco_categories(classes: list[str]) -> list[CocoDict]:
def group_coco_annotations_by_image_id(
- coco_annotations: list[CocoDict],
-) -> dict[int, list[CocoDict]]:
- annotations: dict[int, list[CocoDict]] = {}
+ coco_annotations: list[_TypeCocoAnnotation],
+) -> dict[int, list[_TypeCocoAnnotation]]:
+ annotations: dict[int, list[_TypeCocoAnnotation]] = {}
for annotation in coco_annotations:
- image_id = annotation["image_id"]
- if image_id not in annotations:
- annotations[image_id] = []
- annotations[image_id].append(annotation)
+ annotations.setdefault(annotation["image_id"], []).append(annotation)
return annotations
def coco_annotations_to_masks(
- image_annotations: list[CocoDict], resolution_wh: tuple[int, int]
+ image_annotations: list[_TypeCocoAnnotation], resolution_wh: tuple[int, int]
) -> npt.NDArray[np.bool_]:
height, width = resolution_wh[1], resolution_wh[0]
empty_mask: npt.NDArray[np.bool_] = np.zeros((height, width), dtype=bool)
@@ -82,16 +114,33 @@ def coco_annotations_to_masks(
masks.append(empty_mask.copy())
continue
- if image_annotation.get("iscrowd", 0):
+ if isinstance(segmentation, dict):
+ if "counts" not in segmentation:
+ warnings.warn(
+ "Skipping annotation "
+ f"{image_annotation.get('id', '?')}: segmentation is a dict but "
+ "missing 'counts' key (expected RLE format)",
+ stacklevel=2,
+ )
+ masks.append(empty_mask.copy())
+ continue
masks.append(
- rle_to_mask(rle=segmentation["counts"], resolution_wh=resolution_wh)
+ rle_to_mask(
+ rle=segmentation["counts"],
+ resolution_wh=resolution_wh,
+ )
)
continue
if not isinstance(segmentation, list):
masks.append(empty_mask.copy())
continue
- polygons = segmentation if isinstance(segmentation[0], list) else [segmentation]
+ segmentation_list = segmentation
+ polygons = (
+ segmentation_list
+ if isinstance(segmentation_list[0], list)
+ else [segmentation_list]
+ )
object_mask = empty_mask.copy()
for polygon in polygons:
@@ -117,7 +166,7 @@ def coco_annotations_to_masks(
def coco_annotations_to_detections(
- image_annotations: list[CocoDict],
+ image_annotations: list[_TypeCocoAnnotation],
resolution_wh: tuple[int, int],
with_masks: bool,
use_iscrowd: bool = True,
@@ -125,22 +174,23 @@ def coco_annotations_to_detections(
if not image_annotations:
return Detections.empty()
- class_ids = [
+ class_ids: list[int] = [
image_annotation["category_id"] for image_annotation in image_annotations
]
- xyxy = [image_annotation["bbox"] for image_annotation in image_annotations]
- xyxy = np.asarray(xyxy, dtype=np.float32)
+ xyxy_list: list[list[float]] = [
+ image_annotation["bbox"] for image_annotation in image_annotations
+ ]
+ xyxy = np.asarray(xyxy_list, dtype=np.float32)
xyxy[:, 2:4] += xyxy[:, 0:2]
- data: dict[str, npt.NDArray[np.generic]] = {}
+ data: DetectionDataType = {}
if use_iscrowd:
iscrowd = [
image_annotation["iscrowd"] for image_annotation in image_annotations
]
area = [image_annotation["area"] for image_annotation in image_annotations]
- data = dict(
- iscrowd=np.asarray(iscrowd, dtype=int), area=np.asarray(area, dtype=float)
- )
+ data["iscrowd"] = np.asarray(iscrowd, dtype=np.int64)
+ data["area"] = np.asarray(area, dtype=np.float64)
if with_masks:
mask = coco_annotations_to_masks(
@@ -161,29 +211,31 @@ def detections_to_coco_annotations(
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.75,
-) -> tuple[list[CocoDict], int]:
- coco_annotations: list[CocoDict] = []
+) -> tuple[list[_TypeCocoAnnotation], int]:
+ coco_annotations: list[_TypeCocoAnnotation] = []
for xyxy, mask, _, class_id, _, data in detections:
if class_id is None:
raise ValueError("Detections must include class_id for COCO export.")
box_width, box_height = xyxy[2] - xyxy[0], xyxy[3] - xyxy[1]
- segmentation: Union[list[list[float]], dict[str, list[int]]] = []
+ segmentation: list[list[float]] | _TypeCocoRLE = []
if mask is not None:
+ mask_bool = np.asarray(mask, dtype=bool)
if "iscrowd" in data:
iscrowd = int(np.asarray(data["iscrowd"]).item())
else:
iscrowd = int(
- contains_holes(mask=mask) or contains_multiple_segments(mask=mask)
+ contains_holes(mask=mask_bool)
+ or contains_multiple_segments(mask=mask_bool)
)
if iscrowd:
segmentation = {
- "counts": cast(list[int], mask_to_rle(mask=mask, compressed=False)),
- "size": list(mask.shape[:2]),
+ "counts": mask_to_rle(mask=mask_bool),
+ "size": list(mask_bool.shape[:2]),
}
else:
polygons = approximate_mask_with_polygons(
- mask=mask,
+ mask=mask_bool,
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
@@ -203,7 +255,7 @@ def detections_to_coco_annotations(
iscrowd = int(np.asarray(data.get("iscrowd", 0)).item())
area: float = float(np.asarray(data.get("area", box_width * box_height)).item())
- coco_annotation = {
+ coco_annotation: _TypeCocoAnnotation = {
"id": annotation_id,
"image_id": image_id,
"category_id": int(class_id),
@@ -248,10 +300,11 @@ def get_coco_class_index_mapping(annotations_path: str) -> dict[int, int]:
A mapping from new class id (sequential ranging from 0 to 79)
to original COCO class id (1 to 90 with skipped ids).
"""
- coco_data = read_json_file(annotations_path)
+ coco_data = cast(_TypeCocoFile, read_json_file(annotations_path))
classes = coco_categories_to_classes(coco_categories=coco_data["categories"])
class_mapping = build_coco_class_index_mapping(
- coco_categories=coco_data["categories"], target_classes=classes
+ coco_categories=coco_data["categories"],
+ target_classes=classes,
)
return {v: k for k, v in class_mapping.items()}
@@ -291,11 +344,13 @@ def load_coco_annotations(
attacks when loading user-supplied annotation files. Symlinked images
pointing outside the resolved images directory are also rejected.
"""
- coco_data = read_json_file(file_path=annotations_path)
- classes = coco_categories_to_classes(coco_categories=coco_data["categories"])
+ coco_data = cast(_TypeCocoFile, read_json_file(file_path=annotations_path))
+ coco_categories = coco_data["categories"]
+ classes = coco_categories_to_classes(coco_categories=coco_categories)
class_index_mapping = build_coco_class_index_mapping(
- coco_categories=coco_data["categories"], target_classes=classes
+ coco_categories=coco_categories,
+ target_classes=classes,
)
coco_images = coco_data["images"]
@@ -303,8 +358,8 @@ def load_coco_annotations(
coco_annotations=coco_data["annotations"]
)
- images = []
- annotations = {}
+ images: list[str] = []
+ annotations: dict[str, Detections] = {}
images_directory_resolved = Path(images_directory_path).resolve()
for coco_image in coco_images:
@@ -363,7 +418,7 @@ def load_coco_annotations(
return classes, images, annotations
-def _with_seg_mask(annotation: dict[str, Any]) -> bool:
+def _with_seg_mask(annotation: _TypeCocoAnnotation) -> bool:
return bool(annotation.get("segmentation"))
@@ -443,15 +498,15 @@ def save_coco_annotations(
}
]
- coco_annotations = []
- coco_images = []
+ coco_annotations: list[_TypeCocoAnnotation] = []
+ coco_images: list[_TypeCocoImage] = []
coco_categories = classes_to_coco_categories(classes=dataset.classes)
image_id, annotation_id = starting_image_id, starting_annotation_id
for image_path, image, annotation in dataset:
image_height, image_width, _ = image.shape
image_name = f"{Path(image_path).stem}{Path(image_path).suffix}"
- coco_image = {
+ coco_image: _TypeCocoImage = {
"id": image_id,
"license": 1,
"file_name": image_name,
diff --git a/src/supervision/dataset/formats/pascal_voc.py b/src/supervision/dataset/formats/pascal_voc.py
index 91b6664cdc..2f763e5c28 100644
--- a/src/supervision/dataset/formats/pascal_voc.py
+++ b/src/supervision/dataset/formats/pascal_voc.py
@@ -2,6 +2,7 @@
import os
from pathlib import Path
+from typing import cast
from xml.etree.ElementTree import Element, SubElement
import cv2
@@ -27,7 +28,7 @@ def object_to_pascal_voc(
object_name.text = name
# https://github.com/roboflow/supervision/issues/144
- xyxy += 1
+ xyxy = np.asarray(xyxy).copy() + 1
bndbox = SubElement(root, "bndbox")
xmin = SubElement(bndbox, "xmin")
@@ -41,7 +42,7 @@ def object_to_pascal_voc(
if polygon is not None:
# https://github.com/roboflow/supervision/issues/144
- polygon += 1
+ polygon = np.asarray(polygon).copy() + 1
object_polygon = SubElement(root, "polygon")
for index, point in enumerate(polygon, start=1):
x_coordinate, y_coordinate = point
@@ -57,7 +58,7 @@ def detections_to_pascal_voc(
detections: Detections,
classes: list[str],
filename: str,
- image_shape: tuple[int, int, int],
+ image_shape: tuple[int, ...],
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.75,
@@ -82,7 +83,8 @@ class ids in the Detections object.
Returns:
An XML string in Pascal VOC format representing the detections.
"""
- height, width, depth = image_shape
+ height, width = image_shape[:2]
+ depth = image_shape[2] if len(image_shape) > 2 else 1
# Create root element
annotation = Element("annotation")
@@ -125,7 +127,7 @@ class ids in the Detections object.
name = classes[class_id]
if mask is not None:
polygons = approximate_mask_with_polygons(
- mask=mask,
+ mask=cast(npt.NDArray[np.bool_], mask),
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
@@ -141,7 +143,9 @@ class ids in the Detections object.
annotation.append(next_object)
# Generate XML string
- xml_string = str(parseString(tostring(annotation)).toprettyxml(indent=" "))
+ xml_string = str(
+ parseString(tostring(annotation, encoding="unicode")).toprettyxml(indent=" ")
+ )
return xml_string
@@ -186,6 +190,7 @@ def load_pascal_voc_annotations(
tree = parse(annotation_path)
root = tree.getroot()
+ assert root is not None
image = cv2.imread(image_path)
if image is None:
diff --git a/src/supervision/dataset/formats/yolo.py b/src/supervision/dataset/formats/yolo.py
index 9abaff4595..bb1b3cb0ce 100644
--- a/src/supervision/dataset/formats/yolo.py
+++ b/src/supervision/dataset/formats/yolo.py
@@ -2,8 +2,9 @@
import os
import warnings
+from collections.abc import Sequence
from pathlib import Path
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, cast
import numpy as np
import numpy.typing as npt
@@ -12,6 +13,7 @@
from supervision.config import ORIENTED_BOX_COORDINATES
from supervision.dataset.utils import approximate_mask_with_polygons
from supervision.detection.core import Detections
+from supervision.detection.utils._typing import DetectionDataType
from supervision.detection.utils.converters import polygon_to_mask, polygon_to_xyxy
from supervision.utils.file import (
list_files_with_extensions,
@@ -49,7 +51,7 @@ def _parse_polygon(values: list[str]) -> npt.NDArray[np.float32]:
def _polygons_to_masks(
- polygons: list[npt.NDArray[np.number]], resolution_wh: tuple[int, int]
+ polygons: Sequence[npt.NDArray[np.number]], resolution_wh: tuple[int, int]
) -> npt.NDArray[np.bool_]:
return np.array(
[
@@ -68,7 +70,7 @@ def _with_seg_mask(lines: list[str]) -> bool:
def _extract_class_names(file_path: str) -> list[str]:
- data: dict[str, Any] = read_yaml_file(file_path=file_path)
+ data = read_yaml_file(file_path=file_path)
if not isinstance(data, dict):
raise ValueError(
f"Expected mapping in data.yaml at '{file_path}',"
@@ -99,31 +101,36 @@ def yolo_annotations_to_detections(
if len(lines) == 0:
return Detections.empty()
- class_id, relative_xyxy, relative_polygon, relative_xyxyxyxy = [], [], [], []
+ class_id_list: list[int] = []
+ relative_xyxy_list: list[npt.NDArray[np.float32]] = []
+ relative_polygon_list: list[npt.NDArray[np.float32]] = []
+ relative_xyxyxyxy_list: list[npt.NDArray[np.float32]] = []
w, h = resolution_wh
for line in lines:
values = line.split()
- class_id.append(int(values[0]))
+ class_id_list.append(int(values[0]))
if len(values) == 5:
box = _parse_box(values=values[1:])
- relative_xyxy.append(box)
+ relative_xyxy_list.append(box)
if with_masks:
- relative_polygon.append(_box_to_polygon(box=box))
+ relative_polygon_list.append(_box_to_polygon(box=box))
elif len(values) > 5:
polygon = _parse_polygon(values=values[1:])
- relative_xyxy.append(polygon_to_xyxy(polygon=polygon))
+ relative_xyxy_list.append(
+ np.asarray(polygon_to_xyxy(polygon=polygon), dtype=np.float32)
+ )
if is_obb:
- relative_xyxyxyxy.append(np.array(values[1:]))
+ relative_xyxyxyxy_list.append(np.array(values[1:], dtype=np.float32))
if with_masks:
- relative_polygon.append(polygon)
+ relative_polygon_list.append(polygon)
- class_id = np.array(class_id, dtype=int)
- relative_xyxy = np.array(relative_xyxy, dtype=np.float32)
+ class_id = np.array(class_id_list, dtype=np.int64)
+ relative_xyxy = np.array(relative_xyxy_list, dtype=np.float32)
xyxy = relative_xyxy * np.array([w, h, w, h], dtype=np.float32)
- data = {}
+ data: DetectionDataType = {}
if is_obb:
- relative_xyxyxyxy = np.array(relative_xyxyxyxy, dtype=np.float32)
+ relative_xyxyxyxy = np.array(relative_xyxyxyxy_list, dtype=np.float32)
xyxyxyxy = relative_xyxyxyxy.reshape(-1, 4, 2)
xyxyxyxy *= np.array([w, h], dtype=np.float32)
data[ORIENTED_BOX_COORDINATES] = xyxyxyxy
@@ -133,7 +140,7 @@ def yolo_annotations_to_detections(
polygons = [
polygon * np.array(resolution_wh, dtype=np.float32)
- for polygon in relative_polygon
+ for polygon in relative_polygon_list
]
mask = _polygons_to_masks(polygons=polygons, resolution_wh=resolution_wh)
return Detections(class_id=class_id, xyxy=xyxy, data=data, mask=mask)
@@ -229,10 +236,10 @@ def load_yolo_annotations(
def object_to_yolo(
xyxy: npt.NDArray[np.number],
class_id: int,
- image_shape: tuple[int, int, int],
+ image_shape: tuple[int, ...],
polygon: npt.NDArray[np.number] | None = None,
) -> str:
- h, w, _ = image_shape
+ h, w = image_shape[:2]
if polygon is None:
xyxy_relative = xyxy / np.array([w, h, w, h], dtype=np.float32)
x_min, y_min, x_max, y_max = xyxy_relative
@@ -250,7 +257,7 @@ def object_to_yolo(
def detections_to_yolo_annotations(
detections: Detections,
- image_shape: tuple[int, int, int],
+ image_shape: tuple[int, ...],
min_image_area_percentage: float = 0.0,
max_image_area_percentage: float = 1.0,
approximation_percentage: float = 0.75,
@@ -268,7 +275,7 @@ def detections_to_yolo_annotations(
if mask is not None:
polygons = approximate_mask_with_polygons(
- mask=mask,
+ mask=cast(npt.NDArray[np.bool_], mask),
min_image_area_percentage=min_image_area_percentage,
max_image_area_percentage=max_image_area_percentage,
approximation_percentage=approximation_percentage,
@@ -284,7 +291,9 @@ def detections_to_yolo_annotations(
annotation.append(next_object)
else:
next_object = object_to_yolo(
- xyxy=xyxy, class_id=class_id_int, image_shape=image_shape
+ xyxy=xyxy,
+ class_id=class_id_int,
+ image_shape=image_shape,
)
annotation.append(next_object)
return annotation
diff --git a/src/supervision/dataset/utils.py b/src/supervision/dataset/utils.py
index ab8fa98b6e..e26222dc62 100644
--- a/src/supervision/dataset/utils.py
+++ b/src/supervision/dataset/utils.py
@@ -5,7 +5,7 @@
import random
import shutil
from pathlib import Path
-from typing import TYPE_CHECKING, Any, TypeVar
+from typing import TYPE_CHECKING, TypeVar, cast
import cv2
import numpy as np
@@ -31,16 +31,16 @@ def mask_to_rle(
mask: npt.NDArray[np.bool_], compressed: bool = False
) -> list[int] | str:
"""Deprecated. Use `supervision.detection.utils.converters.mask_to_rle`."""
- return void(mask, compressed) # type: ignore[no-any-return]
+ return cast(list[int] | str, void(mask, compressed))
@deprecated(target=_rle_to_mask, deprecated_in="0.28.0", remove_in="0.30.0") # type: ignore[untyped-decorator]
def rle_to_mask(
- rle: npt.NDArray[np.integer[Any]] | list[int] | str | bytes,
+ rle: npt.NDArray[np.integer] | list[int] | str | bytes,
resolution_wh: tuple[int, int],
) -> npt.NDArray[np.bool_]:
"""Deprecated. Use `supervision.detection.utils.converters.rle_to_mask`."""
- return void(rle, resolution_wh)
+ return cast(npt.NDArray[np.bool_], void(rle, resolution_wh))
if TYPE_CHECKING:
@@ -60,7 +60,7 @@ def approximate_mask_with_polygons(
minimum_detection_area = min_image_area_percentage * image_area
maximum_detection_area = max_image_area_percentage * image_area
- polygons = mask_to_polygons(mask=mask)
+ polygons = cast(list[npt.NDArray[np.number]], mask_to_polygons(mask=mask))
if len(polygons) == 1:
polygons = filter_polygons_by_area(
polygons=polygons, min_area=None, max_area=maximum_detection_area
diff --git a/src/supervision/detection/compact_mask.py b/src/supervision/detection/compact_mask.py
index b2ffdac024..9244159e51 100644
--- a/src/supervision/detection/compact_mask.py
+++ b/src/supervision/detection/compact_mask.py
@@ -13,7 +13,7 @@
import os
from collections.abc import Iterator
-from typing import Any
+from typing import cast, overload
import numpy as np
import numpy.typing as npt
@@ -323,9 +323,10 @@ def _rle_resize(
col_cache: dict[int, list[int]] = {}
scaled_cols = []
for src_c in col_map:
- if src_c not in col_cache:
- col_cache[src_c] = _rle_scale_col(per_col[src_c], crop_h, row_map)
- scaled_cols.append(col_cache[src_c])
+ src_c_int = int(src_c)
+ if src_c_int not in col_cache:
+ col_cache[src_c_int] = _rle_scale_col(per_col[src_c_int], crop_h, row_map)
+ scaled_cols.append(col_cache[src_c_int])
return _rle_join_cols(scaled_cols, new_total)
@@ -476,7 +477,7 @@ def __init__(
def from_dense(
cls,
masks: npt.NDArray[np.bool_],
- xyxy: npt.NDArray[Any],
+ xyxy: npt.NDArray[np.number],
image_shape: tuple[int, int],
) -> CompactMask:
"""Create a :class:`CompactMask` from a dense ``(N, H, W)`` bool array.
@@ -720,7 +721,7 @@ def bbox_xyxy(self) -> npt.NDArray[np.int32]:
return np.column_stack((x1, y1, x2, y2)).astype(np.int32, copy=False)
@property
- def dtype(self) -> np.dtype[Any]:
+ def dtype(self) -> np.dtype[np.bool_]:
"""Return ``np.dtype(bool)`` — always.
Returns:
@@ -763,7 +764,9 @@ def area(self) -> npt.NDArray[np.int64]:
"""
return np.array([_rle_area(rle) for rle in self._rles], dtype=np.int64)
- def sum(self, axis: int | tuple[int, ...] | None = None) -> npt.NDArray[Any] | int:
+ def sum(
+ self, axis: int | tuple[int, ...] | None = None
+ ) -> npt.NDArray[np.int64] | np.int64:
"""NumPy-compatible sum with a fast path for per-mask area.
When ``axis=(1, 2)``, returns the per-mask True-pixel count via
@@ -790,11 +793,32 @@ def sum(self, axis: int | tuple[int, ...] | None = None) -> npt.NDArray[Any] | i
"""
if axis == (1, 2):
return self.area
- return self.to_dense().sum(axis=axis)
+ return cast(npt.NDArray[np.int64] | np.int64, self.to_dense().sum(axis=axis))
+
+ @overload
+ def __getitem__(self, index: int | np.integer) -> npt.NDArray[np.bool_]: ...
+
+ @overload
+ def __getitem__(
+ self,
+ index: slice
+ | list[int]
+ | list[bool]
+ | npt.NDArray[np.int_]
+ | npt.NDArray[np.bool_],
+ ) -> CompactMask: ...
def __getitem__(
self,
- index: int | slice | list[Any] | npt.NDArray[Any],
+ index: (
+ int
+ | np.integer
+ | slice
+ | list[int]
+ | list[bool]
+ | npt.NDArray[np.int_]
+ | npt.NDArray[np.bool_]
+ ),
) -> npt.NDArray[np.bool_] | CompactMask:
"""Index into the mask collection.
@@ -860,7 +884,9 @@ def __getitem__(
new_offsets: npt.NDArray[np.int32] = self._offsets[idx_arr]
return CompactMask(new_rles, new_crop_shapes, new_offsets, self._image_shape)
- def __array__(self, dtype: np.dtype[Any] | None = None) -> npt.NDArray[Any]:
+ def __array__(
+ self, dtype: np.dtype[np.generic] | None = None
+ ) -> npt.NDArray[np.generic]:
"""NumPy interop: materialise as a dense ``(N, H, W)`` array.
Called by ``np.asarray(compact_mask)`` and similar NumPy functions.
diff --git a/src/supervision/detection/core.py b/src/supervision/detection/core.py
index 30baca78b8..942da29337 100644
--- a/src/supervision/detection/core.py
+++ b/src/supervision/detection/core.py
@@ -1,9 +1,10 @@
from __future__ import annotations
-from collections.abc import Iterator
+import builtins
+from collections.abc import Iterator, Mapping, Sequence
from dataclasses import dataclass, field
from functools import reduce
-from typing import Any, cast
+from typing import Protocol, cast, overload
import numpy as np
import numpy.typing as npt
@@ -18,12 +19,15 @@
process_transformers_v4_segmentation_result,
process_transformers_v5_segmentation_result,
)
+from supervision.detection.utils._typing import DetectionDataType
from supervision.detection.utils.converters import (
mask_to_xyxy,
polygon_to_mask,
xywh_to_xyxy,
)
from supervision.detection.utils.internal import (
+ _TypeRoboflowResult,
+ _TypeUltralyticsResult,
extract_ultralytics_masks,
get_data_item,
is_data_equal,
@@ -60,6 +64,114 @@
from supervision.validators import validate_detections_fields, validate_resolution
+class _TypeTensorLike(Protocol):
+ def cpu(self) -> _TypeTensorLike:
+ """Return a CPU copy of the tensor-like object."""
+
+ def numpy(self) -> npt.NDArray[np.generic]:
+ """Convert the tensor-like object to a NumPy array."""
+
+ def astype(self, dtype: object) -> _TypeTensorLike:
+ """Cast the tensor-like object to the requested dtype."""
+
+ def int(self) -> _TypeTensorLike:
+ """Cast the tensor-like object to integers."""
+
+ def numel(self) -> builtins.int:
+ """Return the number of elements."""
+
+
+class _TypeYoloV5Results(Protocol):
+ pred: Sequence[_TypeTensorLike]
+
+
+class _TypeUltralyticsBoxes(Protocol):
+ cls: _TypeTensorLike
+ xyxy: _TypeTensorLike
+ conf: _TypeTensorLike
+ id: _TypeTensorLike | None
+
+
+class _TypeUltralyticsOBB(Protocol):
+ cls: _TypeTensorLike
+ xyxyxyxy: _TypeTensorLike
+ xyxy: _TypeTensorLike
+ conf: _TypeTensorLike
+ id: _TypeTensorLike | None
+
+
+class _TypeUltralyticsResults(_TypeUltralyticsResult, Protocol):
+ names: dict[int, str]
+ boxes: _TypeUltralyticsBoxes | None
+ obb: _TypeUltralyticsOBB | None
+
+ def __len__(self) -> int:
+ """Return the number of detections in the result."""
+
+
+class _TypeYoloNasPrediction(Protocol):
+ bboxes_xyxy: npt.NDArray[np.float32]
+ confidence: npt.NDArray[np.float32]
+ labels: npt.NDArray[np.int_]
+
+
+class _TypeYoloNasResults(Protocol):
+ prediction: _TypeYoloNasPrediction
+
+
+class _TypeTensorflowResult(Protocol):
+ def __getitem__(self, key: str) -> Sequence[_TypeTensorLike]:
+ """Return a tensor sequence for a given prediction key."""
+
+
+class _TypeDeepSparseResults(Protocol):
+ boxes: Sequence[npt.NDArray[np.float32]]
+ scores: Sequence[npt.NDArray[np.float32]]
+ labels: Sequence[npt.NDArray[np.int_]]
+
+
+class _TypeMMDetInstances(Protocol):
+ bboxes: _TypeTensorLike
+ scores: _TypeTensorLike
+ labels: _TypeTensorLike
+ masks: _TypeTensorLike
+
+ def __contains__(self, item: object) -> bool:
+ """Return whether a field is available."""
+
+
+class _TypeMMDetResults(Protocol):
+ pred_instances: _TypeMMDetInstances
+
+
+class _TypeDetectron2Boxes(Protocol):
+ tensor: _TypeTensorLike
+
+
+class _TypeDetectron2Instances(Protocol):
+ pred_boxes: _TypeDetectron2Boxes
+ scores: _TypeTensorLike
+ pred_classes: _TypeTensorLike
+ pred_masks: _TypeTensorLike | None
+
+
+class _TypeDetectron2Results(Protocol):
+ instances: _TypeDetectron2Instances
+
+
+class _TypeNcnnRect(Protocol):
+ x: np.generic
+ y: np.generic
+ w: np.generic
+ h: np.generic
+
+
+class _TypeNcnnResult(Protocol):
+ rect: _TypeNcnnRect
+ prob: float
+ label: int
+
+
@dataclass
class Detections:
"""
@@ -149,13 +261,13 @@ class simplifies data manipulation and filtering, providing a uniform API for
as the video name, camera parameters, timestamp, or other global metadata.
""" # noqa: E501 // docs
- xyxy: npt.NDArray[np.generic]
- mask: npt.NDArray[np.generic] | CompactMask | None = None
- confidence: npt.NDArray[np.generic] | None = None
- class_id: npt.NDArray[np.generic] | None = None
- tracker_id: npt.NDArray[np.generic] | None = None
- data: dict[str, npt.NDArray[np.generic] | list[Any]] = field(default_factory=dict)
- metadata: dict[str, Any] = field(default_factory=dict)
+ xyxy: npt.NDArray[np.number]
+ mask: npt.NDArray[np.bool_] | CompactMask | None = None
+ confidence: npt.NDArray[np.floating] | None = None
+ class_id: npt.NDArray[np.integer] | None = None
+ tracker_id: npt.NDArray[np.integer] | None = None
+ data: DetectionDataType = field(default_factory=dict)
+ metadata: dict[str, object] = field(default_factory=dict)
def __post_init__(self) -> None:
validate_detections_fields(
@@ -177,12 +289,12 @@ def __iter__(
self,
) -> Iterator[
tuple[
- npt.NDArray[np.generic],
- npt.NDArray[np.generic] | None,
- np.generic | None,
- np.generic | None,
- np.generic | None,
- dict[str, npt.NDArray[np.generic] | list[Any]],
+ npt.NDArray[np.number],
+ npt.NDArray[np.bool_] | CompactMask | None,
+ np.floating | None,
+ np.integer | None,
+ np.integer | None,
+ DetectionDataType,
]
]:
"""
@@ -190,32 +302,66 @@ def __iter__(
`(xyxy, mask, confidence, class_id, tracker_id, data)` for each detection.
"""
for i in range(len(self.xyxy)):
+ mask = (
+ cast(npt.NDArray[np.bool_], self.mask[i])
+ if self.mask is not None
+ else None
+ )
+ confidence = (
+ cast(np.floating, self.confidence[i])
+ if self.confidence is not None
+ else None
+ )
+ class_id = (
+ cast(np.integer, self.class_id[i])
+ if self.class_id is not None
+ else None
+ )
+ tracker_id = (
+ cast(np.integer, self.tracker_id[i])
+ if self.tracker_id is not None
+ else None
+ )
yield (
self.xyxy[i],
- self.mask[i] if self.mask is not None else None,
- self.confidence[i] if self.confidence is not None else None,
- self.class_id[i] if self.class_id is not None else None,
- self.tracker_id[i] if self.tracker_id is not None else None,
+ mask,
+ confidence,
+ class_id,
+ tracker_id,
get_data_item(self.data, i),
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Detections):
return NotImplemented
- return all(
- [
- np.array_equal(self.xyxy, other.xyxy),
- np.array_equal(self.mask, other.mask),
- np.array_equal(self.class_id, other.class_id),
- np.array_equal(self.confidence, other.confidence),
- np.array_equal(self.tracker_id, other.tracker_id),
- is_data_equal(self.data, other.data),
- is_metadata_equal(self.metadata, other.metadata),
- ]
+ if not np.array_equal(self.xyxy, other.xyxy):
+ return False
+ if self.mask is None or other.mask is None:
+ if self.mask is not other.mask:
+ return False
+ elif not np.array_equal(np.asarray(self.mask), np.asarray(other.mask)):
+ return False
+ if self.class_id is None or other.class_id is None:
+ if self.class_id is not other.class_id:
+ return False
+ elif not np.array_equal(self.class_id, other.class_id):
+ return False
+ if self.confidence is None or other.confidence is None:
+ if self.confidence is not other.confidence:
+ return False
+ elif not np.array_equal(self.confidence, other.confidence):
+ return False
+ if self.tracker_id is None or other.tracker_id is None:
+ if self.tracker_id is not other.tracker_id:
+ return False
+ elif not np.array_equal(self.tracker_id, other.tracker_id):
+ return False
+ return is_data_equal(self.data, other.data) and is_metadata_equal(
+ self.metadata, other.metadata
)
@classmethod
- def from_yolov5(cls, yolov5_results: Any) -> Detections:
+ def from_yolov5(cls, yolov5_results: _TypeYoloV5Results) -> Detections:
"""
Creates a Detections instance from a
[YOLOv5](https://github.com/ultralytics/yolov5) inference result.
@@ -238,16 +384,21 @@ def from_yolov5(cls, yolov5_results: Any) -> Detections:
detections = sv.Detections.from_yolov5(result)
```
"""
- yolov5_detections_predictions = yolov5_results.pred[0].cpu().cpu().numpy()
+ yolov5_detections_predictions = cast(
+ npt.NDArray[np.float32],
+ yolov5_results.pred[0].cpu().cpu().numpy(),
+ )
return cls(
xyxy=yolov5_detections_predictions[:, :4],
confidence=yolov5_detections_predictions[:, 4],
- class_id=yolov5_detections_predictions[:, 5].astype(int),
+ class_id=yolov5_detections_predictions[:, 5].astype(np.int64),
)
@classmethod
- def from_ultralytics(cls, ultralytics_results: Any) -> Detections:
+ def from_ultralytics(
+ cls, ultralytics_results: _TypeUltralyticsResults
+ ) -> Detections:
"""
Creates a `sv.Detections` instance from a
[YOLOv8](https://github.com/ultralytics/ultralytics) inference result.
@@ -279,15 +430,32 @@ def from_ultralytics(cls, ultralytics_results: Any) -> Detections:
"""
if hasattr(ultralytics_results, "obb") and ultralytics_results.obb is not None:
- class_id = ultralytics_results.obb.cls.cpu().numpy().astype(int)
- class_names = np.array([ultralytics_results.names[i] for i in class_id])
- oriented_box_coordinates = ultralytics_results.obb.xyxyxyxy.cpu().numpy()
+ class_id = cast(
+ npt.NDArray[np.int64],
+ ultralytics_results.obb.cls.cpu().numpy().astype(np.int64),
+ )
+ class_names = np.array(
+ [ultralytics_results.names[int(i)] for i in class_id], dtype=str
+ )
+ oriented_box_coordinates = cast(
+ npt.NDArray[np.float32],
+ ultralytics_results.obb.xyxyxyxy.cpu().numpy().astype(np.float32),
+ )
return cls(
- xyxy=ultralytics_results.obb.xyxy.cpu().numpy(),
- confidence=ultralytics_results.obb.conf.cpu().numpy(),
+ xyxy=cast(
+ npt.NDArray[np.float32],
+ ultralytics_results.obb.xyxy.cpu().numpy().astype(np.float32),
+ ),
+ confidence=cast(
+ npt.NDArray[np.float32],
+ ultralytics_results.obb.conf.cpu().numpy().astype(np.float32),
+ ),
class_id=class_id,
tracker_id=(
- ultralytics_results.obb.id.int().cpu().numpy()
+ cast(
+ npt.NDArray[np.int64],
+ ultralytics_results.obb.id.int().cpu().numpy().astype(np.int64),
+ )
if ultralytics_results.obb.id is not None
else None
),
@@ -299,25 +467,44 @@ def from_ultralytics(cls, ultralytics_results: Any) -> Detections:
if hasattr(ultralytics_results, "boxes") and ultralytics_results.boxes is None:
masks = extract_ultralytics_masks(ultralytics_results)
+ if masks is None:
+ return cls.empty()
return cls(
- xyxy=mask_to_xyxy(masks),
+ xyxy=cast(npt.NDArray[np.float32], mask_to_xyxy(masks)),
mask=masks,
- class_id=np.arange(len(ultralytics_results)),
+ class_id=np.arange(len(ultralytics_results), dtype=np.int64),
)
if (
hasattr(ultralytics_results, "boxes")
and ultralytics_results.boxes is not None
):
- class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int)
- class_names = np.array([ultralytics_results.names[i] for i in class_id])
+ class_id = cast(
+ npt.NDArray[np.int64],
+ ultralytics_results.boxes.cls.cpu().numpy().astype(np.int64),
+ )
+ class_names = np.array(
+ [ultralytics_results.names[int(i)] for i in class_id], dtype=str
+ )
return cls(
- xyxy=ultralytics_results.boxes.xyxy.cpu().numpy(),
- confidence=ultralytics_results.boxes.conf.cpu().numpy(),
+ xyxy=cast(
+ npt.NDArray[np.float32],
+ ultralytics_results.boxes.xyxy.cpu().numpy().astype(np.float32),
+ ),
+ confidence=cast(
+ npt.NDArray[np.float32],
+ ultralytics_results.boxes.conf.cpu().numpy().astype(np.float32),
+ ),
class_id=class_id,
mask=extract_ultralytics_masks(ultralytics_results),
tracker_id=(
- ultralytics_results.boxes.id.int().cpu().numpy()
+ cast(
+ npt.NDArray[np.int64],
+ ultralytics_results.boxes.id.int()
+ .cpu()
+ .numpy()
+ .astype(np.int64),
+ )
if ultralytics_results.boxes.id is not None
else None
),
@@ -327,7 +514,7 @@ def from_ultralytics(cls, ultralytics_results: Any) -> Detections:
return cls.empty()
@classmethod
- def from_yolo_nas(cls, yolo_nas_results: Any) -> Detections:
+ def from_yolo_nas(cls, yolo_nas_results: _TypeYoloNasResults) -> Detections:
"""
Creates a Detections instance from a
[YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md)
@@ -360,12 +547,12 @@ def from_yolo_nas(cls, yolo_nas_results: Any) -> Detections:
return cls(
xyxy=yolo_nas_results.prediction.bboxes_xyxy,
confidence=yolo_nas_results.prediction.confidence,
- class_id=yolo_nas_results.prediction.labels.astype(int),
+ class_id=yolo_nas_results.prediction.labels.astype(np.int64),
)
@classmethod
def from_tensorflow(
- cls, tensorflow_results: dict[str, Any], resolution_wh: tuple[int, int]
+ cls, tensorflow_results: _TypeTensorflowResult, resolution_wh: tuple[int, int]
) -> Detections:
"""
Creates a Detections instance from a
@@ -398,18 +585,26 @@ def from_tensorflow(
```
"""
- boxes = tensorflow_results["detection_boxes"][0].numpy()
+ boxes = cast(
+ npt.NDArray[np.float32], tensorflow_results["detection_boxes"][0].numpy()
+ )
boxes[:, [0, 2]] *= resolution_wh[0]
boxes[:, [1, 3]] *= resolution_wh[1]
boxes = boxes[:, [1, 0, 3, 2]]
return cls(
xyxy=boxes,
- confidence=tensorflow_results["detection_scores"][0].numpy(),
- class_id=tensorflow_results["detection_classes"][0].numpy().astype(int),
+ confidence=cast(
+ npt.NDArray[np.float32],
+ tensorflow_results["detection_scores"][0].numpy(),
+ ),
+ class_id=cast(
+ npt.NDArray[np.int64],
+ tensorflow_results["detection_classes"][0].numpy().astype(np.int64),
+ ),
)
@classmethod
- def from_deepsparse(cls, deepsparse_results: Any) -> Detections:
+ def from_deepsparse(cls, deepsparse_results: _TypeDeepSparseResults) -> Detections:
"""
Creates a Detections instance from a
[DeepSparse](https://github.com/neuralmagic/deepsparse)
@@ -439,13 +634,13 @@ def from_deepsparse(cls, deepsparse_results: Any) -> Detections:
return cls.empty()
return cls(
- xyxy=np.array(deepsparse_results.boxes[0]),
- confidence=np.array(deepsparse_results.scores[0]),
- class_id=np.array(deepsparse_results.labels[0]).astype(float).astype(int),
+ xyxy=np.array(deepsparse_results.boxes[0], dtype=np.float32),
+ confidence=np.array(deepsparse_results.scores[0], dtype=np.float32),
+ class_id=np.array(deepsparse_results.labels[0], dtype=np.int64),
)
@classmethod
- def from_mmdetection(cls, mmdet_results: Any) -> Detections:
+ def from_mmdetection(cls, mmdet_results: _TypeMMDetResults) -> Detections:
"""
Creates a Detections instance from a
[mmdetection](https://github.com/open-mmlab/mmdetection) and
@@ -472,11 +667,23 @@ def from_mmdetection(cls, mmdet_results: Any) -> Detections:
"""
return cls(
- xyxy=mmdet_results.pred_instances.bboxes.cpu().numpy(),
- confidence=mmdet_results.pred_instances.scores.cpu().numpy(),
- class_id=mmdet_results.pred_instances.labels.cpu().numpy().astype(int),
+ xyxy=cast(
+ npt.NDArray[np.float32],
+ mmdet_results.pred_instances.bboxes.cpu().numpy().astype(np.float32),
+ ),
+ confidence=cast(
+ npt.NDArray[np.float32],
+ mmdet_results.pred_instances.scores.cpu().numpy().astype(np.float32),
+ ),
+ class_id=cast(
+ npt.NDArray[np.int64],
+ mmdet_results.pred_instances.labels.cpu().numpy().astype(np.int64),
+ ),
mask=(
- mmdet_results.pred_instances.masks.cpu().numpy()
+ cast(
+ npt.NDArray[np.bool_],
+ mmdet_results.pred_instances.masks.cpu().numpy(),
+ )
if "masks" in mmdet_results.pred_instances
else None
),
@@ -485,7 +692,7 @@ def from_mmdetection(cls, mmdet_results: Any) -> Detections:
@classmethod
def from_transformers(
cls,
- transformers_results: dict[str, Any],
+ transformers_results: dict[str, object],
id2label: dict[int, str] | None = None,
) -> Detections:
"""
@@ -564,7 +771,7 @@ def from_transformers(
)
@classmethod
- def from_detectron2(cls, detectron2_results: Any) -> Detections:
+ def from_detectron2(cls, detectron2_results: Mapping[str, object]) -> Detections:
"""
Create a Detections object from the
[Detectron2](https://github.com/facebookresearch/detectron2) inference result.
@@ -595,23 +802,21 @@ class IDs, and confidences of the predictions.
detections = sv.Detections.from_detectron2(result)
```
"""
+ instances = cast(_TypeDetectron2Instances, detectron2_results["instances"])
return cls(
- xyxy=detectron2_results["instances"].pred_boxes.tensor.cpu().numpy(),
- confidence=detectron2_results["instances"].scores.cpu().numpy(),
+ xyxy=instances.pred_boxes.tensor.cpu().numpy().astype(np.float32),
+ confidence=instances.scores.cpu().numpy().astype(np.float32),
mask=(
- detectron2_results["instances"].pred_masks.cpu().numpy()
- if hasattr(detectron2_results["instances"], "pred_masks")
+ cast(npt.NDArray[np.bool_], instances.pred_masks.cpu().numpy())
+ if instances.pred_masks is not None
else None
),
- class_id=detectron2_results["instances"]
- .pred_classes.cpu()
- .numpy()
- .astype(int),
+ class_id=instances.pred_classes.cpu().numpy().astype(np.int64),
)
@classmethod
- def from_inference(cls, roboflow_result: dict[str, Any] | Any) -> Detections:
+ def from_inference(cls, roboflow_result: object) -> Detections:
"""
Create a `sv.Detections` object from the [Roboflow](https://roboflow.com/)
API inference result or the [Inference](https://inference.roboflow.com/)
@@ -645,12 +850,12 @@ def from_inference(cls, roboflow_result: dict[str, Any] | Any) -> Detections:
elif hasattr(roboflow_result, "json"):
roboflow_result = roboflow_result.json()
xyxy, confidence, class_id, masks, trackers, data = process_roboflow_result(
- roboflow_result=roboflow_result
+ roboflow_result=cast(_TypeRoboflowResult, roboflow_result)
)
if np.asarray(xyxy).shape[0] == 0:
empty_detection = cls.empty()
- empty_detection.data = {CLASS_NAME_DATA_FIELD: np.empty(0)}
+ empty_detection.data = {CLASS_NAME_DATA_FIELD: np.empty(0, dtype=str)}
return empty_detection
return cls(
@@ -663,7 +868,7 @@ def from_inference(cls, roboflow_result: dict[str, Any] | Any) -> Detections:
)
@classmethod
- def from_sam(cls, sam_result: list[dict[str, Any]]) -> Detections:
+ def from_sam(cls, sam_result: list[dict[str, object]]) -> Detections:
"""
Creates a Detections instance from
[Segment Anything Model](https://github.com/facebookresearch/segment-anything)
@@ -692,11 +897,29 @@ def from_sam(cls, sam_result: list[dict[str, Any]]) -> Detections:
"""
sorted_generated_masks = sorted(
- sam_result, key=lambda x: x["area"], reverse=True
+ sam_result, key=lambda x: cast(float, x["area"]), reverse=True
)
- xywh = np.array([mask["bbox"] for mask in sorted_generated_masks])
- mask = np.array([mask["segmentation"] for mask in sorted_generated_masks])
+ xywh = cast(
+ npt.NDArray[np.float32],
+ np.array(
+ [
+ cast(Sequence[float], mask["bbox"])
+ for mask in sorted_generated_masks
+ ],
+ dtype=np.float32,
+ ),
+ )
+ mask = cast(
+ npt.NDArray[np.bool_],
+ np.array(
+ [
+ cast(npt.NDArray[np.bool_], mask["segmentation"])
+ for mask in sorted_generated_masks
+ ],
+ dtype=bool,
+ ),
+ )
if np.asarray(xywh).shape[0] == 0:
return cls.empty()
@@ -706,7 +929,7 @@ def from_sam(cls, sam_result: list[dict[str, Any]]) -> Detections:
@classmethod
def from_sam3(
- cls, sam3_result: dict[str, Any] | Any, resolution_wh: tuple[int, int]
+ cls, sam3_result: dict[str, object] | object, resolution_wh: tuple[int, int]
) -> Detections:
"""
Creates a Detections instance from
@@ -758,15 +981,18 @@ def from_sam3(
"""
width, height = validate_resolution(resolution_wh)
- masks = []
- confidences = []
- class_ids = []
+ masks: list[npt.NDArray[np.bool_]] = []
+ confidences: list[float] = []
+ class_ids: list[int] = []
if isinstance(sam3_result, dict):
- prompt_results = sam3_result.get("prompt_results", [])
+ prompt_results = cast(list[object], sam3_result.get("prompt_results", []))
if not prompt_results and "predictions" in sam3_result:
prompt_results = [
- {"predictions": sam3_result["predictions"], "prompt_index": 0}
+ {
+ "predictions": sam3_result["predictions"],
+ "prompt_index": 0,
+ }
]
else:
prompt_results = getattr(sam3_result, "prompt_results", [])
@@ -780,25 +1006,34 @@ def from_sam3(
for i, prompt_result in enumerate(prompt_results):
if isinstance(prompt_result, dict):
- predictions = prompt_result.get("predictions", [])
- prompt_index = prompt_result.get("prompt_index", i)
+ predictions = cast(list[object], prompt_result.get("predictions", []))
+ prompt_index = cast(int, prompt_result.get("prompt_index", i))
else:
- predictions = getattr(prompt_result, "predictions", [])
- prompt_index = getattr(prompt_result, "prompt_index", i)
+ predictions = cast(
+ list[object], getattr(prompt_result, "predictions", [])
+ )
+ prompt_index = cast(int, getattr(prompt_result, "prompt_index", i))
for prediction in predictions:
if isinstance(prediction, dict):
- prediction_format = prediction.get("format")
+ prediction_format = cast(str | None, prediction.get("format"))
if prediction_format and prediction_format != "polygon":
continue
- pred_masks = prediction.get("masks", [])
- confidence = prediction.get("confidence", 1.0)
+ pred_masks = cast(
+ list[Sequence[Sequence[float]]], prediction.get("masks", [])
+ )
+ confidence = cast(float, prediction.get("confidence", 1.0))
else:
- prediction_format = getattr(prediction, "format", None)
+ prediction_format = cast(
+ str | None, getattr(prediction, "format", None)
+ )
if prediction_format and prediction_format != "polygon":
continue
- pred_masks = getattr(prediction, "masks", [])
- confidence = getattr(prediction, "confidence", 1.0)
+ pred_masks = cast(
+ list[Sequence[Sequence[float]]],
+ getattr(prediction, "masks", []),
+ )
+ confidence = cast(float, getattr(prediction, "confidence", 1.0))
if not pred_masks:
continue
@@ -820,18 +1055,18 @@ def from_sam3(
return cls.empty()
masks_np = np.stack(masks, axis=0)
- xyxy = mask_to_xyxy(masks_np)
+ xyxy = cast(npt.NDArray[np.float32], mask_to_xyxy(masks_np))
return cls(
- xyxy=xyxy.astype(np.float32),
+ xyxy=xyxy,
mask=masks_np,
confidence=np.array(confidences, dtype=np.float32),
- class_id=np.array(class_ids, dtype=int),
+ class_id=np.array(class_ids, dtype=np.int64),
)
@classmethod
def from_azure_analyze_image(
- cls, azure_result: dict[str, Any], class_map: dict[int, str] | None = None
+ cls, azure_result: dict[str, object], class_map: dict[int, str] | None = None
) -> Detections:
"""
Creates a Detections instance from [Azure Image Analysis 4.0](
@@ -871,11 +1106,12 @@ def from_azure_analyze_image(
```
"""
if "error" in azure_result:
- raise ValueError(
- f"Azure API returned an error {azure_result['error']['message']}"
- )
+ error_message = cast(dict[str, object], azure_result["error"])["message"]
+ raise ValueError(f"Azure API returned an error {error_message}")
- xyxy, confidences, class_ids = [], [], []
+ xyxy: list[list[float]] = []
+ confidences: list[float] = []
+ class_ids: list[int] = []
is_dynamic_mapping = class_map is None
if class_map is None:
@@ -883,10 +1119,11 @@ def from_azure_analyze_image(
inverted_map: dict[str, int] = {value: key for key, value in class_map.items()}
- for detection in azure_result["objectsResult"]["values"]:
- bbox = detection["boundingBox"]
+ objects_result = cast(dict[str, object], azure_result["objectsResult"])
+ for detection in cast(list[dict[str, object]], objects_result["values"]):
+ bbox = cast(dict[str, float], detection["boundingBox"])
- tags = detection["tags"]
+ tags = cast(list[dict[str, object]], detection["tags"])
x0 = bbox["x"]
y0 = bbox["y"]
@@ -894,8 +1131,8 @@ def from_azure_analyze_image(
y1 = y0 + bbox["h"]
for tag in tags:
- confidence = tag["confidence"]
- class_name: str = tag["name"]
+ confidence = cast(float, tag["confidence"])
+ class_name = cast(str, tag["name"])
class_id_val: int | None = inverted_map.get(class_name, None)
if is_dynamic_mapping and class_id_val is None:
@@ -911,13 +1148,15 @@ def from_azure_analyze_image(
return Detections.empty()
return cls(
- xyxy=np.array(xyxy),
- class_id=np.array(class_ids),
- confidence=np.array(confidences),
+ xyxy=np.array(xyxy, dtype=np.float32),
+ class_id=np.array(class_ids, dtype=np.int64),
+ confidence=np.array(confidences, dtype=np.float32),
)
@classmethod
- def from_paddledet(cls, paddledet_result: Any) -> Detections:
+ def from_paddledet(
+ cls, paddledet_result: dict[str, npt.NDArray[np.generic]]
+ ) -> Detections:
"""
Creates a Detections instance from
[PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection)
@@ -953,14 +1192,17 @@ def from_paddledet(cls, paddledet_result: Any) -> Detections:
return cls.empty()
return cls(
- xyxy=paddledet_result["bbox"][:, 2:6],
- confidence=paddledet_result["bbox"][:, 1],
- class_id=paddledet_result["bbox"][:, 0].astype(int),
+ xyxy=cast(npt.NDArray[np.float32], paddledet_result["bbox"][:, 2:6]),
+ confidence=cast(npt.NDArray[np.float32], paddledet_result["bbox"][:, 1]),
+ class_id=cast(
+ npt.NDArray[np.int64],
+ paddledet_result["bbox"][:, 0].astype(np.int64),
+ ),
)
@classmethod
def from_lmm(
- cls, lmm: LMM | str, result: str | dict[str, Any], **kwargs: Any
+ cls, lmm: LMM | str, result: str | dict[str, object], **kwargs: object
) -> Detections:
"""
!!! deprecated "Deprecated"
@@ -1449,7 +1691,7 @@ def from_lmm(
@classmethod
def from_vlm(
- cls, vlm: VLM | str, result: str | dict[str, Any], **kwargs: Any
+ cls, vlm: VLM | str, result: str | dict[str, object], **kwargs: object
) -> Detections:
"""
@@ -1874,79 +2116,165 @@ def from_vlm(
if vlm == VLM.PALIGEMMA:
assert isinstance(result, str)
- xyxy, class_id, class_name = from_paligemma(result, **kwargs)
- data: dict[str, npt.NDArray[np.generic] | list[Any]] = {
- CLASS_NAME_DATA_FIELD: class_name,
+ resolution_wh = cast(tuple[int, int], kwargs["resolution_wh"])
+ classes = cast(list[str] | None, kwargs.get("classes"))
+ paligemma_xyxy, paligemma_class_id, paligemma_class_name = from_paligemma(
+ result,
+ resolution_wh=resolution_wh,
+ classes=classes,
+ )
+ paligemma_data: DetectionDataType = {
+ CLASS_NAME_DATA_FIELD: cast(
+ npt.NDArray[np.generic], paligemma_class_name
+ ),
}
- return cls(xyxy=xyxy, class_id=class_id, data=data)
+ return cls(
+ xyxy=paligemma_xyxy,
+ class_id=paligemma_class_id,
+ data=paligemma_data,
+ )
if vlm == VLM.QWEN_2_5_VL:
assert isinstance(result, str)
- xyxy, class_id, class_name = from_qwen_2_5_vl(result, **kwargs)
- data = {CLASS_NAME_DATA_FIELD: class_name}
- confidence_arr: npt.NDArray[np.floating[Any]] = np.ones(
- len(xyxy), dtype=float
+ input_wh = cast(tuple[int, int], kwargs["input_wh"])
+ resolution_wh = cast(tuple[int, int], kwargs["resolution_wh"])
+ classes = cast(list[str] | None, kwargs.get("classes"))
+ qwen_xyxy, qwen_class_id, qwen_class_name = from_qwen_2_5_vl(
+ result,
+ input_wh=input_wh,
+ resolution_wh=resolution_wh,
+ classes=classes,
+ )
+ qwen_data: DetectionDataType = {
+ CLASS_NAME_DATA_FIELD: cast(npt.NDArray[np.generic], qwen_class_name)
+ }
+ confidence_arr: npt.NDArray[np.float32] = np.ones(
+ len(qwen_xyxy), dtype=np.float32
)
return cls(
- xyxy=xyxy, class_id=class_id, confidence=confidence_arr, data=data
+ xyxy=qwen_xyxy,
+ class_id=qwen_class_id,
+ confidence=confidence_arr,
+ data=qwen_data,
)
if vlm == VLM.QWEN_3_VL:
assert isinstance(result, str)
- xyxy, class_id, class_name = from_qwen_3_vl(result, **kwargs)
- data = {CLASS_NAME_DATA_FIELD: class_name}
- confidence_arr = np.ones(len(xyxy), dtype=float)
+ resolution_wh = cast(tuple[int, int], kwargs["resolution_wh"])
+ classes = cast(list[str] | None, kwargs.get("classes"))
+ qwen3_xyxy, qwen3_class_id, qwen3_class_name = from_qwen_3_vl(
+ result,
+ resolution_wh=resolution_wh,
+ classes=classes,
+ )
+ qwen3_data: DetectionDataType = {
+ CLASS_NAME_DATA_FIELD: cast(npt.NDArray[np.generic], qwen3_class_name)
+ }
+ confidence_arr = np.ones(len(qwen3_xyxy), dtype=float)
return cls(
- xyxy=xyxy, class_id=class_id, confidence=confidence_arr, data=data
+ xyxy=qwen3_xyxy,
+ class_id=qwen3_class_id,
+ confidence=confidence_arr,
+ data=qwen3_data,
)
if vlm == VLM.DEEPSEEK_VL_2:
assert isinstance(result, str)
- xyxy, class_id, class_name = from_deepseek_vl_2(result, **kwargs)
- data = {CLASS_NAME_DATA_FIELD: class_name}
- return cls(xyxy=xyxy, class_id=class_id, data=data)
+ resolution_wh = cast(tuple[int, int], kwargs["resolution_wh"])
+ classes = cast(list[str] | None, kwargs.get("classes"))
+ deepseek_xyxy, deepseek_class_id, deepseek_class_name = from_deepseek_vl_2(
+ result,
+ resolution_wh=resolution_wh,
+ classes=classes,
+ )
+ deepseek_data: DetectionDataType = {
+ CLASS_NAME_DATA_FIELD: cast(
+ npt.NDArray[np.generic], deepseek_class_name
+ )
+ }
+ return cls(
+ xyxy=deepseek_xyxy, class_id=deepseek_class_id, data=deepseek_data
+ )
if vlm == VLM.FLORENCE_2:
assert isinstance(result, dict)
- xyxy, labels, mask, xyxyxyxy = from_florence_2(result, **kwargs)
- if len(xyxy) == 0:
+ resolution_wh = cast(tuple[int, int], kwargs["resolution_wh"])
+ florence_xyxy, florence_labels, florence_mask, florence_xyxyxyxy = (
+ from_florence_2(result, resolution_wh=resolution_wh)
+ )
+ if len(florence_xyxy) == 0:
return cls.empty()
- data = {}
- if labels is not None:
- data[CLASS_NAME_DATA_FIELD] = labels
- if xyxyxyxy is not None:
- data[ORIENTED_BOX_COORDINATES] = xyxyxyxy
+ florence_data: DetectionDataType = {}
+ if florence_labels is not None:
+ florence_data[CLASS_NAME_DATA_FIELD] = cast(
+ npt.NDArray[np.generic], florence_labels
+ )
+ if florence_xyxyxyxy is not None:
+ florence_data[ORIENTED_BOX_COORDINATES] = cast(
+ npt.NDArray[np.generic], florence_xyxyxyxy
+ )
- return cls(xyxy=xyxy, mask=mask, data=data)
+ return cls(
+ xyxy=florence_xyxy,
+ mask=florence_mask,
+ data=florence_data,
+ )
if vlm == VLM.GOOGLE_GEMINI_2_0:
assert isinstance(result, str)
- xyxy, class_id, class_name = from_google_gemini_2_0(result, **kwargs)
- data = {CLASS_NAME_DATA_FIELD: class_name}
- return cls(xyxy=xyxy, class_id=class_id, data=data)
+ resolution_wh = cast(tuple[int, int], kwargs["resolution_wh"])
+ classes = cast(list[str] | None, kwargs.get("classes"))
+ gemini20_xyxy, gemini20_class_id, gemini20_class_name = (
+ from_google_gemini_2_0(
+ result,
+ resolution_wh=resolution_wh,
+ classes=classes,
+ )
+ )
+ gemini20_data: DetectionDataType = {
+ CLASS_NAME_DATA_FIELD: cast(
+ npt.NDArray[np.generic], gemini20_class_name
+ )
+ }
+ return cls(
+ xyxy=gemini20_xyxy,
+ class_id=gemini20_class_id,
+ data=gemini20_data,
+ )
if vlm == VLM.MOONDREAM:
assert isinstance(result, dict)
- xyxy = from_moondream(result, **kwargs)
- return cls(xyxy=xyxy)
+ resolution_wh = cast(tuple[int, int], kwargs["resolution_wh"])
+ moondream_xyxy = from_moondream(result, resolution_wh=resolution_wh)
+ return cls(xyxy=moondream_xyxy)
if vlm == VLM.GOOGLE_GEMINI_2_5:
assert isinstance(result, str)
- gemini_result = from_google_gemini_2_5(result, **kwargs)
- data = {CLASS_NAME_DATA_FIELD: gemini_result[2]}
+ resolution_wh = cast(tuple[int, int], kwargs["resolution_wh"])
+ classes = cast(list[str] | None, kwargs.get("classes"))
+ gemini25_result = from_google_gemini_2_5(
+ result,
+ resolution_wh=resolution_wh,
+ classes=classes,
+ )
+ gemini25_data: DetectionDataType = {
+ CLASS_NAME_DATA_FIELD: cast(npt.NDArray[np.generic], gemini25_result[2])
+ }
return cls(
- xyxy=gemini_result[0],
- class_id=gemini_result[1],
- mask=gemini_result[4],
- confidence=gemini_result[3],
- data=data,
+ xyxy=gemini25_result[0],
+ class_id=gemini25_result[1],
+ mask=gemini25_result[4],
+ confidence=gemini25_result[3],
+ data=gemini25_data,
)
return cls.empty()
@classmethod
- def from_easyocr(cls, easyocr_results: list[Any]) -> Detections:
+ def from_easyocr(
+ cls, easyocr_results: list[tuple[Sequence[Sequence[float]], str, float | None]]
+ ) -> Detections:
"""
Create a Detections object from the
[EasyOCR](https://github.com/JaidedAI/EasyOCR) result.
@@ -1992,7 +2320,7 @@ def from_easyocr(cls, easyocr_results: list[Any]) -> Detections:
)
@classmethod
- def from_ncnn(cls, ncnn_results: Any) -> Detections:
+ def from_ncnn(cls, ncnn_results: Sequence[_TypeNcnnResult]) -> Detections:
"""
Creates a Detections instance from the
[ncnn](https://github.com/Tencent/ncnn) inference result.
@@ -2165,7 +2493,10 @@ def merge(cls, detections_list: list[Detections]) -> Detections:
data=detections.data,
)
- xyxy = np.vstack([d.xyxy for d in detections_list])
+ xyxy = cast(
+ npt.NDArray[np.number],
+ np.vstack([np.asarray(d.xyxy) for d in detections_list]),
+ )
def stack_or_none(
name: str,
@@ -2177,15 +2508,21 @@ def stack_or_none(
if name == "mask":
masks = [d.__getattribute__(name) for d in detections_list]
if all(isinstance(m, CompactMask) for m in masks):
- return CompactMask.merge(masks)
+ return CompactMask.merge(cast(list[CompactMask], masks))
# Mixed or all-ndarray: __array__ auto-converts any CompactMask.
- return np.vstack([np.asarray(m) for m in masks])
- return np.hstack([d.__getattribute__(name) for d in detections_list])
+ return cast(
+ npt.NDArray[np.generic],
+ np.vstack([np.asarray(m) for m in masks]),
+ )
+ return cast(
+ npt.NDArray[np.generic],
+ np.hstack([d.__getattribute__(name) for d in detections_list]),
+ )
- mask = stack_or_none("mask")
- confidence = stack_or_none("confidence")
- class_id = stack_or_none("class_id")
- tracker_id = stack_or_none("tracker_id")
+ mask = cast(npt.NDArray[np.bool_] | CompactMask | None, stack_or_none("mask"))
+ confidence = cast(npt.NDArray[np.floating] | None, stack_or_none("confidence"))
+ class_id = cast(npt.NDArray[np.integer] | None, stack_or_none("class_id"))
+ tracker_id = cast(npt.NDArray[np.integer] | None, stack_or_none("tracker_id"))
data = merge_data([d.data for d in detections_list])
@@ -2202,7 +2539,7 @@ def stack_or_none(
metadata=metadata,
)
- def get_anchors_coordinates(self, anchor: Position) -> npt.NDArray[np.generic]:
+ def get_anchors_coordinates(self, anchor: Position) -> npt.NDArray[np.number]:
"""
Calculates and returns the coordinates of a specific anchor point
within the bounding boxes defined by the `xyxy` attribute. The anchor
@@ -2221,55 +2558,102 @@ def get_anchors_coordinates(self, anchor: Position) -> npt.NDArray[np.generic]:
Raises:
ValueError: If the provided `anchor` is not supported.
"""
+ xyxy = self.xyxy
if anchor == Position.CENTER:
- return np.array(
- [
- (self.xyxy[:, 0] + self.xyxy[:, 2]) / 2,
- (self.xyxy[:, 1] + self.xyxy[:, 3]) / 2,
- ]
- ).transpose()
+ return cast(
+ npt.NDArray[np.number],
+ np.array(
+ [
+ (xyxy[:, 0] + xyxy[:, 2]) / 2,
+ (xyxy[:, 1] + xyxy[:, 3]) / 2,
+ ]
+ ).transpose(),
+ )
elif anchor == Position.CENTER_OF_MASS:
if self.mask is None:
raise ValueError(
"Cannot use `Position.CENTER_OF_MASS` without a detection mask."
)
- return calculate_masks_centroids(masks=self.mask)
+ return cast(
+ npt.NDArray[np.number], calculate_masks_centroids(masks=self.mask)
+ )
elif anchor == Position.CENTER_LEFT:
- return np.array(
- [
- self.xyxy[:, 0],
- (self.xyxy[:, 1] + self.xyxy[:, 3]) / 2,
- ]
- ).transpose()
+ return cast(
+ npt.NDArray[np.number],
+ np.array(
+ [
+ xyxy[:, 0],
+ (xyxy[:, 1] + xyxy[:, 3]) / 2,
+ ]
+ ).transpose(),
+ )
elif anchor == Position.CENTER_RIGHT:
- return np.array(
- [
- self.xyxy[:, 2],
- (self.xyxy[:, 1] + self.xyxy[:, 3]) / 2,
- ]
- ).transpose()
+ return cast(
+ npt.NDArray[np.number],
+ np.array(
+ [
+ xyxy[:, 2],
+ (xyxy[:, 1] + xyxy[:, 3]) / 2,
+ ]
+ ).transpose(),
+ )
elif anchor == Position.BOTTOM_CENTER:
- return np.array(
- [(self.xyxy[:, 0] + self.xyxy[:, 2]) / 2, self.xyxy[:, 3]]
- ).transpose()
+ return cast(
+ npt.NDArray[np.number],
+ np.array([(xyxy[:, 0] + xyxy[:, 2]) / 2, xyxy[:, 3]]).transpose(),
+ )
elif anchor == Position.BOTTOM_LEFT:
- return np.array([self.xyxy[:, 0], self.xyxy[:, 3]]).transpose()
+ return cast(
+ npt.NDArray[np.number],
+ np.array([xyxy[:, 0], xyxy[:, 3]]).transpose(),
+ )
elif anchor == Position.BOTTOM_RIGHT:
- return np.array([self.xyxy[:, 2], self.xyxy[:, 3]]).transpose()
+ return cast(
+ npt.NDArray[np.number],
+ np.array([xyxy[:, 2], xyxy[:, 3]]).transpose(),
+ )
elif anchor == Position.TOP_CENTER:
- return np.array(
- [(self.xyxy[:, 0] + self.xyxy[:, 2]) / 2, self.xyxy[:, 1]]
- ).transpose()
+ return cast(
+ npt.NDArray[np.number],
+ np.array([(xyxy[:, 0] + xyxy[:, 2]) / 2, xyxy[:, 1]]).transpose(),
+ )
elif anchor == Position.TOP_LEFT:
- return np.array([self.xyxy[:, 0], self.xyxy[:, 1]]).transpose()
+ return cast(
+ npt.NDArray[np.number],
+ np.array([xyxy[:, 0], xyxy[:, 1]]).transpose(),
+ )
elif anchor == Position.TOP_RIGHT:
- return np.array([self.xyxy[:, 2], self.xyxy[:, 1]]).transpose()
+ return cast(
+ npt.NDArray[np.number],
+ np.array([xyxy[:, 2], xyxy[:, 1]]).transpose(),
+ )
raise ValueError(f"{anchor} is not supported.")
+ @overload
+ def __getitem__(self, index: str) -> object: ...
+
+ @overload
def __getitem__(
- self, index: int | slice | list[int] | npt.NDArray[np.generic] | str
- ) -> Detections | list[Any] | npt.NDArray[np.generic] | None:
+ self,
+ index: int
+ | slice
+ | list[int]
+ | list[bool]
+ | npt.NDArray[np.int_]
+ | npt.NDArray[np.bool_],
+ ) -> Detections: ...
+
+ def __getitem__(
+ self,
+ index: int
+ | slice
+ | list[int]
+ | list[bool]
+ | npt.NDArray[np.int_]
+ | npt.NDArray[np.bool_]
+ | str,
+ ) -> Detections | object | None:
"""
Get a subset of the Detections object or access an item from its data field.
@@ -2306,23 +2690,29 @@ def __getitem__(
return self
if isinstance(index, int):
index = [index]
+ mask = self.mask[index] if self.mask is not None else None
+ confidence = self.confidence[index] if self.confidence is not None else None
+ class_id = self.class_id[index] if self.class_id is not None else None
+ tracker_id = self.tracker_id[index] if self.tracker_id is not None else None
return Detections(
xyxy=self.xyxy[index],
- mask=self.mask[index] if self.mask is not None else None,
- confidence=self.confidence[index] if self.confidence is not None else None,
- class_id=self.class_id[index] if self.class_id is not None else None,
- tracker_id=self.tracker_id[index] if self.tracker_id is not None else None,
+ mask=mask,
+ confidence=confidence,
+ class_id=class_id,
+ tracker_id=tracker_id,
data=get_data_item(self.data, index),
metadata=self.metadata,
)
- def __setitem__(self, key: str, value: npt.NDArray[np.generic] | list[Any]) -> None:
+ def __setitem__(
+ self, key: str, value: npt.NDArray[np.generic] | list[object]
+ ) -> None:
"""
Set a value in the data dictionary of the Detections object.
Args:
key: The key in the data dictionary to set.
- value: The value to set for the key.
+ value: The value to set for the key. Must be a np.ndarray or list.
Example:
```python
@@ -2352,7 +2742,7 @@ def __setitem__(self, key: str, value: npt.NDArray[np.generic] | list[Any]) -> N
self.data[key] = value
@property
- def area(self) -> npt.NDArray[np.generic]:
+ def area(self) -> npt.NDArray[np.number]:
"""
Calculate the area of each detection in the set of object detections.
If masks field is defined property returns are of each mask.
@@ -2367,11 +2757,10 @@ def area(self) -> npt.NDArray[np.generic]:
if isinstance(self.mask, CompactMask):
return self.mask.area
return np.array([np.sum(mask) for mask in self.mask])
- else:
- return self.box_area
+ return self.box_area
@property
- def box_area(self) -> npt.NDArray[np.generic]:
+ def box_area(self) -> npt.NDArray[np.number]:
"""
Calculate the area of each bounding box in the set of object detections.
@@ -2380,10 +2769,11 @@ def box_area(self) -> npt.NDArray[np.generic]:
box in the format of `(area_1, area_2, ..., area_n)`,
where n is the number of detections.
"""
- return (self.xyxy[:, 3] - self.xyxy[:, 1]) * (self.xyxy[:, 2] - self.xyxy[:, 0])
+ xyxy = self.xyxy
+ return (xyxy[:, 3] - xyxy[:, 1]) * (xyxy[:, 2] - xyxy[:, 0])
@property
- def box_aspect_ratio(self) -> npt.NDArray[np.generic]:
+ def box_aspect_ratio(self) -> npt.NDArray[np.floating]:
"""
Compute the aspect ratio (width divided by height) for each bounding box.
@@ -2412,10 +2802,13 @@ def box_aspect_ratio(self) -> npt.NDArray[np.generic]:
# array([[10., 10., 50., 50.]])
```
"""
- widths = self.xyxy[:, 2] - self.xyxy[:, 0]
- heights = self.xyxy[:, 3] - self.xyxy[:, 1]
+ xyxy = self.xyxy
+ widths = xyxy[:, 2] - xyxy[:, 0]
+ heights = xyxy[:, 3] - xyxy[:, 1]
- aspect_ratios = np.full_like(widths, np.nan, dtype=np.float64)
+ aspect_ratios: npt.NDArray[np.float64] = np.full_like(
+ widths, np.nan, dtype=np.float64
+ )
np.divide(widths, heights, out=aspect_ratios, where=heights != 0)
return aspect_ratios
@@ -2483,7 +2876,7 @@ def with_nms(
overlap_metric=overlap_metric,
)
- return cast(Detections, self[indices])
+ return self[indices]
def with_nmm(
self,
@@ -2551,7 +2944,7 @@ def with_nmm(
result: list[Detections] = []
for merge_group in merge_groups:
- unmerged_detections = [cast(Detections, self[i]) for i in merge_group]
+ unmerged_detections = [self[i] for i in merge_group]
merged_detections = merge_inner_detections_objects_without_iou(
unmerged_detections
)
@@ -2630,7 +3023,9 @@ def merge_inner_detection_object_pair(
if detections_1.mask is None and detections_2.mask is None:
merged_mask = None
else:
- merged_mask = np.logical_or(detections_1.mask, detections_2.mask)
+ merged_mask = np.logical_or(
+ np.asarray(detections_1.mask), np.asarray(detections_2.mask)
+ )
if detections_1.confidence is None or detections_2.confidence is None:
winning_detection = detections_1
@@ -2672,7 +3067,11 @@ def merge_inner_detections_objects(
0
]
else:
- iou = box_iou_batch(detections_1.xyxy, detections_2.xyxy, overlap_metric)[0]
+ iou = box_iou_batch(
+ detections_1.xyxy,
+ detections_2.xyxy,
+ overlap_metric,
+ )[0]
if iou < threshold:
break
detections_1 = merge_inner_detection_object_pair(detections_1, detections_2)
diff --git a/src/supervision/detection/line_zone.py b/src/supervision/detection/line_zone.py
index 3178fca24a..6d869521c4 100644
--- a/src/supervision/detection/line_zone.py
+++ b/src/supervision/detection/line_zone.py
@@ -5,7 +5,7 @@
from collections import Counter, defaultdict, deque
from collections.abc import Iterable
from functools import lru_cache
-from typing import Any, Literal, cast
+from typing import Literal
import cv2
import numpy as np
@@ -99,7 +99,7 @@ def __init__(
Position.BOTTOM_RIGHT,
),
minimum_crossing_threshold: int = 1,
- ):
+ ) -> None:
"""
Args:
start: The starting point of the line.
@@ -344,7 +344,7 @@ def __init__(
display_text_box: bool = True,
text_orient_to_line: bool = False,
text_centered: bool = True,
- ):
+ ) -> None:
"""
A class for drawing the `LineZone` and its detected object count
on an image.
@@ -673,29 +673,33 @@ def _make_label_image(
annotation_shape = (annotation_dim, annotation_dim)
annotation_center = Point(annotation_dim // 2, annotation_dim // 2)
- annotation = np.zeros((*annotation_shape, 3), dtype=np.uint8)
- annotation_alpha = np.zeros((*annotation_shape, 1), dtype=np.uint8)
-
- text_args: dict[str, Any] = dict(
+ annotation: npt.NDArray[np.uint8] = np.zeros(
+ (*annotation_shape, 3), dtype=np.uint8
+ )
+ annotation_alpha: npt.NDArray[np.uint8] = np.zeros(
+ (*annotation_shape, 1), dtype=np.uint8
+ )
+ draw_text(
+ scene=annotation,
text=text,
text_anchor=annotation_center,
text_scale=text_scale,
text_thickness=text_thickness,
text_padding=text_padding,
- )
- draw_text(
- scene=annotation,
text_color=text_color,
background_color=text_box_color if text_box_show else None,
- **text_args,
)
draw_text(
scene=annotation_alpha,
+ text=text,
+ text_anchor=annotation_center,
+ text_scale=text_scale,
+ text_thickness=text_thickness,
+ text_padding=text_padding,
text_color=Color.WHITE,
background_color=Color.WHITE if text_box_show else None,
- **text_args,
)
- annotation = np.dstack((annotation, annotation_alpha))
+ annotation = np.dstack((annotation, annotation_alpha)).astype(np.uint8)
# Make sure text is displayed upright
if 90 < line_angle_degrees % 360 < 270:
@@ -705,9 +709,11 @@ def _make_label_image(
rotation_matrix = cv2.getRotationMatrix2D(
annotation_center.as_xy_float_tuple(), rotation_angle, scale=1
)
- annotation = cv2.warpAffine(annotation, rotation_matrix, annotation_shape)
+ annotation = cv2.warpAffine(
+ annotation, rotation_matrix, annotation_shape
+ ).astype(np.uint8)
- return cast(npt.NDArray[np.uint8], annotation)
+ return annotation
class LineZoneAnnotatorMulticlass:
@@ -728,7 +734,7 @@ def __init__(
text_scale: float = 0.75,
text_thickness: int = 1,
force_draw_class_ids: bool = False,
- ):
+ ) -> None:
"""
Draw a table showing how many items of each class crossed each line.
diff --git a/src/supervision/detection/tools/inference_slicer.py b/src/supervision/detection/tools/inference_slicer.py
index c26a7584ab..897c7c85fa 100644
--- a/src/supervision/detection/tools/inference_slicer.py
+++ b/src/supervision/detection/tools/inference_slicer.py
@@ -4,10 +4,11 @@
import warnings
from collections.abc import Callable
from concurrent.futures import ThreadPoolExecutor, as_completed
-from typing import Any
+from typing import TypeAlias, cast
import numpy as np
import numpy.typing as npt
+from PIL import Image as PILImage
from supervision.config import ORIENTED_BOX_COORDINATES
from supervision.detection.compact_mask import CompactMask
@@ -19,10 +20,18 @@
from supervision.utils.image import crop_image, get_image_resolution_wh
from supervision.utils.internal import SupervisionWarnings
+# Concrete union for callback storage: the callback may receive either an
+# 8-bit ndarray or a PIL image. ``ImageType`` is a constrained ``TypeVar``
+# (see ``supervision.draw.base``) and cannot be stored on a non-generic class
+# as an attribute annotation — using the underlying union keeps the public
+# API contract intact while satisfying static type checking.
+_TypeSliceCallbackInput: TypeAlias = npt.NDArray[np.uint8] | PILImage.Image
+_TypeSliceCallback = Callable[[_TypeSliceCallbackInput], Detections]
+
def move_detections(
detections: Detections,
- offset: npt.NDArray[Any],
+ offset: npt.NDArray[np.int_],
resolution_wh: tuple[int, int] | None = None,
) -> Detections:
"""
@@ -36,10 +45,16 @@ def move_detections(
Returns:
Repositioned Detections object.
"""
- detections.xyxy = move_boxes(xyxy=detections.xyxy, offset=offset)
+ detections.xyxy = move_boxes(
+ xyxy=cast(npt.NDArray[np.float32], detections.xyxy), offset=offset
+ )
if ORIENTED_BOX_COORDINATES in detections.data:
+ oriented_boxes = cast(
+ npt.NDArray[np.float32],
+ detections.data[ORIENTED_BOX_COORDINATES],
+ )
detections.data[ORIENTED_BOX_COORDINATES] = move_oriented_boxes(
- xyxyxyxy=detections.data[ORIENTED_BOX_COORDINATES], offset=offset
+ xyxyxyxy=oriented_boxes, offset=offset
)
if detections.mask is not None:
if resolution_wh is None:
@@ -56,7 +71,9 @@ def move_detections(
)
else:
detections.mask = move_masks(
- masks=detections.mask, offset=offset, resolution_wh=resolution_wh
+ masks=detections.mask,
+ offset=offset,
+ resolution_wh=resolution_wh,
)
return detections
@@ -142,7 +159,7 @@ def callback(tile):
def __init__(
self,
- callback: Callable[[ImageType], Detections],
+ callback: _TypeSliceCallback,
slice_wh: int | tuple[int, int] = 640,
overlap_wh: int | tuple[int, int] = 100,
overlap_filter: OverlapFilter | str = OverlapFilter.NON_MAX_SUPPRESSION,
@@ -150,7 +167,7 @@ def __init__(
overlap_metric: OverlapMetric | str = OverlapMetric.IOU,
thread_workers: int = 1,
compact_masks: bool = False,
- ):
+ ) -> None:
slice_wh_norm = self._normalize_slice_wh(slice_wh)
overlap_wh_norm = self._normalize_overlap_wh(overlap_wh)
@@ -167,7 +184,7 @@ def __init__(
self.iou_threshold = iou_threshold
self.overlap_metric = OverlapMetric.from_value(overlap_metric)
self.overlap_filter = OverlapFilter.from_value(overlap_filter)
- self.callback: Callable[[ImageType], Detections] = callback
+ self.callback: _TypeSliceCallback = callback
self.thread_workers = thread_workers
self.compact_masks = compact_masks
self._out_of_slice_bounds_warned: bool = False
@@ -272,7 +289,9 @@ def __call__(self, image: ImageType) -> Detections:
)
return merged
- def _run_callback(self, image: ImageType, offset: npt.NDArray[Any]) -> Detections:
+ def _run_callback(
+ self, image: ImageType, offset: npt.NDArray[np.int_]
+ ) -> Detections:
"""
Run detection callback on a sliced portion of the image and adjust coordinates.
@@ -285,7 +304,10 @@ def _run_callback(self, image: ImageType, offset: npt.NDArray[Any]) -> Detection
Detections adjusted to the full image coordinate system.
"""
image_slice = crop_image(image=image, xyxy=offset)
- detections = self.callback(image_slice)
+ # ``image_slice`` matches the input ``ImageType`` (TypeVar); the callback
+ # is typed against the underlying union for storage, so a narrowing cast
+ # is required to bridge the two at the call site.
+ detections = self.callback(cast(_TypeSliceCallbackInput, image_slice))
if (
self.compact_masks
@@ -312,10 +334,11 @@ def _run_callback(self, image: ImageType, offset: npt.NDArray[Any]) -> Detection
if not self._out_of_slice_bounds_warned and len(detections) > 0:
slice_width = offset[2] - offset[0]
slice_height = offset[3] - offset[1]
- x_exceeds = np.any(detections.xyxy[:, [0, 2]] > slice_width)
- y_exceeds = np.any(detections.xyxy[:, [1, 3]] > slice_height)
- x_negative = np.any(detections.xyxy[:, [0, 2]] < 0)
- y_negative = np.any(detections.xyxy[:, [1, 3]] < 0)
+ xyxy_num = detections.xyxy
+ x_exceeds = np.any(xyxy_num[:, [0, 2]] > slice_width)
+ y_exceeds = np.any(xyxy_num[:, [1, 3]] > slice_height)
+ x_negative = np.any(xyxy_num[:, [0, 2]] < 0)
+ y_negative = np.any(xyxy_num[:, [1, 3]] < 0)
if x_exceeds or y_exceeds or x_negative or y_negative:
self._out_of_slice_bounds_warned = True
msg = (
@@ -390,7 +413,7 @@ def _generate_offset(
resolution_wh: tuple[int, int],
slice_wh: tuple[int, int],
overlap_wh: tuple[int, int],
- ) -> npt.NDArray[Any]:
+ ) -> npt.NDArray[np.int_]:
"""
Generate bounding boxes defining the coordinates of image slices with overlap.
@@ -442,7 +465,7 @@ def _compute_axis_starts(
x_max = np.clip(x_min + slice_width, 0, image_width)
y_max = np.clip(y_min + slice_height, 0, image_height)
- offsets: npt.NDArray[Any] = np.stack(
+ offsets: npt.NDArray[np.int_] = np.stack(
[x_min, y_min, x_max, y_max],
axis=-1,
).reshape(-1, 4)
diff --git a/src/supervision/detection/tools/polygon_zone.py b/src/supervision/detection/tools/polygon_zone.py
index 58e979f9b1..8556dfbc38 100644
--- a/src/supervision/detection/tools/polygon_zone.py
+++ b/src/supervision/detection/tools/polygon_zone.py
@@ -62,7 +62,7 @@ def __init__(
self,
polygon: npt.NDArray[np.int64],
triggering_anchors: Iterable[Position] = (Position.BOTTOM_CENTER,),
- ):
+ ) -> None:
self.polygon = polygon.astype(int)
self.triggering_anchors = triggering_anchors
if not list(self.triggering_anchors):
@@ -94,7 +94,7 @@ def trigger(self, detections: Detections) -> npt.NDArray[np.bool_]:
"""
if len(detections) == 0:
self.current_count = 0
- return np.array([], dtype=bool)
+ return cast(npt.NDArray[np.bool_], np.array([], dtype=bool))
all_anchors = np.array(
[
@@ -110,7 +110,7 @@ def trigger(self, detections: Detections) -> npt.NDArray[np.bool_]:
y_safe = np.clip(y, 0, mask_h - 1)
is_in_zone = np.all(in_bounds & self.mask[y_safe, x_safe], axis=0)
self.current_count = int(np.sum(is_in_zone))
- return is_in_zone.astype(bool)
+ return cast(npt.NDArray[np.bool_], is_in_zone.astype(bool))
class PolygonZoneAnnotator:
@@ -144,7 +144,7 @@ def __init__(
text_padding: int = 10,
display_in_zone_count: bool = True,
opacity: float = 0,
- ):
+ ) -> None:
self.zone = zone
self.color = color
self.thickness = thickness
diff --git a/src/supervision/detection/tools/smoother.py b/src/supervision/detection/tools/smoother.py
index 52282f2de6..18d04c2542 100644
--- a/src/supervision/detection/tools/smoother.py
+++ b/src/supervision/detection/tools/smoother.py
@@ -3,7 +3,6 @@
import warnings
from collections import defaultdict, deque
from copy import deepcopy
-from typing import cast
import numpy as np
@@ -115,7 +114,7 @@ def update_with_detections(self, detections: Detections) -> Detections:
tracker_id_value = detections.tracker_id[detection_idx]
tracker_id = int(tracker_id_value)
- self.tracks[tracker_id].append(cast(Detections, detections[detection_idx]))
+ self.tracks[tracker_id].append(detections[detection_idx])
for track_id in self.tracks.keys():
if track_id not in detections.tracker_id:
@@ -137,8 +136,16 @@ def get_track(self, track_id: int) -> Detections | None:
return None
ret = deepcopy(valid[0])
- ret.xyxy = np.mean([d.xyxy for d in valid], axis=0)
- ret.confidence = np.mean([d.confidence for d in valid], axis=0)
+ xyxy_stack = np.stack([d.xyxy for d in valid], axis=0)
+ ret.xyxy = xyxy_stack.mean(axis=0)
+
+ if all(d.confidence is not None for d in valid):
+ confidence_stack = np.stack(
+ [d.confidence for d in valid if d.confidence is not None], axis=0
+ )
+ ret.confidence = confidence_stack.mean(axis=0)
+ else:
+ ret.confidence = None
return ret
diff --git a/src/supervision/detection/utils/_typing.py b/src/supervision/detection/utils/_typing.py
new file mode 100644
index 0000000000..8f7a4103ce
--- /dev/null
+++ b/src/supervision/detection/utils/_typing.py
@@ -0,0 +1,10 @@
+from __future__ import annotations
+
+from typing import TypeAlias
+
+import numpy as np
+import numpy.typing as npt
+
+DetectionDataValueType: TypeAlias = npt.NDArray[np.generic] | list[object]
+DetectionDataType: TypeAlias = dict[str, DetectionDataValueType]
+MetadataType: TypeAlias = dict[str, object]
diff --git a/src/supervision/detection/utils/boxes.py b/src/supervision/detection/utils/boxes.py
index a19b05d445..eefab11eca 100644
--- a/src/supervision/detection/utils/boxes.py
+++ b/src/supervision/detection/utils/boxes.py
@@ -42,7 +42,7 @@ def clip_boxes(
```
"""
- result: npt.NDArray[np.number] = np.copy(xyxy)
+ result = np.copy(xyxy)
width, height = resolution_wh
result[:, [0, 2]] = result[:, [0, 2]].clip(0, width)
result[:, [1, 3]] = result[:, [1, 3]].clip(0, height)
@@ -90,8 +90,8 @@ def pad_boxes(
py = px
result = xyxy.copy()
- result[:, [0, 1]] -= [px, py]
- result[:, [2, 3]] += [px, py]
+ result[:, :2] = result[:, :2] - [px, py]
+ result[:, 2:] = result[:, 2:] + [px, py]
return result
@@ -125,7 +125,8 @@ def denormalize_boxes(
Returns:
Array of shape `(N, 4)` with absolute coordinates in
- `(x_min, y_min, x_max, y_max)` format.
+ `(x_min, y_min, x_max, y_max)` format. Preserves the caller's
+ floating-point dtype; integer inputs are upcast to float64.
Examples:
```pycon
@@ -153,7 +154,7 @@ def denormalize_boxes(
```
"""
width, height = resolution_wh
- result = xyxy.copy()
+ result: npt.NDArray[np.number] = np.array(xyxy, copy=True)
result[:, [0, 2]] = (result[:, [0, 2]] * width) / normalization_factor
result[:, [1, 3]] = (result[:, [1, 3]] * height) / normalization_factor
@@ -162,8 +163,8 @@ def denormalize_boxes(
def move_boxes(
- xyxy: npt.NDArray[np.float64], offset: npt.NDArray[np.int32]
-) -> npt.NDArray[np.float64]:
+ xyxy: npt.NDArray[np.number], offset: npt.NDArray[np.integer]
+) -> npt.NDArray[np.number]:
"""
Args:
xyxy: An array of shape `(n, 4)` containing the
@@ -193,8 +194,8 @@ def move_boxes(
def move_oriented_boxes(
- xyxyxyxy: npt.NDArray[np.float64], offset: npt.NDArray[np.int32]
-) -> npt.NDArray[np.float64]:
+ xyxyxyxy: npt.NDArray[np.number], offset: npt.NDArray[np.integer]
+) -> npt.NDArray[np.number]:
"""
Args:
xyxyxyxy: An array of shape `(n, 4, 2)` containing the
@@ -241,9 +242,7 @@ def move_oriented_boxes(
return xyxyxyxy + offset
-def scale_boxes(
- xyxy: npt.NDArray[np.float64], factor: float
-) -> npt.NDArray[np.float64]:
+def scale_boxes(xyxy: npt.NDArray[np.number], factor: float) -> npt.NDArray[np.number]:
"""
Scale the dimensions of bounding boxes.
@@ -322,7 +321,7 @@ def spread_out_boxes(
# NxNx2
delta_centers = centers[:, np.newaxis, :] - centers[np.newaxis, :, :]
- delta_centers *= overlap_mask[:, :, np.newaxis]
+ delta_centers = delta_centers * overlap_mask[:, :, np.newaxis]
# Nx2
delta_sum = np.sum(delta_centers, axis=1)
@@ -337,13 +336,13 @@ def spread_out_boxes(
force_vectors = np.sum(iou, axis=1)
force_vectors = force_vectors[:, np.newaxis] * direction_vectors
- force_vectors *= 10
+ force_vectors = force_vectors * 10
force_vectors[(force_vectors > 0) & (force_vectors < 2)] = 2
force_vectors[(force_vectors < 0) & (force_vectors > -2)] = -2
force_vectors = force_vectors.astype(int)
- xyxy_padded[:, [0, 1]] += force_vectors
- xyxy_padded[:, [2, 3]] += force_vectors
+ xyxy_padded[:, :2] = xyxy_padded[:, :2] + force_vectors
+ xyxy_padded[:, 2:] = xyxy_padded[:, 2:] + force_vectors
return pad_boxes(xyxy_padded, px=-1)
diff --git a/src/supervision/detection/utils/converters.py b/src/supervision/detection/utils/converters.py
index 9626daf6dd..b40c9991a9 100644
--- a/src/supervision/detection/utils/converters.py
+++ b/src/supervision/detection/utils/converters.py
@@ -79,7 +79,7 @@ def xywh_to_xyxy(xywh: npt.NDArray[np.number]) -> npt.NDArray[np.number]:
xyxy = xywh.copy()
xyxy[:, 2] = xywh[:, 0] + xywh[:, 2]
xyxy[:, 3] = xywh[:, 1] + xywh[:, 3]
- return xyxy
+ return cast(npt.NDArray[np.number], np.asarray(xyxy))
def xyxy_to_xywh(xyxy: npt.NDArray[np.number]) -> npt.NDArray[np.number]:
@@ -113,7 +113,7 @@ def xyxy_to_xywh(xyxy: npt.NDArray[np.number]) -> npt.NDArray[np.number]:
xywh = xyxy.copy()
xywh[:, 2] = xyxy[:, 2] - xyxy[:, 0]
xywh[:, 3] = xyxy[:, 3] - xyxy[:, 1]
- return xywh
+ return cast(npt.NDArray[np.number], np.asarray(xywh))
def xcycwh_to_xyxy(xcycwh: npt.NDArray[np.number]) -> npt.NDArray[np.number]:
@@ -149,7 +149,7 @@ def xcycwh_to_xyxy(xcycwh: npt.NDArray[np.number]) -> npt.NDArray[np.number]:
xyxy[:, 1] = xcycwh[:, 1] - xcycwh[:, 3] / 2
xyxy[:, 2] = xcycwh[:, 0] + xcycwh[:, 2] / 2
xyxy[:, 3] = xcycwh[:, 1] + xcycwh[:, 3] / 2
- return xyxy
+ return cast(npt.NDArray[np.number], np.asarray(xyxy))
def xyxy_to_xcycarh(xyxy: npt.NDArray[np.number]) -> npt.NDArray[np.floating]:
diff --git a/src/supervision/detection/utils/internal.py b/src/supervision/detection/utils/internal.py
index 1d0d3371fa..7e684a1c09 100644
--- a/src/supervision/detection/utils/internal.py
+++ b/src/supervision/detection/utils/internal.py
@@ -2,27 +2,92 @@
import logging
from itertools import chain
-from typing import Any, cast
+from typing import Protocol, TypedDict, cast
import cv2
import numpy as np
import numpy.typing as npt
from supervision.config import CLASS_NAME_DATA_FIELD
+from supervision.detection.utils._typing import (
+ DetectionDataType,
+ DetectionDataValueType,
+ MetadataType,
+)
from supervision.detection.utils.converters import polygon_to_mask, rle_to_mask
from supervision.geometry.core import Vector
logger = logging.getLogger(__name__)
-def extract_ultralytics_masks(yolov8_results: Any) -> npt.NDArray[np.bool_] | None:
- if not yolov8_results.masks:
+class _TypeUltralyticsMaskData(Protocol):
+ shape: tuple[int, ...]
+
+ def cpu(self) -> _TypeUltralyticsMaskData:
+ """Return the mask data on CPU."""
+
+ def numpy(self) -> npt.NDArray[np.generic]:
+ """Convert the mask data to a NumPy array."""
+
+
+class _TypeUltralyticsMasks(Protocol):
+ data: _TypeUltralyticsMaskData
+
+
+class _TypeUltralyticsResult(Protocol):
+ masks: _TypeUltralyticsMasks | None
+ orig_shape: tuple[int, int]
+
+
+class _TypeRoboflowImage(TypedDict):
+ width: int
+ height: int
+
+
+class _TypeRoboflowPoint(TypedDict):
+ x: float
+ y: float
+
+
+class _TypeRoboflowRLE(TypedDict):
+ size: tuple[int, int] | list[int]
+ counts: npt.NDArray[np.integer] | list[int] | str | bytes
+
+
+_TypeRoboflowPrediction = TypedDict(
+ "_TypeRoboflowPrediction",
+ {
+ "x": float,
+ "y": float,
+ "width": float,
+ "height": float,
+ "class_id": int,
+ "class": str,
+ "confidence": float,
+ "tracker_id": int,
+ "rle": _TypeRoboflowRLE,
+ "rle_mask": _TypeRoboflowRLE,
+ "points": list[_TypeRoboflowPoint],
+ },
+ total=False,
+)
+
+
+class _TypeRoboflowResult(TypedDict):
+ predictions: list[_TypeRoboflowPrediction]
+ image: _TypeRoboflowImage
+
+
+def extract_ultralytics_masks(
+ yolov8_results: _TypeUltralyticsResult,
+) -> npt.NDArray[np.bool_] | None:
+ if yolov8_results.masks is None:
return None
orig_shape = yolov8_results.orig_shape
inference_shape = tuple(yolov8_results.masks.data.shape[1:])
- pad = (0, 0)
+ pad: tuple[float, float] = (0.0, 0.0)
if inference_shape != orig_shape:
gain = min(
@@ -52,14 +117,14 @@ def extract_ultralytics_masks(yolov8_results: Any) -> npt.NDArray[np.bool_] | No
def process_roboflow_result(
- roboflow_result: dict[str, Any],
+ roboflow_result: _TypeRoboflowResult,
) -> tuple[
- npt.NDArray[np.floating],
- npt.NDArray[np.floating],
- npt.NDArray[np.integer],
+ npt.NDArray[np.float64],
+ npt.NDArray[np.float64],
+ npt.NDArray[np.int64],
npt.NDArray[np.bool_] | None,
- npt.NDArray[np.integer] | None,
- dict[str, npt.NDArray[np.generic]],
+ npt.NDArray[np.int64] | None,
+ DetectionDataType,
]:
if not roboflow_result["predictions"]:
return (
@@ -133,36 +198,47 @@ def process_roboflow_result(
polygon = np.array(
[[point["x"], point["y"]] for point in prediction["points"]], dtype=int
)
- mask = polygon_to_mask(polygon, resolution_wh=(image_width, image_height))
+ polygon_mask = cast(
+ npt.NDArray[np.bool_],
+ polygon_to_mask(
+ polygon, resolution_wh=(image_width, image_height)
+ ).astype(bool),
+ )
xyxy.append([x_min, y_min, x_max, y_max])
class_id.append(prediction["class_id"])
class_name.append(prediction["class"])
confidence.append(prediction["confidence"])
- masks.append(mask.astype(bool))
+ masks.append(polygon_mask)
if "tracker_id" in prediction:
tracker_ids.append(prediction["tracker_id"])
- xyxy_arr: npt.NDArray[np.floating] = (
- np.array(xyxy, dtype=np.float64) if len(xyxy) > 0 else np.empty((0, 4))
+ xyxy_arr: npt.NDArray[np.float64] = (
+ np.array(xyxy, dtype=np.float64)
+ if len(xyxy) > 0
+ else np.empty((0, 4), dtype=np.float64)
)
- confidence_arr: npt.NDArray[np.floating] = (
- np.array(confidence, dtype=np.float64) if len(confidence) > 0 else np.empty(0)
+ confidence_arr: npt.NDArray[np.float64] = (
+ np.array(confidence, dtype=np.float64)
+ if len(confidence) > 0
+ else np.empty(0, dtype=np.float64)
)
- class_id_arr: npt.NDArray[np.integer] = (
+ class_id_arr: npt.NDArray[np.int64] = (
np.array(class_id, dtype=np.int64)
if len(class_id) > 0
else np.empty(0, dtype=np.int64)
)
class_name_arr: npt.NDArray[np.str_] = (
- np.array(class_name) if len(class_name) > 0 else np.empty(0, dtype=str)
+ np.array(class_name)
+ if len(class_name) > 0
+ else cast(npt.NDArray[np.str_], np.empty(0, dtype=str))
)
masks_arr: npt.NDArray[np.bool_] | None = (
- np.array(masks, dtype=bool) if len(masks) > 0 else None
+ cast(npt.NDArray[np.bool_], np.stack(masks, axis=0)) if len(masks) > 0 else None
)
- tracker_id_arr: npt.NDArray[np.integer] | None = (
+ tracker_id_arr: npt.NDArray[np.int64] | None = (
np.array(tracker_ids, dtype=np.int64) if len(tracker_ids) > 0 else None
)
- data: dict[str, npt.NDArray[np.generic]] = {CLASS_NAME_DATA_FIELD: class_name_arr}
+ data: DetectionDataType = {CLASS_NAME_DATA_FIELD: class_name_arr}
return (
xyxy_arr,
@@ -175,8 +251,8 @@ def process_roboflow_result(
def is_data_equal(
- data_a: dict[str, npt.NDArray[np.generic]],
- data_b: dict[str, npt.NDArray[np.generic]],
+ data_a: DetectionDataType,
+ data_b: DetectionDataType,
) -> bool:
"""
Compares the data payloads of two Detections instances.
@@ -187,12 +263,25 @@ def is_data_equal(
Returns:
True if the data payloads are equal, False otherwise.
"""
- return set(data_a.keys()) == set(data_b.keys()) and all(
- np.array_equal(data_a[key], data_b[key]) for key in data_a
- )
+ if set(data_a.keys()) != set(data_b.keys()):
+ return False
+
+ for key in data_a:
+ value_a = data_a[key]
+ value_b = data_b[key]
+
+ if isinstance(value_a, np.ndarray) or isinstance(value_b, np.ndarray):
+ array_a = cast(npt.NDArray[np.generic], np.asarray(value_a))
+ array_b = cast(npt.NDArray[np.generic], np.asarray(value_b))
+ if not np.array_equal(array_a, array_b):
+ return False
+ elif value_a != value_b:
+ return False
+
+ return True
-def is_metadata_equal(metadata_a: dict[str, Any], metadata_b: dict[str, Any]) -> bool:
+def is_metadata_equal(metadata_a: MetadataType, metadata_b: MetadataType) -> bool:
"""
Compares the metadata payloads of two Detections instances.
@@ -202,20 +291,25 @@ def is_metadata_equal(metadata_a: dict[str, Any], metadata_b: dict[str, Any]) ->
Returns:
True if the metadata payloads are equal, False otherwise.
"""
- return set(metadata_a.keys()) == set(metadata_b.keys()) and all(
- np.array_equal(metadata_a[key], metadata_b[key])
- if (
- isinstance(metadata_a[key], np.ndarray)
- and isinstance(metadata_b[key], np.ndarray)
- )
- else metadata_a[key] == metadata_b[key]
- for key in metadata_a
- )
+ if set(metadata_a.keys()) != set(metadata_b.keys()):
+ return False
+
+ for key in metadata_a:
+ value_a = metadata_a[key]
+ value_b = metadata_b[key]
+
+ if isinstance(value_a, np.ndarray) or isinstance(value_b, np.ndarray):
+ array_a = cast(npt.NDArray[np.generic], np.asarray(value_a))
+ array_b = cast(npt.NDArray[np.generic], np.asarray(value_b))
+ if not np.array_equal(array_a, array_b):
+ return False
+ elif value_a != value_b:
+ return False
+ return True
-def merge_data(
- data_list: list[dict[str, npt.NDArray[np.generic] | list[Any]]],
-) -> dict[str, npt.NDArray[np.generic] | list[Any]]:
+
+def merge_data(data_list: list[DetectionDataType]) -> DetectionDataType:
"""
Merges the data payloads of a list of Detections instances.
@@ -249,20 +343,25 @@ def merge_data(
"All data values within a single object must have equal length."
)
- merged_data: dict[str, list[Any]] = {key: [] for key in all_keys_sets[0]}
+ merged_data_values: dict[str, list[DetectionDataValueType]] = {
+ key: [] for key in all_keys_sets[0]
+ }
for data in data_list:
- for key in data:
- merged_data[key].append(data[key])
-
- for key in merged_data:
- if all(isinstance(item, list) for item in merged_data[key]):
- merged_data[key] = list(chain.from_iterable(merged_data[key]))
- elif all(isinstance(item, np.ndarray) for item in merged_data[key]):
- ndim = merged_data[key][0].ndim
+ for key, value in data.items():
+ merged_data_values[key].append(value)
+
+ merged_data: DetectionDataType = {}
+ for key, values in merged_data_values.items():
+ if all(isinstance(item, list) for item in values):
+ list_values = cast(list[list[object]], values)
+ merged_data[key] = list(chain.from_iterable(list_values))
+ elif all(isinstance(item, np.ndarray) for item in values):
+ array_values = cast(list[npt.NDArray[np.generic]], values)
+ ndim = array_values[0].ndim
if ndim == 1:
- merged_data[key] = np.hstack(merged_data[key])
+ merged_data[key] = np.hstack(array_values)
elif ndim > 1:
- merged_data[key] = np.vstack(merged_data[key])
+ merged_data[key] = np.vstack(array_values)
else:
raise ValueError(f"Unexpected array dimension for key '{key}'.")
else:
@@ -274,7 +373,7 @@ def merge_data(
return merged_data
-def merge_metadata(metadata_list: list[dict[str, Any]]) -> dict[str, Any]:
+def merge_metadata(metadata_list: list[MetadataType]) -> MetadataType:
"""
Merge metadata from a list of metadata dictionaries.
@@ -301,7 +400,7 @@ def merge_metadata(metadata_list: list[dict[str, Any]]) -> dict[str, Any]:
if not all(keys_set == all_keys_sets[0] for keys_set in all_keys_sets):
raise ValueError("All metadata dictionaries must have the same keys to merge.")
- merged_metadata: dict[str, Any] = {}
+ merged_metadata: MetadataType = {}
for metadata in metadata_list:
for key, value in metadata.items():
if key not in merged_metadata:
@@ -310,7 +409,9 @@ def merge_metadata(metadata_list: list[dict[str, Any]]) -> dict[str, Any]:
other_value = merged_metadata[key]
if isinstance(value, np.ndarray) and isinstance(other_value, np.ndarray):
- if not np.array_equal(merged_metadata[key], value):
+ array_a = cast(npt.NDArray[np.generic], merged_metadata[key])
+ array_b = cast(npt.NDArray[np.generic], value)
+ if not np.array_equal(array_a, array_b):
raise ValueError(
f"Conflicting metadata for key: '{key}': "
"{type(value)}, {type(other_value)}."
@@ -329,9 +430,14 @@ def merge_metadata(metadata_list: list[dict[str, Any]]) -> dict[str, Any]:
def get_data_item(
- data: dict[str, npt.NDArray[np.generic] | list[Any]],
- index: int | slice | list[int] | npt.NDArray[np.integer | np.bool_],
-) -> dict[str, npt.NDArray[np.generic] | list[Any]]:
+ data: DetectionDataType,
+ index: int
+ | slice
+ | list[int]
+ | list[bool]
+ | npt.NDArray[np.int_]
+ | npt.NDArray[np.bool_],
+) -> DetectionDataType:
"""
Retrieve a subset of the data dictionary based on the given index.
@@ -342,12 +448,14 @@ def get_data_item(
Returns:
A subset of the data dictionary corresponding to the specified index.
"""
- subset_data: dict[str, npt.NDArray[np.generic] | list[Any]] = {}
+ subset_data: DetectionDataType = {}
for key, value in data.items():
if isinstance(value, np.ndarray):
subset_data[key] = value[index]
elif isinstance(value, list):
- if isinstance(index, slice):
+ if isinstance(index, int):
+ subset_data[key] = [value[index]]
+ elif isinstance(index, slice):
subset_data[key] = value[index]
elif isinstance(index, list):
subset_data[key] = [value[i] for i in index]
@@ -358,8 +466,6 @@ def get_data_item(
]
else:
subset_data[key] = [value[i] for i in index]
- elif isinstance(index, int):
- subset_data[key] = [value[index]]
else:
raise TypeError(f"Unsupported index type: {type(index)}")
else:
diff --git a/src/supervision/detection/utils/iou_and_nms.py b/src/supervision/detection/utils/iou_and_nms.py
index c2aa230f06..ea8b52b519 100644
--- a/src/supervision/detection/utils/iou_and_nms.py
+++ b/src/supervision/detection/utils/iou_and_nms.py
@@ -1,7 +1,7 @@
from __future__ import annotations
from enum import Enum
-from typing import Any, cast
+from typing import cast
import numpy as np
import numpy.typing as npt
@@ -359,7 +359,8 @@ def box_iou_batch_with_jaccard(
def oriented_box_iou_batch(
- boxes_true: npt.NDArray[np.number], boxes_detection: npt.NDArray[np.number]
+ boxes_true: npt.NDArray[np.number],
+ boxes_detection: npt.NDArray[np.number],
) -> npt.NDArray[np.floating]:
"""
Compute Intersection over Union (IoU) of two sets of oriented bounding boxes -
@@ -385,27 +386,29 @@ def oriented_box_iou_batch(
# adding 1 because we are 0-indexed
max_width = int(max(boxes_true[:, :, 1].max(), boxes_detection[:, :, 1].max()) + 1)
- mask_true = np.zeros((boxes_true.shape[0], max_height, max_width), dtype=np.uint8)
+ mask_true = np.zeros((boxes_true.shape[0], max_height, max_width), dtype=bool)
for box_idx, box_true in enumerate(boxes_true):
- mask_true[box_idx] = polygon_to_mask(box_true, (max_width, max_height))
+ mask_true[box_idx] = polygon_to_mask(box_true, (max_width, max_height)).astype(
+ bool
+ )
mask_detection = np.zeros(
- (boxes_detection.shape[0], max_height, max_width), dtype=np.uint8
+ (boxes_detection.shape[0], max_height, max_width), dtype=bool
)
for box_idx, box_detection in enumerate(boxes_detection):
mask_detection[box_idx] = polygon_to_mask(
box_detection, (max_width, max_height)
- )
+ ).astype(bool)
ious = mask_iou_batch(mask_true, mask_detection)
return ious
def compact_mask_iou_batch(
- masks_true: Any,
- masks_detection: Any,
+ masks_true: CompactMask,
+ masks_detection: CompactMask,
overlap_metric: OverlapMetric = OverlapMetric.IOU,
-) -> npt.NDArray[np.floating]:
+) -> npt.NDArray[np.float64]:
"""Compute pairwise overlap between two :class:`CompactMask` collections.
Avoids materialising full ``(N, H, W)`` arrays by:
@@ -433,7 +436,7 @@ def compact_mask_iou_batch(
"""
n1: int = len(masks_true)
n2: int = len(masks_detection)
- result: npt.NDArray[np.floating] = np.zeros((n1, n2), dtype=float)
+ result: npt.NDArray[np.float64] = np.zeros((n1, n2), dtype=float)
if n1 == 0 or n2 == 0:
return result
@@ -505,10 +508,10 @@ def compact_mask_iou_batch(
def _mask_iou_batch_split(
- masks_true: npt.NDArray[Any],
- masks_detection: npt.NDArray[Any],
+ masks_true: npt.NDArray[np.bool_],
+ masks_detection: npt.NDArray[np.bool_],
overlap_metric: OverlapMetric = OverlapMetric.IOU,
-) -> npt.NDArray[np.floating]:
+) -> npt.NDArray[np.float64]:
"""
Internal function.
Compute Intersection over Union (IoU) of two sets of masks -
@@ -554,15 +557,15 @@ def _mask_iou_batch_split(
)
ious = np.nan_to_num(ious)
- return cast(npt.NDArray[np.floating], ious)
+ return cast(npt.NDArray[np.float64], ious)
def mask_iou_batch(
- masks_true: npt.NDArray[Any],
- masks_detection: npt.NDArray[Any],
+ masks_true: npt.NDArray[np.bool_] | CompactMask,
+ masks_detection: npt.NDArray[np.bool_] | CompactMask,
overlap_metric: OverlapMetric = OverlapMetric.IOU,
memory_limit: int = 1024 * 5,
-) -> npt.NDArray[np.floating]:
+) -> npt.NDArray[np.float64]:
"""
Compute Intersection over Union (IoU) of two sets of masks -
`masks_true` and `masks_detection`.
@@ -626,12 +629,12 @@ def mask_iou_batch(
)
)
- return cast(npt.NDArray[np.floating], np.vstack(ious))
+ return cast(npt.NDArray[np.float64], np.vstack(ious))
def mask_non_max_suppression(
predictions: npt.NDArray[np.floating],
- masks: npt.NDArray[Any],
+ masks: npt.NDArray[np.bool_] | CompactMask,
iou_threshold: float = 0.5,
overlap_metric: OverlapMetric = OverlapMetric.IOU,
mask_dimension: int = 640,
@@ -738,7 +741,7 @@ def box_non_max_suppression(
boxes = predictions[:, :4]
categories = predictions[:, 5]
ious = box_iou_batch(boxes, boxes, overlap_metric)
- ious = ious - np.eye(rows)
+ ious = ious - np.eye(rows, dtype=ious.dtype)
keep = np.ones(rows, dtype=bool)
@@ -755,8 +758,8 @@ def box_non_max_suppression(
def _group_overlapping_masks(
- predictions: npt.NDArray[np.float64],
- masks: npt.NDArray[np.float64],
+ predictions: npt.NDArray[np.floating],
+ masks: npt.NDArray[np.bool_],
iou_threshold: float = 0.5,
overlap_metric: OverlapMetric = OverlapMetric.IOU,
) -> list[list[int]]:
@@ -813,7 +816,7 @@ def _group_overlapping_masks(
def mask_non_max_merge(
predictions: npt.NDArray[np.floating],
- masks: npt.NDArray[Any],
+ masks: npt.NDArray[np.bool_] | CompactMask,
iou_threshold: float = 0.5,
mask_dimension: int = 640,
overlap_metric: OverlapMetric = OverlapMetric.IOU,
diff --git a/src/supervision/detection/utils/masks.py b/src/supervision/detection/utils/masks.py
index e8dd43227d..f6dc42e1cf 100644
--- a/src/supervision/detection/utils/masks.py
+++ b/src/supervision/detection/utils/masks.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from typing import Any, Literal, cast
+from typing import Literal, cast
import cv2
import numpy as np
@@ -11,7 +11,7 @@
def move_masks(
masks: npt.NDArray[np.bool_],
- offset: npt.NDArray[np.int32],
+ offset: npt.NDArray[np.integer],
resolution_wh: tuple[int, int],
) -> npt.NDArray[np.bool_]:
"""
@@ -88,7 +88,7 @@ def move_masks(
def calculate_masks_centroids(
- masks: npt.NDArray[Any] | CompactMask,
+ masks: npt.NDArray[np.bool_] | CompactMask,
) -> npt.NDArray[np.int_]:
"""
Calculate the centroids of binary masks in a tensor.
@@ -260,7 +260,9 @@ def contains_multiple_segments(
return bool(number_of_labels > 2)
-def resize_masks(masks: npt.NDArray[Any], max_dimension: int = 640) -> npt.NDArray[Any]:
+def resize_masks(
+ masks: npt.NDArray[np.bool_], max_dimension: int = 640
+) -> npt.NDArray[np.bool_]:
"""
Resize all masks in the array to have a maximum dimension of max_dimension,
maintaining aspect ratio.
@@ -376,14 +378,12 @@ def filter_segments_by_distance(
if not np.any(mask):
return mask.copy()
- image = mask.astype(np.uint8)
- num_labels: int
- labels: npt.NDArray[np.int32]
- stats: npt.NDArray[np.int32]
- centroids: npt.NDArray[np.float64]
- num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(
- image, connectivity=connectivity
- )
+ image = cast(npt.NDArray[np.uint8], mask.astype(np.uint8))
+ components = cv2.connectedComponentsWithStats(image, connectivity=connectivity)
+ num_labels = int(components[0])
+ labels = cast(npt.NDArray[np.int32], components[1])
+ stats = cast(npt.NDArray[np.int32], components[2])
+ centroids = cast(npt.NDArray[np.float64], components[3])
if num_labels <= 1:
return mask.copy()
diff --git a/src/supervision/detection/utils/polygons.py b/src/supervision/detection/utils/polygons.py
index 041e43f614..6d2ccf151e 100644
--- a/src/supervision/detection/utils/polygons.py
+++ b/src/supervision/detection/utils/polygons.py
@@ -34,7 +34,9 @@ def filter_polygons_by_area(
"""
if min_area is None and max_area is None:
return polygons
- ares = [cv2.contourArea(polygon) for polygon in polygons]
+ ares = [
+ cv2.contourArea(np.asarray(polygon, dtype=np.float32)) for polygon in polygons
+ ]
return [
polygon
for polygon, area in zip(polygons, ares)
@@ -71,16 +73,20 @@ def approximate_polygon(
if percentage < 0 or percentage >= 1:
raise ValueError("Percentage must be in the range [0, 1).")
- target_points = max(int(len(polygon) * (1 - percentage)), 3)
+ polygon_f32 = np.asarray(polygon, dtype=np.float32)
+ target_points = max(int(len(polygon_f32) * (1 - percentage)), 3)
- if len(polygon) <= target_points:
- return polygon
+ if len(polygon_f32) <= target_points:
+ return cast(npt.NDArray[np.number], polygon_f32)
epsilon: float = 0
- approximated_points = polygon
+ approximated_points: npt.NDArray[np.float32] = polygon_f32
while True:
epsilon += epsilon_step
- new_approximated_points = cv2.approxPolyDP(polygon, epsilon, closed=True)
+ new_approximated_points = cast(
+ npt.NDArray[np.float32],
+ cv2.approxPolyDP(polygon_f32, epsilon, closed=True),
+ )
if len(new_approximated_points) > target_points:
approximated_points = new_approximated_points
else:
diff --git a/src/supervision/detection/vlm.py b/src/supervision/detection/vlm.py
index de53548d04..6bc055ad9c 100644
--- a/src/supervision/detection/vlm.py
+++ b/src/supervision/detection/vlm.py
@@ -6,7 +6,7 @@
import json
import re
from enum import Enum
-from typing import Any, cast
+from typing import cast
import numpy as np
import numpy.typing as npt
@@ -159,7 +159,9 @@ def from_value(cls, value: VLM | str) -> VLM:
]
-def validate_vlm_parameters(vlm: VLM | str, result: Any, kwargs: dict[str, Any]) -> VLM:
+def validate_vlm_parameters(
+ vlm: VLM | str, result: object, kwargs: dict[str, object]
+) -> VLM:
"""
Validates the parameters and result type for a given Vision-Language Model (VLM).
@@ -202,7 +204,7 @@ def validate_vlm_parameters(vlm: VLM | str, result: Any, kwargs: dict[str, Any])
def from_paligemma(
result: str, resolution_wh: tuple[int, int], classes: list[str] | None = None
-) -> tuple[npt.NDArray[Any], npt.NDArray[Any], npt.NDArray[Any]]:
+) -> tuple[npt.NDArray[np.float32], npt.NDArray[np.int64] | None, npt.NDArray[np.str_]]:
"""
Parse bounding boxes from paligemma-formatted text, scale them to the specified
resolution, and optionally filter by classes.
@@ -226,26 +228,43 @@ def from_paligemma(
r"(?) ([\w\s\-]+)"
)
matches = pattern.findall(result)
- matches = np.array(matches) if matches else np.empty((0, 5))
+ matches_arr = (
+ np.array(matches, dtype=object) if matches else np.empty((0, 5), dtype=object)
+ )
- if matches.shape[0] == 0:
- return np.empty((0, 4)), np.empty((0,), dtype=int), np.empty(0, dtype=str)
+ if matches_arr.shape[0] == 0:
+ return (
+ np.empty((0, 4), dtype=np.float32),
+ np.empty((0,), dtype=np.int64),
+ np.empty(0, dtype=str),
+ )
- xyxy, class_name = matches[:, [1, 0, 3, 2]], matches[:, 4]
- xyxy = xyxy.astype(int) / 1024 * np.array([w, h, w, h])
- class_name = np.char.strip(class_name.astype(str))
- class_id = None
+ xyxy_arr: npt.NDArray[np.float32] = np.asarray(
+ matches_arr[:, [1, 0, 3, 2]].astype(np.float32)
+ / np.float32(1024)
+ * np.array([w, h, w, h], dtype=np.float32),
+ dtype=np.float32,
+ )
+ class_name_arr: npt.NDArray[np.str_] = np.asarray(
+ np.char.strip(matches_arr[:, 4].astype(str)),
+ dtype=str,
+ )
+ class_id_arr: npt.NDArray[np.int64] | None = None
if classes is not None:
- mask = np.array([name in classes for name in class_name], dtype=bool)
- xyxy = xyxy[mask]
- class_name = class_name[mask]
- class_id = np.array([classes.index(name) for name in class_name])
+ mask_arr = np.array([name in classes for name in class_name_arr], dtype=bool)
+ xyxy_arr = xyxy_arr[mask_arr]
+ class_name_arr = class_name_arr[mask_arr]
+ class_id_arr = np.array(
+ [classes.index(name) for name in class_name_arr], dtype=np.int64
+ )
- return xyxy, class_id, class_name
+ return xyxy_arr, class_id_arr, class_name_arr
-def recover_truncated_qwen_2_5_vl_response(text: str) -> Any | None:
+def recover_truncated_qwen_2_5_vl_response(
+ text: str,
+) -> list[dict[str, object]] | None:
"""
Attempt to recover and parse a truncated or malformed JSON snippet from Qwen-2.5-VL
output.
@@ -285,7 +304,10 @@ def recover_truncated_qwen_2_5_vl_response(text: str) -> Any | None:
repaired = prefix + body + "]"
- return json.loads(repaired)
+ recovered = json.loads(repaired)
+ if isinstance(recovered, list):
+ return cast(list[dict[str, object]], recovered)
+ return None
except Exception:
return None
@@ -295,7 +317,7 @@ def from_qwen_2_5_vl(
input_wh: tuple[int, int],
resolution_wh: tuple[int, int],
classes: list[str] | None = None,
-) -> tuple[npt.NDArray[Any], npt.NDArray[Any] | None, npt.NDArray[Any]]:
+) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.int64] | None, npt.NDArray[np.str_]]:
"""
Parse and rescale bounding boxes and class labels from Qwen-2.5-VL JSON output.
@@ -334,6 +356,7 @@ def from_qwen_2_5_vl(
if start != -1 and end != -1 and end > start:
text = text[start : end + 1].strip()
+ data: object
try:
data = json.loads(text)
except json.JSONDecodeError:
@@ -346,24 +369,33 @@ def from_qwen_2_5_vl(
except (ValueError, SyntaxError, TypeError):
return (
np.empty((0, 4)),
- np.empty((0,), dtype=int),
+ np.empty((0,), dtype=np.int64),
np.empty((0,), dtype=str),
)
if not isinstance(data, list):
- return (np.empty((0, 4)), np.empty((0,), dtype=int), np.empty((0,), dtype=str))
+ return (
+ np.empty((0, 4)),
+ np.empty((0,), dtype=np.int64),
+ np.empty((0,), dtype=str),
+ )
- boxes_list = []
- labels_list = []
+ data = cast(list[dict[str, object]], data)
+ boxes_list: list[list[float]] = []
+ labels_list: list[str] = []
for item in data:
if "bbox_2d" not in item or "label" not in item:
continue
- boxes_list.append(item["bbox_2d"])
- labels_list.append(item["label"])
+ boxes_list.append(cast(list[float], item["bbox_2d"]))
+ labels_list.append(cast(str, item["label"]))
if not boxes_list:
- return (np.empty((0, 4)), np.empty((0,), dtype=int), np.empty((0,), dtype=str))
+ return (
+ np.empty((0, 4)),
+ np.empty((0,), dtype=np.int64),
+ np.empty((0,), dtype=str),
+ )
xyxy = np.array(boxes_list, dtype=float)
class_name = np.array(labels_list, dtype=str)
@@ -377,7 +409,9 @@ def from_qwen_2_5_vl(
mask = np.array([label in classes for label in class_name], dtype=bool)
xyxy = xyxy[mask]
class_name = class_name[mask]
- class_id = np.array([classes.index(label) for label in class_name], dtype=int)
+ class_id = np.array(
+ [classes.index(label) for label in class_name], dtype=np.int64
+ )
return xyxy, class_id, class_name
@@ -386,7 +420,7 @@ def from_qwen_3_vl(
result: str,
resolution_wh: tuple[int, int],
classes: list[str] | None = None,
-) -> tuple[npt.NDArray[Any], npt.NDArray[Any] | None, npt.NDArray[Any]]:
+) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.int64] | None, npt.NDArray[np.str_]]:
"""
Parse and scale bounding boxes from Qwen-3-VL style JSON output.
@@ -411,7 +445,7 @@ def from_qwen_3_vl(
def from_deepseek_vl_2(
result: str, resolution_wh: tuple[int, int], classes: list[str] | None = None
-) -> tuple[npt.NDArray[Any], npt.NDArray[Any] | None, npt.NDArray[Any]]:
+) -> tuple[npt.NDArray[np.float32], npt.NDArray[np.int64] | None, npt.NDArray[np.str_]]:
"""
Parse bounding boxes from deepseek-vl2-formatted text, scale them to the specified
resolution, and optionally filter by classes.
@@ -448,12 +482,13 @@ def from_deepseek_vl_2(
f"and det tags ({len(detection_segments)}) in the result must be equal."
)
- xyxy, class_name_list = [], []
+ xyxy_list: list[list[float]] = []
+ class_name_list: list[str] = []
for label, detection_blob in zip(label_segments, detection_segments):
current_class_name = label.strip()
for box in re.findall(r"\[(.*?)\]", detection_blob):
x1, y1, x2, y2 = map(float, box.strip("[]").split(","))
- xyxy.append(
+ xyxy_list.append(
[
(x1 / 999 * width),
(y1 / 999 * height),
@@ -463,29 +498,33 @@ def from_deepseek_vl_2(
)
class_name_list.append(current_class_name)
- xyxy = np.array(xyxy, dtype=np.float32)
- class_name = np.array(class_name_list)
+ xyxy_arr = np.array(xyxy_list, dtype=np.float32)
+ class_name_arr = np.array(class_name_list, dtype=str)
if classes is not None:
- mask = np.array([name in classes for name in class_name], dtype=bool)
- xyxy = xyxy[mask]
- class_name = class_name[mask]
- class_id = np.array([classes.index(name) for name in class_name])
+ mask_arr = np.array([name in classes for name in class_name_arr], dtype=bool)
+ xyxy_arr = xyxy_arr[mask_arr]
+ class_name_arr = class_name_arr[mask_arr]
+ class_id_arr = np.array(
+ [classes.index(name) for name in class_name_arr], dtype=np.int64
+ )
else:
- unique_classes = sorted(list(set(class_name)))
+ unique_classes = sorted(list(set(class_name_arr)))
class_to_id = {name: i for i, name in enumerate(unique_classes)}
- class_id = np.array([class_to_id[name] for name in class_name])
+ class_id_arr = np.array(
+ [class_to_id[name] for name in class_name_arr], dtype=np.int64
+ )
- return xyxy, class_id, class_name
+ return xyxy_arr, class_id_arr, class_name_arr
def from_florence_2(
- result: dict[str, Any], resolution_wh: tuple[int, int]
+ result: dict[str, object], resolution_wh: tuple[int, int]
) -> tuple[
- npt.NDArray[Any],
- npt.NDArray[Any] | None,
- npt.NDArray[Any] | None,
- npt.NDArray[Any] | None,
+ npt.NDArray[np.float32],
+ npt.NDArray[np.str_] | None,
+ npt.NDArray[np.bool_] | None,
+ npt.NDArray[np.float32] | None,
]:
"""
Parse results from the Florence 2 multi-model model.
@@ -509,66 +548,94 @@ def from_florence_2(
raise ValueError(
f"{task} not supported. Supported tasks are: {SUPPORTED_TASKS_FLORENCE_2}"
)
- result = result[task]
+ task_result = result[task]
if task in ["", "", ""]:
- xyxy = np.array(result["bboxes"], dtype=np.float32)
- labels = np.array(result["labels"])
+ task_result = cast(dict[str, object], task_result)
+ xyxy = np.array(
+ cast(list[list[float]], task_result["bboxes"]), dtype=np.float32
+ )
+ labels = np.array(cast(list[str], task_result["labels"]), dtype=str)
return xyxy, labels, None, None
if task == "":
- xyxy = np.array(result["bboxes"], dtype=np.float32)
+ task_result = cast(dict[str, object], task_result)
+ xyxy = np.array(
+ cast(list[list[float]], task_result["bboxes"]), dtype=np.float32
+ )
# provides labels, but they are ["", "", "", ...]
return xyxy, None, None, None
if task == "":
- xyxyxyxy = np.array(result["quad_boxes"], dtype=np.float32)
+ task_result = cast(dict[str, object], task_result)
+ xyxyxyxy = np.array(
+ cast(list[list[float]], task_result["quad_boxes"]), dtype=np.float32
+ )
xyxyxyxy = xyxyxyxy.reshape(-1, 4, 2)
- xyxy = np.array([polygon_to_xyxy(polygon) for polygon in xyxyxyxy])
- labels = np.array(result["labels"])
+ xyxy = np.array(
+ [polygon_to_xyxy(polygon) for polygon in xyxyxyxy], dtype=np.float32
+ )
+ labels = np.array(cast(list[str], task_result["labels"]), dtype=str)
return xyxy, labels, None, xyxyxyxy
if task in ["", ""]:
- xyxy_list = []
- masks_list = []
- for polygons_of_same_class in result["polygons"]:
+ task_result = cast(dict[str, object], task_result)
+ xyxy_list: list[npt.NDArray[np.float32]] = []
+ masks_list: list[npt.NDArray[np.bool_]] = []
+ for polygons_of_same_class in cast(
+ list[list[list[float]]], task_result["polygons"]
+ ):
for polygon in polygons_of_same_class:
- polygon = np.reshape(polygon, (-1, 2)).astype(np.int32)
- mask = polygon_to_mask(polygon, resolution_wh).astype(bool)
+ polygon_arr = np.reshape(polygon, (-1, 2)).astype(np.int32)
+ mask = cast(
+ npt.NDArray[np.bool_],
+ polygon_to_mask(polygon_arr, resolution_wh).astype(bool),
+ )
masks_list.append(mask)
- xyxy = polygon_to_xyxy(polygon)
- xyxy_list.append(xyxy)
+ xyxy_box = cast(
+ npt.NDArray[np.float32],
+ np.asarray(polygon_to_xyxy(polygon_arr), dtype=np.float32),
+ )
+ xyxy_list.append(xyxy_box)
# per-class labels also provided, but they are ["", "", "", ...]
# when we figure out how to set class names, we can do
# zip(result["labels"], result["polygons"])
xyxy = np.array(xyxy_list, dtype=np.float32)
- masks = np.array(masks_list)
+ masks = np.stack(masks_list, axis=0)
return xyxy, None, masks, None
if task == "":
- xyxy = np.array(result["bboxes"], dtype=np.float32)
- labels = np.array(result["bboxes_labels"])
+ task_result = cast(dict[str, object], task_result)
+ xyxy = np.array(
+ cast(list[list[float]], task_result["bboxes"]), dtype=np.float32
+ )
+ labels = np.array(cast(list[str], task_result["bboxes_labels"]), dtype=str)
# Also has "polygons" and "polygons_labels", but they don't seem to be used
return xyxy, labels, None, None
if task in ["", ""]:
- assert isinstance(result, str), (
- f"Expected string as result, got {type(result)}"
+ assert isinstance(task_result, str), (
+ f"Expected string as result, got {type(task_result)}"
)
- if result == "No object detected.":
- return np.empty((0, 4), dtype=np.float32), np.array([]), None, None
+ if task_result == "No object detected.":
+ return (
+ np.empty((0, 4), dtype=np.float32),
+ np.empty(0, dtype=str),
+ None,
+ None,
+ )
pattern = re.compile(r"")
- match = pattern.search(result)
+ match = pattern.search(task_result)
assert match is not None, (
- f"Expected string to end in location tags, but got {result}"
+ f"Expected string to end in location tags, but got {task_result}"
)
w, h = validate_resolution(resolution_wh)
xyxy = np.array([match.groups()], dtype=np.float32)
xyxy *= np.array([w, h, w, h]) / 1000
- result_string = result[: match.start()]
+ result_string = task_result[: match.start()]
labels = np.array([result_string])
return xyxy, labels, None, None
@@ -579,7 +646,7 @@ def from_google_gemini_2_0(
result: str,
resolution_wh: tuple[int, int],
classes: list[str] | None = None,
-) -> tuple[npt.NDArray[Any], npt.NDArray[Any] | None, npt.NDArray[Any]]:
+) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.int64] | None, npt.NDArray[np.str_]]:
"""
Parse and scale bounding boxes from Google Gemini style
[JSON output](https://ai.google.dev/gemini-api/docs/vision?lang=python).
@@ -626,37 +693,57 @@ def from_google_gemini_2_0(
try:
data = json.loads(result)
except json.JSONDecodeError:
- return np.empty((0, 4)), np.empty((0,), dtype=int), np.empty((0,), dtype=str)
+ return (
+ np.empty((0, 4), dtype=np.float64),
+ np.empty((0,), dtype=np.int64),
+ np.empty((0,), dtype=str),
+ )
+ if not isinstance(data, list):
+ return (
+ np.empty((0, 4), dtype=np.float64),
+ np.empty((0,), dtype=np.int64),
+ np.empty((0,), dtype=str),
+ )
- labels = []
- xyxy = []
+ labels: list[str] = []
+ xyxy_list: list[list[float]] = []
for item in data:
- if "box_2d" not in item or "label" not in item:
+ if not isinstance(item, dict):
continue
- labels.append(item["label"])
- box = item["box_2d"]
+ label = item.get("label")
+ box_2d = item.get("box_2d")
+ if not isinstance(label, str) or not isinstance(box_2d, list):
+ continue
+ labels.append(label)
+ box = [float(value) for value in box_2d]
# Gemini bbox order is [y_min, x_min, y_max, x_max]
- xyxy.append([box[1], box[0], box[3], box[2]])
+ xyxy_list.append([box[1], box[0], box[3], box[2]])
- if len(xyxy) == 0:
- return np.empty((0, 4)), np.empty((0,), dtype=int), np.empty((0,), dtype=str)
+ if len(xyxy_list) == 0:
+ return (
+ np.empty((0, 4), dtype=np.float64),
+ np.empty((0,), dtype=np.int64),
+ np.empty((0,), dtype=str),
+ )
- xyxy = denormalize_boxes(
- np.array(xyxy, dtype=np.float64),
+ xyxy_arr: npt.NDArray[np.float64] = denormalize_boxes(
+ np.array(xyxy_list, dtype=np.float64),
resolution_wh=(w, h),
normalization_factor=1000,
)
- class_name = np.array(labels)
- class_id = None
+ class_name_arr: npt.NDArray[np.str_] = np.array(labels, dtype=str)
+ class_id_arr: npt.NDArray[np.int64] | None = None
if classes is not None:
- mask = np.array([name in classes for name in class_name], dtype=bool)
- xyxy = xyxy[mask]
- class_name = class_name[mask]
- class_id = np.array([classes.index(name) for name in class_name])
+ mask = np.array([name in classes for name in class_name_arr], dtype=bool)
+ xyxy_arr = xyxy_arr[mask]
+ class_name_arr = class_name_arr[mask]
+ class_id_arr = np.array(
+ [classes.index(name) for name in class_name_arr], dtype=np.int64
+ )
- return xyxy, class_id, class_name
+ return xyxy_arr, class_id_arr, class_name_arr
def from_google_gemini_2_5(
@@ -664,11 +751,11 @@ def from_google_gemini_2_5(
resolution_wh: tuple[int, int],
classes: list[str] | None = None,
) -> tuple[
- npt.NDArray[Any],
- npt.NDArray[Any] | None,
- npt.NDArray[Any],
- npt.NDArray[Any] | None,
- npt.NDArray[Any] | None,
+ npt.NDArray[np.float64],
+ npt.NDArray[np.int64] | None,
+ npt.NDArray[np.str_],
+ npt.NDArray[np.float64] | None,
+ npt.NDArray[np.bool_] | None,
]:
"""
Parse and scale bounding boxes and masks from Google Gemini 2.5 style
@@ -712,44 +799,45 @@ def from_google_gemini_2_5(
break
try:
- data = json.loads(result)
+ data = cast(list[dict[str, object]], json.loads(result))
except json.JSONDecodeError:
return (
- np.empty((0, 4)),
- np.array([], dtype=int),
+ np.empty((0, 4), dtype=np.float64),
+ np.array([], dtype=np.int64),
np.array([], dtype=str),
- np.array([], dtype=float),
+ np.array([], dtype=np.float64),
None,
)
- boxes_list: list[Any] = []
+ boxes_list: list[npt.NDArray[np.float64]] = []
labels_list: list[str] = []
confidence_list: list[float] | None = []
- masks_list: list[npt.NDArray[Any]] | None = []
+ masks_list: list[npt.NDArray[np.bool_]] | None = []
for item in data:
if "box_2d" not in item or "label" not in item:
continue
- labels_list.append(item["label"])
- box = item["box_2d"]
+ labels_list.append(cast(str, item["label"]))
+ box = cast(list[float], item["box_2d"])
# Gemini bbox order is [y_min, x_min, y_max, x_max]
absolute_bbox = denormalize_boxes(
np.array([[box[1], box[0], box[3], box[2]]]).astype(np.float64),
resolution_wh=(w, h),
normalization_factor=1000,
)[0]
+ absolute_bbox = cast(npt.NDArray[np.float64], absolute_bbox)
boxes_list.append(absolute_bbox)
if "mask" in item:
if masks_list is not None:
- png_str = item["mask"]
+ png_str = cast(str, item["mask"])
if not png_str.startswith("data:image/png;base64,"):
masks_list.append(np.zeros((h, w), dtype=bool))
continue
- png_str = png_str.removeprefix("data:image/png;base64,")
- png_str = base64.b64decode(png_str)
- mask_img = Image.open(io.BytesIO(png_str))
+ png_payload = png_str.removeprefix("data:image/png;base64,")
+ png_bytes = base64.b64decode(png_payload)
+ mask_img = Image.open(io.BytesIO(png_bytes))
y_min, y_max = int(absolute_bbox[1]), int(absolute_bbox[3])
x_min, x_max = int(absolute_bbox[0]), int(absolute_bbox[2])
@@ -758,11 +846,11 @@ def from_google_gemini_2_5(
bbox_width = x_max - x_min
if bbox_height > 0 and bbox_width > 0:
- mask_img = mask_img.resize(
+ mask_img_resized = mask_img.resize(
(bbox_width, bbox_height), resample=Image.Resampling.BILINEAR
)
np_mask: npt.NDArray[np.bool_] = np.zeros((h, w), dtype=bool)
- np_mask[y_min:y_max, x_min:x_max] = np.array(mask_img) > 0
+ np_mask[y_min:y_max, x_min:x_max] = np.array(mask_img_resized) > 0
masks_list.append(np_mask)
else:
masks_list.append(np.zeros((h, w), dtype=bool))
@@ -771,28 +859,30 @@ def from_google_gemini_2_5(
if "confidence" in item:
if confidence_list is not None:
- confidence_list.append(item["confidence"])
+ confidence_list.append(cast(float, item["confidence"]))
else:
confidence_list = None
if not boxes_list:
return (
- np.empty((0, 4)),
- np.array([], dtype=int),
+ np.empty((0, 4), dtype=np.float64),
+ np.array([], dtype=np.int64),
np.array([], dtype=str),
- np.array([], dtype=float),
+ np.array([], dtype=np.float64),
None,
)
- xyxy = np.array(boxes_list, dtype=float)
- class_name = np.array(labels_list)
- class_id: npt.NDArray[Any]
+ xyxy = np.array(boxes_list, dtype=np.float64)
+ class_name = np.array(labels_list, dtype=str)
+ class_id: npt.NDArray[np.int64]
if classes is not None:
mask = np.array([name in classes for name in class_name], dtype=bool)
xyxy = xyxy[mask]
class_name = class_name[mask]
- class_id = np.array([classes.index(name) for name in class_name])
+ class_id = np.array(
+ [classes.index(name) for name in class_name], dtype=np.int64
+ )
if masks_list is not None:
masks_list = [m for m, keep in zip(masks_list, mask) if keep]
@@ -801,12 +891,14 @@ def from_google_gemini_2_5(
else:
unique_labels = sorted(list(set(class_name)))
label_to_id = {label: i for i, label in enumerate(unique_labels)}
- class_id = np.array([label_to_id[name] for name in class_name])
+ class_id = np.array([label_to_id[name] for name in class_name], dtype=np.int64)
confidence = (
- np.array(confidence_list, dtype=float) if confidence_list is not None else None
+ np.array(confidence_list, dtype=np.float64)
+ if confidence_list is not None
+ else None
)
- masks = np.array(masks_list) if masks_list is not None else None
+ masks = np.array(masks_list, dtype=bool) if masks_list is not None else None
return (
xyxy,
@@ -818,9 +910,9 @@ def from_google_gemini_2_5(
def from_moondream(
- result: dict[str, Any],
+ result: dict[str, object],
resolution_wh: tuple[int, int],
-) -> npt.NDArray[Any]:
+) -> npt.NDArray[np.float64]:
"""
Parse and scale bounding boxes from moondream JSON output.
@@ -856,28 +948,27 @@ def from_moondream(
)
if "objects" not in result or not isinstance(result["objects"], list):
- return np.empty((0, 4), dtype=float)
+ return np.empty((0, 4), dtype=np.float64)
- xyxy = []
+ xyxy: list[list[float]] = []
+ objects = cast(list[object], result["objects"])
- for item in result["objects"]:
+ for item in objects:
+ item = cast(dict[str, object], item)
if not all(k in item for k in ["x_min", "y_min", "x_max", "y_max"]):
continue
- x_min = item["x_min"]
- y_min = item["y_min"]
- x_max = item["x_max"]
- y_max = item["y_max"]
+ x_min = cast(float, item["x_min"])
+ y_min = cast(float, item["y_min"])
+ x_max = cast(float, item["x_max"])
+ y_max = cast(float, item["y_max"])
xyxy.append([x_min, y_min, x_max, y_max])
if len(xyxy) == 0:
- return cast(npt.NDArray[Any], np.empty((0, 4)))
+ return np.empty((0, 4), dtype=np.float64)
return cast(
- npt.NDArray[Any],
- denormalize_boxes(
- np.array(xyxy).astype(np.float64),
- resolution_wh=(w, h),
- ),
+ npt.NDArray[np.float64],
+ denormalize_boxes(np.array(xyxy, dtype=np.float64), resolution_wh=(w, h)),
)
diff --git a/src/supervision/draw/utils.py b/src/supervision/draw/utils.py
index 37ea37b61d..4d9fff0479 100644
--- a/src/supervision/draw/utils.py
+++ b/src/supervision/draw/utils.py
@@ -318,6 +318,7 @@ def draw_image(
Raises:
FileNotFoundError: If the image path does not exist.
+ OSError: If the image path exists but cannot be decoded.
ValueError: For invalid opacity or rectangle dimensions.
"""
@@ -325,7 +326,12 @@ def draw_image(
if isinstance(image, str):
if not os.path.exists(image):
raise FileNotFoundError(f"Image path ('{image}') does not exist.")
- image = cv2.imread(image, cv2.IMREAD_UNCHANGED)
+ loaded_image = cv2.imread(image, cv2.IMREAD_UNCHANGED)
+ if loaded_image is None:
+ raise OSError(f"Could not decode image path ('{image}').")
+ image_np = cast(npt.NDArray[np.uint8], loaded_image)
+ else:
+ image_np = image
# Validate opacity
if not 0.0 <= opacity <= 1.0:
@@ -345,12 +351,13 @@ def draw_image(
raise ValueError("Invalid rectangle dimensions.")
# Resize and isolate alpha channel
- image = cv2.resize(image, (rect_width, rect_height))
- image = cast(npt.NDArray[np.uint8], image)
+ image_np = cast(
+ npt.NDArray[np.uint8], cv2.resize(image_np, (rect_width, rect_height))
+ )
alpha_channel = (
- image[:, :, 3]
- if image.shape[2] == 4
- else np.ones((rect_height, rect_width), dtype=image.dtype) * 255
+ image_np[:, :, 3]
+ if image_np.shape[2] == 4
+ else np.ones((rect_height, rect_width), dtype=image_np.dtype) * 255
)
alpha_scaled = cv2.convertScaleAbs(alpha_channel * opacity)
@@ -359,7 +366,7 @@ def draw_image(
alpha_float = alpha_scaled.astype(np.float32) / 255.0
blended_roi = cv2.convertScaleAbs(
(1 - alpha_float[..., np.newaxis]) * scene_roi
- + alpha_float[..., np.newaxis] * image[:, :, :3]
+ + alpha_float[..., np.newaxis] * image_np[:, :, :3]
)
# Update the scene
diff --git a/src/supervision/key_points/annotators.py b/src/supervision/key_points/annotators.py
index d4dfb73013..7b1ba37e3a 100644
--- a/src/supervision/key_points/annotators.py
+++ b/src/supervision/key_points/annotators.py
@@ -2,7 +2,6 @@
from abc import ABC, abstractmethod
from collections.abc import Sequence
-from typing import Any
import cv2
import numpy as np
@@ -207,7 +206,7 @@ def __init__(
text_padding: int = 10,
border_radius: int = 0,
smart_position: bool = False,
- ):
+ ) -> None:
"""
Args:
color: The color to use for each keypoint label. If a list is provided,
@@ -331,7 +330,9 @@ def annotate(
if skeletons_count == 0:
return scene
- anchors = key_points.xy.reshape(points_count * skeletons_count, 2).astype(int)
+ anchors: npt.NDArray[np.int_] = np.asarray(
+ key_points.xy.reshape(points_count * skeletons_count, 2), dtype=int
+ )
mask = np.all(anchors != 0, axis=1)
if not np.any(mask):
@@ -358,7 +359,7 @@ def annotate(
text_colors = text_colors[mask]
filtered_labels = processed_labels[mask]
- xyxy = np.array(
+ xyxy: npt.NDArray[np.float32] = np.array(
[
self.get_text_bounding_box(
text=label,
@@ -368,13 +369,16 @@ def annotate(
center_coordinates=tuple(anchor),
)
for anchor, label in zip(anchors, filtered_labels)
- ]
+ ],
+ dtype=np.float32,
)
- xyxy_padded = pad_boxes(xyxy=xyxy, px=self.text_padding)
+ xyxy_padded: npt.NDArray[np.float32] = pad_boxes(
+ xyxy=xyxy, px=self.text_padding
+ ).astype(np.float32)
if self.smart_position:
- xyxy_padded = spread_out_boxes(xyxy_padded)
- xyxy = pad_boxes(xyxy=xyxy_padded, px=-self.text_padding)
+ xyxy_padded = spread_out_boxes(xyxy_padded).astype(np.float32)
+ xyxy = pad_boxes(xyxy=xyxy_padded, px=-self.text_padding).astype(np.float32)
for text, color, text_color, box, box_padded in zip(
filtered_labels, colors, text_colors, xyxy, xyxy_padded
@@ -388,7 +392,7 @@ def annotate(
cv2.putText(
img=scene,
text=text,
- org=(box[0], box[3]),
+ org=(int(box[0]), int(box[3])),
fontFace=font,
fontScale=self.text_scale,
color=text_color.as_bgr(),
@@ -439,7 +443,7 @@ def preprocess_and_validate_colors(
colors: Color | list[Color] | None,
points_count: int,
skeletons_count: int,
- ) -> npt.NDArray[Any]:
+ ) -> npt.NDArray[np.object_]:
if isinstance(colors, list) and len(colors) != points_count:
raise ValueError(
f"Number of colors ({len(colors)}) must match number of key points "
diff --git a/src/supervision/key_points/core.py b/src/supervision/key_points/core.py
index cd8190ae15..279fe16497 100644
--- a/src/supervision/key_points/core.py
+++ b/src/supervision/key_points/core.py
@@ -1,26 +1,114 @@
from __future__ import annotations
-from collections.abc import Iterable, Iterator
+from collections.abc import Iterable, Iterator, Sequence
from dataclasses import dataclass, field
-from typing import Any, Union, cast
+from typing import Protocol, TypedDict, cast, overload
import numpy as np
import numpy.typing as npt
from supervision.config import CLASS_NAME_DATA_FIELD
from supervision.detection.core import Detections
-from supervision.detection.utils.internal import get_data_item, is_data_equal
+from supervision.detection.utils._typing import DetectionDataType
+from supervision.detection.utils.internal import (
+ get_data_item,
+ is_data_equal,
+)
from supervision.validators import validate_key_points_fields
-Index1D = Union[
- int,
- slice,
- list[int],
- list[bool],
- npt.NDArray[np.int_],
- npt.NDArray[np.bool_],
-]
-Index2D = tuple[Index1D, Index1D]
+
+class _TypeTensorLike(Protocol):
+ def cpu(self) -> _TypeTensorLike:
+ """Return the tensor on CPU."""
+
+ def numpy(self) -> npt.NDArray[np.generic]:
+ """Convert the tensor to a NumPy array."""
+
+ def numel(self) -> int:
+ """Return the number of elements in the tensor."""
+
+
+class _TypeInferenceKeypoint(TypedDict):
+ x: float
+ y: float
+ confidence: float
+
+
+_TypeInferencePrediction = TypedDict(
+ "_TypeInferencePrediction",
+ {
+ "keypoints": list[_TypeInferenceKeypoint],
+ "class_id": int,
+ "class": str,
+ },
+)
+
+
+class _TypeInferenceResult(TypedDict):
+ predictions: list[_TypeInferencePrediction]
+
+
+class _TypeMediapipeLandmark(Protocol):
+ x: float
+ y: float
+ visibility: float
+
+
+class _TypeMediapipeLandmarkList(Protocol):
+ landmark: list[_TypeMediapipeLandmark]
+
+
+class _TypeMediapipeResults(Protocol):
+ pose_landmarks: (
+ list[list[_TypeMediapipeLandmark]] | _TypeMediapipeLandmarkList | None
+ )
+ face_landmarks: list[list[_TypeMediapipeLandmark]]
+ multi_face_landmarks: list[_TypeMediapipeLandmarkList] | None
+
+
+class _TypeUltralyticsKeypoints(Protocol):
+ xy: _TypeTensorLike
+ conf: _TypeTensorLike
+
+
+class _TypeUltralyticsBoxes(Protocol):
+ cls: _TypeTensorLike
+
+
+class _TypeUltralyticsResults(Protocol):
+ keypoints: _TypeUltralyticsKeypoints
+ boxes: _TypeUltralyticsBoxes
+ names: dict[int, str]
+
+
+class _TypeYoloNasPrediction(Protocol):
+ poses: npt.NDArray[np.float32]
+ labels: npt.NDArray[np.int_] | None
+
+
+class _TypeYoloNasResults(Protocol):
+ prediction: _TypeYoloNasPrediction
+ class_names: Sequence[str] | None
+
+
+class _TypeDetectron2Instances(Protocol):
+ pred_keypoints: _TypeTensorLike
+ pred_classes: _TypeTensorLike
+
+
+class _TypeDetectron2Results(Protocol):
+ instances: _TypeDetectron2Instances
+
+
+class _TypeTransformersResult(Protocol):
+ keypoints: _TypeTensorLike
+ scores: _TypeTensorLike
+
+
+_TypeIndex1D = (
+ int | slice | list[int] | list[bool] | npt.NDArray[np.int_] | npt.NDArray[np.bool_]
+)
+_TypeIndex2D = tuple[_TypeIndex1D, _TypeIndex1D]
@dataclass
@@ -167,7 +255,7 @@ class simplifies data manipulation and filtering, providing a uniform API for
xy: npt.NDArray[np.float32]
class_id: npt.NDArray[np.int_] | None = None
confidence: npt.NDArray[np.float32] | None = None
- data: dict[str, npt.NDArray[np.generic] | list[Any]] = field(default_factory=dict)
+ data: DetectionDataType = field(default_factory=dict)
def __post_init__(self) -> None:
validate_key_points_fields(
@@ -202,9 +290,9 @@ def __iter__(
) -> Iterator[
tuple[
npt.NDArray[np.float32],
- npt.NDArray[np.float32] | None,
- npt.NDArray[np.int_] | None,
- dict[str, npt.NDArray[np.generic] | list[Any]],
+ np.float32 | None,
+ np.integer | None,
+ DetectionDataType,
]
]:
"""
@@ -212,27 +300,42 @@ def __iter__(
`(xy, confidence, class_id, data)` for each object detection.
"""
for i in range(len(self.xy)):
+ confidence = (
+ cast(np.float32, self.confidence[i])
+ if self.confidence is not None
+ else None
+ )
+ class_id = (
+ cast(np.integer, self.class_id[i])
+ if self.class_id is not None
+ else None
+ )
yield (
self.xy[i],
- self.confidence[i] if self.confidence is not None else None,
- self.class_id[i] if self.class_id is not None else None,
+ confidence,
+ class_id,
get_data_item(self.data, i),
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, KeyPoints):
return NotImplemented
- return all(
- [
- np.array_equal(self.xy, other.xy),
- np.array_equal(self.class_id, other.class_id),
- np.array_equal(self.confidence, other.confidence),
- is_data_equal(self.data, other.data),
- ]
- )
+ if not np.array_equal(self.xy, other.xy):
+ return False
+ if self.class_id is None or other.class_id is None:
+ if self.class_id is not other.class_id:
+ return False
+ elif not np.array_equal(self.class_id, other.class_id):
+ return False
+ if self.confidence is None or other.confidence is None:
+ if self.confidence is not other.confidence:
+ return False
+ elif not np.array_equal(self.confidence, other.confidence):
+ return False
+ return is_data_equal(self.data, other.data)
@classmethod
- def from_inference(cls, inference_result: Any) -> KeyPoints:
+ def from_inference(cls, inference_result: object) -> KeyPoints:
"""
Create a `sv.KeyPoints` object from the [Roboflow](https://roboflow.com/)
API inference result or the [Inference](https://inference.roboflow.com/)
@@ -281,20 +384,26 @@ def from_inference(cls, inference_result: Any) -> KeyPoints:
)
if hasattr(inference_result, "dict"):
- inference_result = inference_result.dict(exclude_none=True, by_alias=True)
+ inference_result = cast(
+ _TypeInferenceResult,
+ inference_result.dict(exclude_none=True, by_alias=True),
+ )
elif hasattr(inference_result, "json"):
- inference_result = inference_result.json()
+ inference_result = cast(_TypeInferenceResult, inference_result.json())
+ else:
+ inference_result = cast(_TypeInferenceResult, inference_result)
+
if not inference_result.get("predictions"):
return cls.empty()
- xy = []
- confidence = []
- class_id = []
- class_names = []
+ xy: list[list[list[float]]] = []
+ confidence: list[list[float]] = []
+ class_id: list[int] = []
+ class_names: list[str] = []
for prediction in inference_result["predictions"]:
- prediction_xy = []
- prediction_confidence = []
+ prediction_xy: list[list[float]] = []
+ prediction_confidence: list[float] = []
for keypoint in prediction["keypoints"]:
prediction_xy.append([keypoint["x"], keypoint["y"]])
prediction_confidence.append(keypoint["confidence"])
@@ -304,18 +413,20 @@ def from_inference(cls, inference_result: Any) -> KeyPoints:
class_id.append(prediction["class_id"])
class_names.append(prediction["class"])
- data = {CLASS_NAME_DATA_FIELD: np.array(class_names)}
+ data: DetectionDataType = {
+ CLASS_NAME_DATA_FIELD: np.array(class_names, dtype=str)
+ }
return cls(
xy=np.array(xy, dtype=np.float32),
confidence=np.array(confidence, dtype=np.float32),
- class_id=np.array(class_id, dtype=int),
+ class_id=np.array(class_id, dtype=np.int_),
data=data,
)
@classmethod
def from_mediapipe(
- cls, mediapipe_results: Any, resolution_wh: tuple[int, int]
+ cls, mediapipe_results: _TypeMediapipeResults, resolution_wh: tuple[int, int]
) -> KeyPoints:
"""
Creates a `sv.KeyPoints` instance from a
@@ -392,18 +503,15 @@ def from_mediapipe(
```
"""
+ results: Sequence[Sequence[_TypeMediapipeLandmark]] = []
if hasattr(mediapipe_results, "pose_landmarks"):
- results = mediapipe_results.pose_landmarks
- if not isinstance(mediapipe_results.pose_landmarks, list):
- if mediapipe_results.pose_landmarks is None:
- results = []
- else:
- results = [
- [
- landmark
- for landmark in mediapipe_results.pose_landmarks.landmark
- ]
- ]
+ pose_landmarks = mediapipe_results.pose_landmarks
+ if pose_landmarks is None:
+ results = []
+ elif isinstance(pose_landmarks, list):
+ results = pose_landmarks
+ else:
+ results = [pose_landmarks.landmark]
elif hasattr(mediapipe_results, "face_landmarks"):
results = mediapipe_results.face_landmarks
elif hasattr(mediapipe_results, "multi_face_landmarks"):
@@ -418,11 +526,11 @@ def from_mediapipe(
if len(results) == 0:
return cls.empty()
- xy = []
- confidence = []
+ xy: list[list[list[float]]] = []
+ confidence: list[list[float]] = []
for pose in results:
- prediction_xy = []
- prediction_confidence = []
+ prediction_xy: list[list[float]] = []
+ prediction_confidence: list[float] = []
for landmark in pose:
keypoint_xy = [
landmark.x * resolution_wh[0],
@@ -440,7 +548,9 @@ def from_mediapipe(
)
@classmethod
- def from_ultralytics(cls, ultralytics_results: Any) -> KeyPoints:
+ def from_ultralytics(
+ cls, ultralytics_results: _TypeUltralyticsResults
+ ) -> KeyPoints:
"""
Creates a `sv.KeyPoints` instance from a
[YOLOv8](https://github.com/ultralytics/ultralytics) pose inference result.
@@ -468,16 +578,18 @@ def from_ultralytics(cls, ultralytics_results: Any) -> KeyPoints:
if ultralytics_results.keypoints.xy.numel() == 0:
return cls.empty()
- xy = ultralytics_results.keypoints.xy.cpu().numpy()
- class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int)
- class_names = np.array([ultralytics_results.names[i] for i in class_id])
+ xy = ultralytics_results.keypoints.xy.cpu().numpy().astype(np.float32)
+ class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(np.int_)
+ class_names = np.array(
+ [ultralytics_results.names[int(i)] for i in class_id], dtype=str
+ )
- confidence = ultralytics_results.keypoints.conf.cpu().numpy()
- data = {CLASS_NAME_DATA_FIELD: class_names}
+ confidence = ultralytics_results.keypoints.conf.cpu().numpy().astype(np.float32)
+ data: DetectionDataType = {CLASS_NAME_DATA_FIELD: class_names}
return cls(xy, class_id, confidence, data)
@classmethod
- def from_yolo_nas(cls, yolo_nas_results: Any) -> KeyPoints:
+ def from_yolo_nas(cls, yolo_nas_results: _TypeYoloNasResults) -> KeyPoints:
"""
Create a `sv.KeyPoints` instance from a [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS-POSE.md)
pose inference results.
@@ -519,7 +631,7 @@ def from_yolo_nas(cls, yolo_nas_results: Any) -> KeyPoints:
else:
class_id = None
- data = {}
+ data: DetectionDataType = {}
if class_id is not None and yolo_nas_results.class_names is not None:
class_names = []
for c_id in class_id:
@@ -535,7 +647,7 @@ def from_yolo_nas(cls, yolo_nas_results: Any) -> KeyPoints:
)
@classmethod
- def from_detectron2(cls, detectron2_results: Any) -> KeyPoints:
+ def from_detectron2(cls, detectron2_results: dict[str, object]) -> KeyPoints:
"""
Create a `sv.KeyPoints` object from the
[Detectron2](https://github.com/facebookresearch/detectron2) inference result.
@@ -567,26 +679,24 @@ def from_detectron2(cls, detectron2_results: Any) -> KeyPoints:
```
"""
- if hasattr(detectron2_results["instances"], "pred_keypoints"):
- if detectron2_results["instances"].pred_keypoints.cpu().numpy().size == 0:
+ instances = cast(_TypeDetectron2Instances, detectron2_results["instances"])
+ if hasattr(instances, "pred_keypoints"):
+ if instances.pred_keypoints.cpu().numpy().size == 0:
return cls.empty()
return cls(
- xy=detectron2_results["instances"]
- .pred_keypoints.cpu()
- .numpy()[:, :, :2],
- confidence=detectron2_results["instances"]
- .pred_keypoints.cpu()
- .numpy()[:, :, 2],
- class_id=detectron2_results["instances"]
- .pred_classes.cpu()
- .numpy()
- .astype(int),
+ xy=instances.pred_keypoints.cpu().numpy()[:, :, :2].astype(np.float32),
+ confidence=instances.pred_keypoints.cpu()
+ .numpy()[:, :, 2]
+ .astype(np.float32),
+ class_id=instances.pred_classes.cpu().numpy().astype(np.int_),
)
else:
return cls.empty()
@classmethod
- def from_transformers(cls, transformers_results: Any) -> KeyPoints:
+ def from_transformers(
+ cls, transformers_results: list[dict[str, _TypeTensorLike]]
+ ) -> KeyPoints:
"""
Create a `sv.KeyPoints` object from the
[Transformers](https://github.com/huggingface/transformers) inference result.
@@ -665,7 +775,7 @@ def from_transformers(cls, transformers_results: Any) -> KeyPoints:
return cls(
xy=np.stack(xy).astype(np.float32),
confidence=np.stack(scores).astype(np.float32),
- class_id=np.arange(len(xy)).astype(int),
+ class_id=np.arange(len(xy)).astype(np.int_),
)
else:
return cls.empty()
@@ -743,15 +853,20 @@ def _get_by_2d_bool_mask(self, mask: npt.NDArray[np.bool_]) -> KeyPoints:
data=get_data_item(self.data, slice(None)),
)
+ @overload
+ def __getitem__(self, index: str) -> object: ...
+
+ @overload
+ def __getitem__(self, index: _TypeIndex1D | _TypeIndex2D) -> KeyPoints: ...
+
def __getitem__(
- self,
- index: Index1D | Index2D | str,
- ) -> KeyPoints | npt.NDArray[np.generic] | list[Any] | None:
+ self, index: _TypeIndex1D | _TypeIndex2D | str
+ ) -> KeyPoints | object | None:
if isinstance(index, str):
return self.data.get(index)
if isinstance(index, np.ndarray) and index.ndim == 2 and index.dtype == bool:
- return self._get_by_2d_bool_mask(index)
+ return self._get_by_2d_bool_mask(cast(npt.NDArray[np.bool_], index))
if not isinstance(index, tuple):
index = (index, slice(None))
@@ -777,7 +892,7 @@ def __getitem__(
and not np.isscalar(i)
and not np.isscalar(j)
):
- i, j = np.ix_(i, j)
+ i, j = np.ix_(np.asarray(i), np.asarray(j))
xy_selected = self.xy[i, j]
@@ -812,13 +927,15 @@ def __getitem__(
data=data_selected,
)
- def __setitem__(self, key: str, value: npt.NDArray[np.generic] | list[Any]) -> None:
+ def __setitem__(
+ self, key: str, value: npt.NDArray[np.generic] | list[object]
+ ) -> None:
"""
Set a value in the data dictionary of the `sv.KeyPoints` object.
Args:
key: The key in the data dictionary to set.
- value: The value to set for the key.
+ value: The value to set for the key. Must be a np.ndarray or list.
Examples:
```python
@@ -953,6 +1070,7 @@ def as_detections(
detections = Detections.merge(detections_list)
detections.class_id = self.class_id
detections.data = self.data
- detections = cast(Detections, detections[detections.area > 0])
+ area = detections.area
+ detections = detections[area > 0]
return detections
diff --git a/src/supervision/metrics/core.py b/src/supervision/metrics/core.py
index ad6a17818e..c9287666e8 100644
--- a/src/supervision/metrics/core.py
+++ b/src/supervision/metrics/core.py
@@ -2,16 +2,19 @@
from abc import ABC, abstractmethod
from enum import Enum
-from typing import Any
+from typing import Any, Generic, ParamSpec, TypeVar
+P = ParamSpec("P")
+R = TypeVar("R")
-class Metric(ABC):
+
+class Metric(ABC, Generic[P, R]):
"""
The base class for all supervision metrics.
"""
@abstractmethod
- def update(self, *args: Any, **kwargs: Any) -> Metric:
+ def update(self, *args: P.args, **kwargs: P.kwargs) -> Metric[P, R]:
"""
Add data to the metric, without computing the result.
Return the metric itself to allow method chaining.
@@ -26,7 +29,7 @@ def reset(self) -> None:
raise NotImplementedError
@abstractmethod
- def compute(self, *args: Any, **kwargs: Any) -> Any:
+ def compute(self, *args: Any, **kwargs: Any) -> R:
"""
Compute the metric from the internal state and return the result.
"""
diff --git a/src/supervision/metrics/detection.py b/src/supervision/metrics/detection.py
index ccebf3e513..b6342ca0ef 100644
--- a/src/supervision/metrics/detection.py
+++ b/src/supervision/metrics/detection.py
@@ -2,6 +2,7 @@
from collections.abc import Callable
from dataclasses import dataclass
+from typing import cast
import matplotlib
import matplotlib.pyplot as plt
@@ -143,8 +144,8 @@ def from_detections(
... classes=['person']
... )
>>> confusion_matrix.matrix
- array([[1., 0.],
- [0., 0.]])
+ array([[1, 0],
+ [0, 0]], dtype=int32)
```
"""
@@ -217,16 +218,18 @@ def from_tensors(
... classes=['person', 'dog']
... )
>>> confusion_matrix.matrix
- array([[1., 0., 1.],
- [0., 1., 0.],
- [1., 0., 0.]])
+ array([[1, 0, 1],
+ [0, 1, 0],
+ [1, 0, 0]], dtype=int32)
```
"""
validate_input_tensors(predictions, targets)
num_classes = len(classes)
- matrix = np.zeros((num_classes + 1, num_classes + 1))
+ matrix: npt.NDArray[np.int32] = np.zeros(
+ (num_classes + 1, num_classes + 1), dtype=np.int32
+ )
for true_batch, detection_batch in zip(targets, predictions):
matrix += cls.evaluate_detection_batch(
predictions=detection_batch,
@@ -271,7 +274,9 @@ def evaluate_detection_batch(
Returns:
Confusion matrix based on a single image.
"""
- result_matrix = np.zeros((num_classes + 1, num_classes + 1))
+ result_matrix: npt.NDArray[np.int32] = np.zeros(
+ (num_classes + 1, num_classes + 1), dtype=np.int32
+ )
# Filter predictions by confidence threshold
conf_idx = 5
@@ -472,7 +477,7 @@ def plot(
Confusion matrix plot.
"""
- array = self.matrix.copy()
+ array = self.matrix.astype(np.float32, copy=True)
if normalize:
eps = 1e-8
@@ -495,7 +500,7 @@ def plot(
im = ax.imshow(array, cmap="Blues")
cbar = ax.figure.colorbar(im, ax=ax)
- cbar.mappable.set_clim(vmin=0, vmax=np.nanmax(array))
+ cbar.mappable.set_clim(vmin=0, vmax=float(np.nanmax(array)))
if x_tick_labels is None:
tick_interval = 2
@@ -572,7 +577,7 @@ class MeanAveragePrecision:
map50_95: float
map50: float
map75: float
- per_class_ap50_95: npt.NDArray[np.float64]
+ per_class_ap50_95: npt.NDArray[np.float32]
@classmethod
def from_detections(
@@ -742,7 +747,9 @@ def from_tensors(
if true_objs.shape[0]:
matches = cls._match_detection_batch(
- predicted_objs, true_objs, iou_thresholds
+ predicted_objs,
+ true_objs,
+ iou_thresholds.astype(np.float32, copy=False),
)
stats.append(
(
@@ -756,7 +763,12 @@ def from_tensors(
# Compute average precisions if any matches exist
if stats:
concatenated_stats = [np.concatenate(items, 0) for items in zip(*stats)]
- average_precisions = cls._average_precisions_per_class(*concatenated_stats)
+ average_precisions = cls._average_precisions_per_class(
+ cast(npt.NDArray[np.bool_], concatenated_stats[0]),
+ cast(npt.NDArray[np.float32], concatenated_stats[1]),
+ cast(npt.NDArray[np.int32], concatenated_stats[2]),
+ cast(npt.NDArray[np.int32], concatenated_stats[3]),
+ )
map50 = average_precisions[:, 0].mean()
map75 = average_precisions[:, 5].mean()
map50_95 = average_precisions.mean()
@@ -861,7 +873,7 @@ def _average_precisions_per_class(
prediction_class_ids: npt.NDArray[np.int32],
true_class_ids: npt.NDArray[np.int32],
eps: float = 1e-16,
- ) -> npt.NDArray[np.float64]:
+ ) -> npt.NDArray[np.float32]:
"""
Compute the average precision, given the recall and precision curves.
Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
@@ -883,8 +895,8 @@ def _average_precisions_per_class(
unique_classes, class_counts = np.unique(true_class_ids, return_counts=True)
num_classes = unique_classes.shape[0]
- average_precisions: npt.NDArray[np.float64] = np.zeros(
- (num_classes, matches.shape[1]), dtype=np.float64
+ average_precisions: npt.NDArray[np.float32] = np.zeros(
+ (num_classes, matches.shape[1]), dtype=np.float32
)
for class_idx, class_id in enumerate(unique_classes):
@@ -907,5 +919,5 @@ def _average_precisions_per_class(
)
)
- result: npt.NDArray[np.float64] = average_precisions
+ result: npt.NDArray[np.float32] = average_precisions
return result
diff --git a/src/supervision/metrics/f1_score.py b/src/supervision/metrics/f1_score.py
index 9525a6dde5..eaabb0345d 100644
--- a/src/supervision/metrics/f1_score.py
+++ b/src/supervision/metrics/f1_score.py
@@ -2,7 +2,7 @@
from copy import deepcopy
from dataclasses import dataclass
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, cast
import numpy as np
import numpy.typing as npt
@@ -26,8 +26,20 @@
if TYPE_CHECKING:
import pandas as pd
+F1Stats = tuple[
+ npt.NDArray[np.bool_],
+ npt.NDArray[np.float32],
+ npt.NDArray[np.int32],
+ npt.NDArray[np.int32],
+]
-class F1Score(Metric):
+
+class F1Score(
+ Metric[
+ [Detections | list[Detections], Detections | list[Detections]],
+ "F1ScoreResult",
+ ]
+):
"""
F1 Score is a metric used to evaluate object detection models. It is the harmonic
mean of precision and recall, calculated at different IoU thresholds.
@@ -67,7 +79,7 @@ def __init__(
self,
metric_target: MetricTarget = MetricTarget.BOXES,
averaging_method: AveragingMethod = AveragingMethod.WEIGHTED,
- ):
+ ) -> None:
"""
Initialize the F1Score metric.
@@ -152,32 +164,61 @@ def compute(self) -> F1ScoreResult:
def _compute(
self, predictions_list: list[Detections], targets_list: list[Detections]
) -> F1ScoreResult:
- iou_thresholds = np.linspace(0.5, 0.95, 10)
- stats: list[Any] = []
+ iou_thresholds = np.linspace(0.5, 0.95, 10, dtype=np.float32)
+ stats: list[F1Stats] = []
for predictions, targets in zip(predictions_list, targets_list):
prediction_contents = self._detections_content(predictions)
target_contents = self._detections_content(targets)
if len(targets) > 0:
+ if predictions.class_id is None or targets.class_id is None:
+ raise ValueError(
+ "F1Score metric requires `class_id` on both predictions "
+ "and targets."
+ )
if len(predictions) == 0:
stats.append(
(
np.zeros((0, iou_thresholds.size), dtype=bool),
np.zeros((0,), dtype=np.float32),
- np.zeros((0,), dtype=int),
- targets.class_id,
+ np.zeros((0,), dtype=np.int32),
+ np.asarray(targets.class_id, dtype=np.int32),
)
)
else:
+ if predictions.confidence is None:
+ raise ValueError(
+ "F1Score metric requires `confidence` on predictions."
+ )
+ prediction_class_ids = np.asarray(
+ predictions.class_id, dtype=np.int32
+ )
+ target_class_ids = np.asarray(targets.class_id, dtype=np.int32)
if self._metric_target == MetricTarget.BOXES:
- iou = box_iou_batch(target_contents, prediction_contents)
+ iou: npt.NDArray[np.float64] = np.asarray(
+ box_iou_batch(
+ np.asarray(target_contents, dtype=np.float32),
+ np.asarray(prediction_contents, dtype=np.float32),
+ ),
+ dtype=np.float64,
+ )
elif self._metric_target == MetricTarget.MASKS:
- iou = mask_iou_batch(target_contents, prediction_contents)
+ iou = np.asarray(
+ mask_iou_batch(
+ np.asarray(target_contents, dtype=bool),
+ np.asarray(prediction_contents, dtype=bool),
+ ),
+ dtype=np.float64,
+ )
elif self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
- iou = oriented_box_iou_batch(
- target_contents, prediction_contents
+ iou = np.asarray(
+ oriented_box_iou_batch(
+ np.asarray(target_contents, dtype=np.float32),
+ np.asarray(prediction_contents, dtype=np.float32),
+ ),
+ dtype=np.float64,
)
else:
raise ValueError(
@@ -185,21 +226,17 @@ def _compute(
)
matches = self._match_detection_batch(
- predictions.class_id
- if predictions.class_id is not None
- else np.array([]),
- targets.class_id
- if targets.class_id is not None
- else np.array([]),
+ prediction_class_ids,
+ target_class_ids,
iou,
iou_thresholds,
)
stats.append(
(
matches,
- predictions.confidence,
- predictions.class_id,
- targets.class_id,
+ np.asarray(predictions.confidence, dtype=np.float32),
+ prediction_class_ids,
+ target_class_ids,
)
)
@@ -210,7 +247,7 @@ def _compute(
f1_scores=np.zeros(iou_thresholds.shape[0]),
f1_per_class=np.zeros((0, iou_thresholds.shape[0])),
iou_thresholds=iou_thresholds,
- matched_classes=np.array([], dtype=int),
+ matched_classes=np.array([], dtype=np.int32),
small_objects=None,
medium_objects=None,
large_objects=None,
@@ -248,6 +285,8 @@ def _compute_f1_for_classes(
matches = matches[sorted_indices]
prediction_class_ids = prediction_class_ids[sorted_indices]
unique_classes, class_counts = np.unique(true_class_ids, return_counts=True)
+ unique_classes = np.asarray(unique_classes, dtype=np.int32)
+ class_counts = np.asarray(class_counts, dtype=np.int32)
# Shape: PxTh,P,C,C -> CxThx3
confusion_matrix = self._compute_confusion_matrix(
@@ -273,7 +312,7 @@ def _compute_f1_for_classes(
def _match_detection_batch(
predictions_classes: npt.NDArray[np.int32],
target_classes: npt.NDArray[np.int32],
- iou: npt.NDArray[np.float32],
+ iou: npt.NDArray[np.floating],
iou_thresholds: npt.NDArray[np.float32],
) -> npt.NDArray[np.bool_]:
num_predictions, num_iou_levels = (
@@ -304,9 +343,9 @@ def _match_detection_batch(
@staticmethod
def _compute_confusion_matrix(
sorted_matches: npt.NDArray[np.bool_],
- sorted_prediction_class_ids: npt.NDArray[np.int32],
- unique_classes: npt.NDArray[np.int32],
- class_counts: npt.NDArray[np.int32],
+ sorted_prediction_class_ids: npt.NDArray[np.integer],
+ unique_classes: npt.NDArray[np.integer],
+ class_counts: npt.NDArray[np.integer],
) -> npt.NDArray[np.float64]:
"""
Compute the confusion matrix for each class and IoU threshold.
@@ -341,13 +380,15 @@ class ids.
num_predictions = is_class.sum()
if num_predictions == 0:
- true_positives = np.zeros(num_thresholds)
- false_positives = np.zeros(num_thresholds)
- false_negatives = np.full(num_thresholds, num_true)
+ true_positives = np.zeros(num_thresholds, dtype=np.float64)
+ false_positives = np.zeros(num_thresholds, dtype=np.float64)
+ false_negatives = np.full(num_thresholds, num_true, dtype=np.float64)
elif num_true == 0:
- true_positives = np.zeros(num_thresholds)
- false_positives = np.full(num_thresholds, num_predictions)
- false_negatives = np.zeros(num_thresholds)
+ true_positives = np.zeros(num_thresholds, dtype=np.float64)
+ false_positives = np.full(
+ num_thresholds, num_predictions, dtype=np.float64
+ )
+ false_negatives = np.zeros(num_thresholds, dtype=np.float64)
else:
true_positives = sorted_matches[is_class].sum(0)
false_positives = (1 - sorted_matches[is_class]).sum(0)
@@ -389,14 +430,18 @@ def _compute_f1(
result_f1_score: npt.NDArray[np.float64] = f1_score
return result_f1_score
- def _detections_content(self, detections: Detections) -> npt.NDArray[Any]:
+ def _detections_content(self, detections: Detections) -> npt.NDArray[np.generic]:
"""Return boxes, masks or oriented bounding boxes from detections."""
if self._metric_target == MetricTarget.BOXES:
- result_boxes: npt.NDArray[np.float32] = detections.xyxy
+ result_boxes: npt.NDArray[np.float32] = np.asarray(
+ detections.xyxy, dtype=np.float32
+ )
return result_boxes
if self._metric_target == MetricTarget.MASKS:
if detections.mask is not None:
- result_masks: npt.NDArray[np.bool_] = detections.mask
+ result_masks: npt.NDArray[np.bool_] = np.asarray(
+ detections.mask, dtype=bool
+ )
return result_masks
return self._make_empty_content()
if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
@@ -407,7 +452,7 @@ def _detections_content(self, detections: Detections) -> npt.NDArray[Any]:
return self._make_empty_content()
raise ValueError(f"Invalid metric target: {self._metric_target}")
- def _make_empty_content(self) -> npt.NDArray[Any]:
+ def _make_empty_content(self) -> npt.NDArray[np.generic]:
if self._metric_target == MetricTarget.BOXES:
empty_boxes: npt.NDArray[np.float32] = np.empty((0, 4), dtype=np.float32)
return empty_boxes
@@ -430,18 +475,30 @@ def _filter_detections_by_size(
sizes = get_detection_size_category(new_detections, self._metric_target)
size_mask = sizes == size_category.value
- new_detections.xyxy = new_detections.xyxy[size_mask]
+ new_detections.xyxy = cast(
+ npt.NDArray[np.number], new_detections.xyxy[size_mask]
+ )
if new_detections.mask is not None:
- new_detections.mask = new_detections.mask[size_mask]
+ new_detections.mask = cast(
+ npt.NDArray[np.bool_], new_detections.mask[size_mask]
+ )
if new_detections.class_id is not None:
- new_detections.class_id = new_detections.class_id[size_mask]
+ new_detections.class_id = cast(
+ npt.NDArray[np.int32], new_detections.class_id[size_mask]
+ )
if new_detections.confidence is not None:
- new_detections.confidence = new_detections.confidence[size_mask]
+ new_detections.confidence = cast(
+ npt.NDArray[np.float32], new_detections.confidence[size_mask]
+ )
if new_detections.tracker_id is not None:
- new_detections.tracker_id = new_detections.tracker_id[size_mask]
+ new_detections.tracker_id = cast(
+ npt.NDArray[np.int32], new_detections.tracker_id[size_mask]
+ )
if new_detections.data is not None:
for key, value in new_detections.data.items():
- new_detections.data[key] = np.array(value)[size_mask]
+ new_detections.data[key] = cast(
+ npt.NDArray[np.generic], np.asarray(value)[size_mask]
+ )
return new_detections
@@ -593,7 +650,7 @@ def to_pandas(self) -> pd.DataFrame:
ensure_pandas_installed()
import pandas as pd
- pandas_data = {
+ pandas_data: dict[str, object] = {
"F1@50": self.f1_50,
"F1@75": self.f1_75,
}
diff --git a/src/supervision/metrics/mean_average_precision.py b/src/supervision/metrics/mean_average_precision.py
index 8d598da9c2..0db410b4a6 100644
--- a/src/supervision/metrics/mean_average_precision.py
+++ b/src/supervision/metrics/mean_average_precision.py
@@ -7,7 +7,7 @@
from copy import deepcopy
from dataclasses import dataclass
from enum import Enum
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, TypeAlias, TypedDict
import numpy as np
import numpy.typing as npt
@@ -26,6 +26,40 @@
import pandas as pd
+class _TypeCocoDict(TypedDict, total=False):
+ id: int
+ image_id: int
+ category_id: int
+ bbox: list[float]
+ area: float
+ iscrowd: int
+ ignore: int
+ _ignore: int
+ score: float
+ segmentation: list[list[float]]
+ name: str
+ supercategory: str
+ caption: str
+ keypoints: list[float]
+
+
+_TypeCocoDataset: TypeAlias = dict[str, list[_TypeCocoDict]]
+
+
+class _TypeEvaluationImageResult(TypedDict):
+ image_id: int
+ category_id: int
+ area_range: list[float] | tuple[float, float]
+ max_det: int
+ dt_ids: list[int]
+ gt_ids: list[int]
+ dtMatches: npt.NDArray[np.int64]
+ gtMatches: npt.NDArray[np.int64]
+ dtScores: list[float]
+ gtIgnore: npt.NDArray[np.int64]
+ dtIgnore: npt.NDArray[np.bool_]
+
+
@dataclass
class MeanAveragePrecisionResult:
"""
@@ -152,7 +186,7 @@ def to_pandas(self) -> pd.DataFrame:
ensure_pandas_installed()
import pandas as pd
- pandas_data = {
+ pandas_data: dict[str, object] = {
"mAP@50:95": self.map50_95,
"mAP@50": self.map50,
"mAP@75": self.map75,
@@ -252,7 +286,7 @@ class EvaluationDataset:
`COCOEvaluator` class.
"""
- def __init__(self, targets: dict[str, Any] | None = None):
+ def __init__(self, targets: _TypeCocoDataset | None = None) -> None:
"""
Constructor of EvaluationDataset object used to evaluate models with
Mean Average Precision.
@@ -263,11 +297,11 @@ def __init__(self, targets: dict[str, Any] | None = None):
"""
# Initialize members
# Initialize members
- self.dataset: dict[str, Any] = dict()
- self.anns: dict[int, Any] = dict()
- self.cats: dict[int, Any] = dict()
- self.imgs: dict[int, Any] = dict()
- self.img_to_anns: dict[int, list[Any]] = defaultdict(list)
+ self.dataset: _TypeCocoDataset = {}
+ self.anns: dict[int, _TypeCocoDict] = {}
+ self.cats: dict[int, _TypeCocoDict] = {}
+ self.imgs: dict[int, _TypeCocoDict] = {}
+ self.img_to_anns: dict[int, list[_TypeCocoDict]] = defaultdict(list)
self.cat_to_imgs: dict[int, list[int]] = defaultdict(list)
if targets is None:
@@ -285,8 +319,11 @@ def create_class_members(self) -> None:
"""
Create index elements for the dataset.
"""
- anns, cats, imgs = {}, {}, {}
- img_to_anns, cat_to_imgs = defaultdict(list), defaultdict(list)
+ anns: dict[int, _TypeCocoDict] = {}
+ cats: dict[int, _TypeCocoDict] = {}
+ imgs: dict[int, _TypeCocoDict] = {}
+ img_to_anns: dict[int, list[_TypeCocoDict]] = defaultdict(list)
+ cat_to_imgs: dict[int, list[int]] = defaultdict(list)
if "annotations" in self.dataset:
for ann in self.dataset["annotations"]:
img_to_anns[ann["image_id"]].append(ann)
@@ -442,7 +479,7 @@ def get_image_ids(
return list(ids_set)
- def get_annotations(self, ids: list[int] | None = None) -> list[dict[str, Any]]:
+ def get_annotations(self, ids: list[int] | None = None) -> list[_TypeCocoDict]:
"""
Get annotations with the specified ids.
@@ -456,7 +493,7 @@ def get_annotations(self, ids: list[int] | None = None) -> list[dict[str, Any]]:
return []
return [self.anns[idx] for idx in ids]
- def load_predictions(self, predictions: list[dict[str, Any]]) -> EvaluationDataset:
+ def load_predictions(self, predictions: list[_TypeCocoDict]) -> EvaluationDataset:
"""
Load prediction result into an EvaluationDataset object.
@@ -468,7 +505,7 @@ def load_predictions(self, predictions: list[dict[str, Any]]) -> EvaluationDatas
"""
# Create an empty EvaluationDataset object for the predictions
predictions_dataset = EvaluationDataset.empty()
- predictions_dataset.dataset["images"] = [img for img in self.dataset["images"]]
+ predictions_dataset.dataset["images"] = list(self.dataset["images"])
if not isinstance(predictions, list):
raise ValueError("results must be a list")
@@ -588,7 +625,7 @@ class COCOEvaluator:
def __init__(
self, coco_targets: EvaluationDataset, coco_predictions: EvaluationDataset
- ):
+ ) -> None:
"""
Constructor of COCOEvaluator object.
@@ -606,18 +643,22 @@ def __init__(
# List of dictionaries containing the evaluation results
# len(eval_imgs) = (categories) * (area_ranges) * (images)
# For COCO 2017: len(eval_images) = 80 * 4 * 5000 = 1600000
- self.eval_imgs: Any = defaultdict(list)
+ self.eval_imgs: list[_TypeEvaluationImageResult | None] = []
# Dictionary of accumulated results
- self.results: dict[str, Any] = {}
+ self.results: dict[str, object] = {}
# Dictionary of targets for evaluation
- self._targets: defaultdict[tuple[int, int], list[Any]] = defaultdict(list)
- self._predictions: defaultdict[tuple[int, int], list[Any]] = defaultdict(list)
+ self._targets: defaultdict[tuple[int, int], list[_TypeCocoDict]] = defaultdict(
+ list
+ )
+ self._predictions: defaultdict[tuple[int, int], list[_TypeCocoDict]] = (
+ defaultdict(list)
+ )
# Parameters for evaluation
self.params = COCOEvaluatorParameters()
# List of results summarization
- self.stats: list[Any] = []
+ self.stats: list[object] = []
# Dictionary of IOUs between all targets and predictions
- self.ious: dict[tuple[int, int], Any] = {}
+ self.ious: dict[tuple[int, int], npt.NDArray[np.float32]] = {}
# Set image and category ids
self.params.img_ids = sorted(self.coco_targets.get_image_ids())
self.params.cat_ids = sorted(self.coco_targets.get_category_ids())
@@ -653,7 +694,7 @@ def _prepare_targets_and_predictions(self) -> None:
self._predictions[dt["image_id"], dt["category_id"]].append(dt)
# Initialize evaluation results
- self.eval_imgs = defaultdict(list)
+ self.eval_imgs = []
self.results = {}
def _compute_iou(self, img_id: int, cat_id: int) -> npt.NDArray[np.float32]:
@@ -703,7 +744,7 @@ def _evaluate_image(
cat_id: int,
area_range: list[float] | tuple[float, float],
max_det: int,
- ) -> dict[str, Any] | None:
+ ) -> _TypeEvaluationImageResult | None:
"""
Perform evaluation for single category and image.
Args:
@@ -716,8 +757,8 @@ def _evaluate_image(
The evaluation results.
"""
# Get targets (gt) and predictions (dt) for the given image and category
- gt: list[dict[str, Any]] = self._targets[img_id, cat_id]
- dt: list[dict[str, Any]] = self._predictions[img_id, cat_id]
+ gt: list[_TypeCocoDict] = self._targets[img_id, cat_id]
+ dt: list[_TypeCocoDict] = self._predictions[img_id, cat_id]
# If there is nothing to evaluate
if len(gt) == 0 and len(dt) == 0:
@@ -754,11 +795,11 @@ def _evaluate_image(
num_detections = len(dt)
# Initialize matches: 0 means no match
- gt_matches = np.zeros((num_thresholds, num_ground_truths))
- dt_matches = np.zeros((num_thresholds, num_detections))
+ gt_matches = np.zeros((num_thresholds, num_ground_truths), dtype=np.int64)
+ dt_matches = np.zeros((num_thresholds, num_detections), dtype=np.int64)
# Initialize ignore flags: 0 means no ignore
- gt_ignore = np.array([g["_ignore"] for g in gt])
- dt_ignore = np.zeros((num_thresholds, num_detections))
+ gt_ignore = np.array([g["_ignore"] for g in gt], dtype=np.int64)
+ dt_ignore = np.zeros((num_thresholds, num_detections), dtype=np.bool_)
if len(ious) != 0:
# Go through the iou thresholds
for tresh_idx, thresh in enumerate(self.params.iou_thrs):
@@ -772,7 +813,7 @@ def _evaluate_image(
for g_idx, g in enumerate(gt):
# If current gt is already matched, and not a crowd, continue
# if gt_matches[tresh_idx, g_idx] > 0 and not iscrowd[g_idx]:
- iscrowd = int(g.get("iscrowd", 0))
+ iscrowd = g["iscrowd"]
if gt_matches[tresh_idx, g_idx] > 0 and not iscrowd:
continue
# Stop searching the ground truths
@@ -901,10 +942,12 @@ def _accumulate(self) -> None:
# Loop through max detections
for max_det_idx, max_det in enumerate(selected_max_detections):
- eval_img_data = [
+ eval_img_data_raw = [
self.eval_imgs[cat_offset + area_offset + i] for i in image_inds
]
- eval_img_data = [e for e in eval_img_data if e is not None]
+ eval_img_data: list[_TypeEvaluationImageResult] = [
+ e for e in eval_img_data_raw if e is not None
+ ]
# No image to evaluate
if len(eval_img_data) == 0:
@@ -1009,23 +1052,23 @@ def _accumulate(self) -> None:
# Helper function to compute average precision while handling -1 sentinel values
def compute_average_precision(
precision_slice: npt.NDArray[np.float32],
- ) -> tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]:
+ ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
"""Compute average precision while handling -1 sentinel values."""
valid_mask = precision_slice != -1
valid_precision = np.where(valid_mask, precision_slice, np.float32(0.0))
def mean_with_mask(
axis: int | tuple[int, ...],
- ) -> npt.NDArray[np.float32]:
+ ) -> npt.NDArray[np.float64]:
sums = valid_precision.sum(axis=axis, dtype=np.float64)
counts = valid_mask.sum(axis=axis)
- means = np.divide(
+ means: npt.NDArray[np.float64] = np.divide(
sums,
counts,
out=np.full(sums.shape, -1.0, dtype=np.float64),
where=counts > 0,
)
- return means.astype(np.float32)
+ return means
mAP_scores = mean_with_mask((1, 2))
ap_per_class = mean_with_mask(1).transpose(1, 0)
@@ -1119,7 +1162,9 @@ def _summarize(
if use_ap:
# Dimension of precision:
# threshold x recall x classes x areas x max detections
- s = self.results["precision"]
+ s: npt.NDArray[np.float32] = np.asarray(
+ self.results["precision"], dtype=np.float32
+ )
# IOU
if iou_thr is not None:
t = np.where(iou_thr == self.params.iou_thrs)[0]
@@ -1128,7 +1173,7 @@ def _summarize(
else:
# Dimension of recall:
# threshold x classes x areas x max detections
- s = self.results["recall"]
+ s = np.asarray(self.results["recall"], dtype=np.float32)
if iou_thr is not None:
t = np.where(iou_thr == self.params.iou_thrs)[0]
s = s[t]
@@ -1223,7 +1268,12 @@ def evaluate(self) -> None:
self._accumulate()
-class MeanAveragePrecision(Metric):
+class MeanAveragePrecision(
+ Metric[
+ [Detections | list[Detections], Detections | list[Detections]],
+ MeanAveragePrecisionResult,
+ ]
+):
"""
Mean Average Precision (mAP) is a metric used to evaluate object detection models.
It is the average of the precision-recall curves at different IoU thresholds.
@@ -1267,7 +1317,7 @@ def __init__(
class_agnostic: bool = False,
class_mapping: dict[int, int] | None = None,
image_indices: list[int] | None = None,
- ):
+ ) -> None:
"""
Initialize the Mean Average Precision metric.
@@ -1336,13 +1386,13 @@ def update(
def _prepare_targets(
self, targets: list[Detections]
- ) -> dict[str, list[dict[str, Any]]]:
+ ) -> dict[str, list[_TypeCocoDict]]:
"""Transform targets into a dictionary that can be used by the COCO evaluator"""
- images = [{"id": img_id} for img_id in range(len(targets))]
+ images: list[_TypeCocoDict] = [{"id": img_id} for img_id in range(len(targets))]
if self._image_indices is not None:
images = [{"id": self._image_indices[img["id"]]} for img in images]
# Annotations list
- annotations: list[dict[str, Any]] = []
+ annotations: list[_TypeCocoDict] = []
for image_id, image_targets in enumerate(targets):
if self._image_indices is not None:
image_id = self._image_indices[image_id]
@@ -1367,16 +1417,22 @@ def _prepare_targets(
# Use area from data if available, otherwise calculate from bbox
area = None
if image_targets.data is not None and "area" in image_targets.data:
- area = float(image_targets.data["area"][target_idx])
+ area_data: npt.NDArray[np.float32] = np.asarray(
+ image_targets.data["area"], dtype=np.float32
+ )
+ area = float(area_data[target_idx])
if area is None:
area = xywh[2] * xywh[3]
iscrowd = 0
if image_targets.data is not None and "iscrowd" in image_targets.data:
- iscrowd = int(image_targets.data["iscrowd"][target_idx])
+ iscrowd_data: npt.NDArray[np.int64] = np.asarray(
+ image_targets.data["iscrowd"], dtype=np.int64
+ )
+ iscrowd = int(iscrowd_data[target_idx])
- dict_annotation = {
+ dict_annotation: _TypeCocoDict = {
"area": area,
"iscrowd": iscrowd,
"image_id": image_id,
@@ -1387,8 +1443,8 @@ def _prepare_targets(
}
annotations.append(dict_annotation)
# Category list
- all_cat_ids = {annotation.get("category_id") for annotation in annotations}
- categories = [{"id": cat_id} for cat_id in all_cat_ids]
+ all_cat_ids = {annotation["category_id"] for annotation in annotations}
+ categories: list[_TypeCocoDict] = [{"id": cat_id} for cat_id in all_cat_ids]
# Create coco dictionary
return {
"images": images,
@@ -1398,10 +1454,10 @@ def _prepare_targets(
def _prepare_predictions(
self, predictions: list[Detections]
- ) -> list[dict[str, Any]]:
+ ) -> list[_TypeCocoDict]:
"""Transform predictions into a list of predictions that can be used by the COCO
evaluator."""
- coco_predictions: list[dict[str, Any]] = []
+ coco_predictions: list[_TypeCocoDict] = []
for image_id, image_predictions in enumerate(predictions):
if self._image_indices is not None:
image_id = self._image_indices[image_id]
@@ -1431,12 +1487,15 @@ def _prepare_predictions(
image_predictions.data is not None
and "area" in image_predictions.data
):
- area = float(image_predictions.data["area"][pred_idx])
+ area_data: npt.NDArray[np.float32] = np.asarray(
+ image_predictions.data["area"], dtype=np.float32
+ )
+ area = float(area_data[pred_idx])
if area is None:
area = xywh[2] * xywh[3]
- dict_prediction = {
+ dict_prediction: _TypeCocoDict = {
"image_id": image_id,
"bbox": xywh,
"score": score,
@@ -1481,38 +1540,54 @@ def compute(self) -> MeanAveragePrecisionResult:
mAP_small = MeanAveragePrecisionResult(
metric_target=self._metric_target,
is_class_agnostic=self._class_agnostic,
- mAP_scores=cocoEval.results["mAP_scores_small"],
- ap_per_class=cocoEval.results["ap_per_class_small"],
- iou_thresholds=cocoEval.params.iou_thrs,
- matched_classes=np.array(cocoEval.params.cat_ids),
+ mAP_scores=np.asarray(
+ cocoEval.results["mAP_scores_small"], dtype=np.float64
+ ),
+ ap_per_class=np.asarray(
+ cocoEval.results["ap_per_class_small"], dtype=np.float64
+ ),
+ iou_thresholds=np.asarray(cocoEval.params.iou_thrs, dtype=np.float64),
+ matched_classes=np.asarray(cocoEval.params.cat_ids, dtype=np.int32),
)
# Create MeanAveragePrecisionResult object for medium objects
mAP_medium = MeanAveragePrecisionResult(
metric_target=self._metric_target,
is_class_agnostic=self._class_agnostic,
- mAP_scores=cocoEval.results["mAP_scores_medium"],
- ap_per_class=cocoEval.results["ap_per_class_medium"],
- iou_thresholds=cocoEval.params.iou_thrs,
- matched_classes=np.array(cocoEval.params.cat_ids),
+ mAP_scores=np.asarray(
+ cocoEval.results["mAP_scores_medium"], dtype=np.float64
+ ),
+ ap_per_class=np.asarray(
+ cocoEval.results["ap_per_class_medium"], dtype=np.float64
+ ),
+ iou_thresholds=np.asarray(cocoEval.params.iou_thrs, dtype=np.float64),
+ matched_classes=np.asarray(cocoEval.params.cat_ids, dtype=np.int32),
)
# Create MeanAveragePrecisionResult object for large objects
mAP_large = MeanAveragePrecisionResult(
metric_target=self._metric_target,
is_class_agnostic=self._class_agnostic,
- mAP_scores=cocoEval.results["mAP_scores_large"],
- ap_per_class=cocoEval.results["ap_per_class_large"],
- iou_thresholds=cocoEval.params.iou_thrs,
- matched_classes=np.array(cocoEval.params.cat_ids),
+ mAP_scores=np.asarray(
+ cocoEval.results["mAP_scores_large"], dtype=np.float64
+ ),
+ ap_per_class=np.asarray(
+ cocoEval.results["ap_per_class_large"], dtype=np.float64
+ ),
+ iou_thresholds=np.asarray(cocoEval.params.iou_thrs, dtype=np.float64),
+ matched_classes=np.asarray(cocoEval.params.cat_ids, dtype=np.int32),
)
# Create the final MeanAveragePrecisionResult object
mAP_result = MeanAveragePrecisionResult(
metric_target=self._metric_target,
is_class_agnostic=self._class_agnostic,
- mAP_scores=cocoEval.results["mAP_scores_all_sizes"],
- ap_per_class=cocoEval.results["ap_per_class_all_sizes"],
- iou_thresholds=cocoEval.params.iou_thrs,
- matched_classes=np.array(cocoEval.params.cat_ids),
+ mAP_scores=np.asarray(
+ cocoEval.results["mAP_scores_all_sizes"], dtype=np.float64
+ ),
+ ap_per_class=np.asarray(
+ cocoEval.results["ap_per_class_all_sizes"], dtype=np.float64
+ ),
+ iou_thresholds=np.asarray(cocoEval.params.iou_thrs, dtype=np.float64),
+ matched_classes=np.asarray(cocoEval.params.cat_ids, dtype=np.int32),
small_objects=mAP_small,
medium_objects=mAP_medium,
large_objects=mAP_large,
diff --git a/src/supervision/metrics/mean_average_recall.py b/src/supervision/metrics/mean_average_recall.py
index 01b80fd5e9..dae7fe0c89 100644
--- a/src/supervision/metrics/mean_average_recall.py
+++ b/src/supervision/metrics/mean_average_recall.py
@@ -2,7 +2,7 @@
from copy import deepcopy
from dataclasses import dataclass
-from typing import TYPE_CHECKING, Any, cast
+from typing import TYPE_CHECKING, cast
import numpy as np
import numpy.typing as npt
@@ -26,6 +26,13 @@
if TYPE_CHECKING:
import pandas as pd
+MeanAverageRecallStats = tuple[
+ npt.NDArray[np.bool_],
+ npt.NDArray[np.int32],
+ npt.NDArray[np.int32],
+ npt.NDArray[np.int32],
+]
+
@dataclass
class MeanAverageRecallResult:
@@ -161,7 +168,7 @@ def to_pandas(self) -> pd.DataFrame:
ensure_pandas_installed()
import pandas as pd
- pandas_data = {
+ pandas_data: dict[str, object] = {
"mAR @ 1": self.mAR_at_1,
"mAR @ 10": self.mAR_at_10,
"mAR @ 100": self.mAR_at_100,
@@ -256,7 +263,12 @@ def plot(self) -> None:
plt.show()
-class MeanAverageRecall(Metric):
+class MeanAverageRecall(
+ Metric[
+ [Detections | list[Detections], Detections | list[Detections]],
+ MeanAverageRecallResult,
+ ]
+):
"""
Mean Average Recall (mAR) measures how well the model detects
and retrieves relevant objects by averaging recall over multiple
@@ -297,7 +309,7 @@ class MeanAverageRecall(Metric):
def __init__(
self,
metric_target: MetricTarget = MetricTarget.BOXES,
- ):
+ ) -> None:
"""
Initialize the Mean Average Recall metric.
@@ -381,32 +393,62 @@ def compute(self) -> MeanAverageRecallResult:
def _compute(
self, predictions_list: list[Detections], targets_list: list[Detections]
) -> MeanAverageRecallResult:
- iou_thresholds = np.linspace(0.5, 0.95, 10)
- stats: list[Any] = []
+ iou_thresholds = np.linspace(0.5, 0.95, 10, dtype=np.float32)
+ stats: list[MeanAverageRecallStats] = []
for predictions, targets in zip(predictions_list, targets_list):
prediction_contents = self._detections_content(predictions)
target_contents = self._detections_content(targets)
if len(targets) > 0:
+ if predictions.class_id is None or targets.class_id is None:
+ raise ValueError(
+ "MeanAverageRecall metric requires `class_id` on both "
+ "predictions and targets."
+ )
if len(predictions) == 0:
stats.append(
(
np.zeros((0, iou_thresholds.size), dtype=bool),
- np.zeros((0,), dtype=int),
- np.zeros((0,), dtype=int),
- targets.class_id,
+ np.zeros((0,), dtype=np.int32),
+ np.zeros((0,), dtype=np.int32),
+ np.asarray(targets.class_id, dtype=np.int32),
)
)
else:
+ if predictions.confidence is None:
+ raise ValueError(
+ "MeanAverageRecall metric requires `confidence` on "
+ "predictions."
+ )
+ prediction_class_ids = np.asarray(
+ predictions.class_id, dtype=np.int32
+ )
+ target_class_ids = np.asarray(targets.class_id, dtype=np.int32)
if self._metric_target == MetricTarget.BOXES:
- iou = box_iou_batch(target_contents, prediction_contents)
+ iou: npt.NDArray[np.float64] = np.asarray(
+ box_iou_batch(
+ np.asarray(target_contents, dtype=np.float32),
+ np.asarray(prediction_contents, dtype=np.float32),
+ ),
+ dtype=np.float64,
+ )
elif self._metric_target == MetricTarget.MASKS:
- iou = mask_iou_batch(target_contents, prediction_contents)
+ iou = np.asarray(
+ mask_iou_batch(
+ np.asarray(target_contents, dtype=bool),
+ np.asarray(prediction_contents, dtype=bool),
+ ),
+ dtype=np.float64,
+ )
elif self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
- iou = oriented_box_iou_batch(
- target_contents, prediction_contents
+ iou = np.asarray(
+ oriented_box_iou_batch(
+ np.asarray(target_contents, dtype=np.float32),
+ np.asarray(prediction_contents, dtype=np.float32),
+ ),
+ dtype=np.float64,
)
else:
raise ValueError(
@@ -414,27 +456,21 @@ def _compute(
)
matches = self._match_detection_batch(
- predictions.class_id
- if predictions.class_id is not None
- else np.array([]),
- targets.class_id
- if targets.class_id is not None
- else np.array([]),
+ prediction_class_ids,
+ target_class_ids,
iou,
iou_thresholds,
)
sorted_indices = np.argsort(
- -cast(npt.NDArray[np.float32], predictions.confidence)
+ -np.asarray(predictions.confidence, dtype=np.float32)
)
stats.append(
(
matches[sorted_indices],
- np.arange(len(predictions)),
- cast(npt.NDArray[np.int32], predictions.class_id)[
- sorted_indices
- ],
- cast(npt.NDArray[np.int32], targets.class_id),
+ np.arange(len(predictions), dtype=np.int32),
+ prediction_class_ids[sorted_indices],
+ target_class_ids,
)
)
@@ -445,7 +481,7 @@ def _compute(
recall_per_class=np.zeros((0, iou_thresholds.shape[0])),
max_detections=self.max_detections,
iou_thresholds=iou_thresholds,
- matched_classes=np.array([], dtype=int),
+ matched_classes=np.array([], dtype=np.int32),
small_objects=None,
medium_objects=None,
large_objects=None,
@@ -480,8 +516,10 @@ def _compute_average_recall_for_classes(
npt.NDArray[np.int32],
]:
unique_classes, class_counts = np.unique(true_class_ids, return_counts=True)
+ unique_classes = np.asarray(unique_classes, dtype=np.int32)
+ class_counts = np.asarray(class_counts, dtype=np.int32)
- recalls_at_k = []
+ recalls_at_k_list: list[npt.NDArray[np.float64]] = []
for max_detections in self.max_detections:
# Shape: PxTh,P,C,C -> CxThx3
confusion_matrix = self._compute_confusion_matrix(
@@ -493,10 +531,10 @@ def _compute_average_recall_for_classes(
# Shape: CxThx3 -> CxTh
recall_per_class = self._compute_recall(confusion_matrix)
- recalls_at_k.append(recall_per_class)
+ recalls_at_k_list.append(recall_per_class)
# Shape: KxCxTh -> KxC
- recalls_at_k = np.array(recalls_at_k)
+ recalls_at_k = np.array(recalls_at_k_list, dtype=np.float64)
average_recall_per_class = np.mean(recalls_at_k, axis=2)
# Shape: KxC -> K
@@ -508,7 +546,7 @@ def _compute_average_recall_for_classes(
def _match_detection_batch(
predictions_classes: npt.NDArray[np.int32],
target_classes: npt.NDArray[np.int32],
- iou: npt.NDArray[np.float32],
+ iou: npt.NDArray[np.floating],
iou_thresholds: npt.NDArray[np.float32],
) -> npt.NDArray[np.bool_]:
num_predictions, num_iou_levels = (
@@ -538,9 +576,9 @@ def _match_detection_batch(
@staticmethod
def _compute_confusion_matrix(
sorted_matches: npt.NDArray[np.bool_],
- sorted_prediction_class_ids: npt.NDArray[np.int32],
- unique_classes: npt.NDArray[np.int32],
- class_counts: npt.NDArray[np.int32],
+ sorted_prediction_class_ids: npt.NDArray[np.integer],
+ unique_classes: npt.NDArray[np.integer],
+ class_counts: npt.NDArray[np.integer],
) -> npt.NDArray[np.float64]:
"""
Compute the confusion matrix for each class and IoU threshold.
@@ -577,13 +615,15 @@ class ids.
num_predictions = is_class.sum()
if num_predictions == 0:
- true_positives = np.zeros(num_thresholds)
- false_positives = np.zeros(num_thresholds)
- false_negatives = np.full(num_thresholds, num_true)
+ true_positives = np.zeros(num_thresholds, dtype=np.float64)
+ false_positives = np.zeros(num_thresholds, dtype=np.float64)
+ false_negatives = np.full(num_thresholds, num_true, dtype=np.float64)
elif num_true == 0:
- true_positives = np.zeros(num_thresholds)
- false_positives = np.full(num_thresholds, num_predictions)
- false_negatives = np.zeros(num_thresholds)
+ true_positives = np.zeros(num_thresholds, dtype=np.float64)
+ false_positives = np.full(
+ num_thresholds, num_predictions, dtype=np.float64
+ )
+ false_negatives = np.zeros(num_thresholds, dtype=np.float64)
else:
limited_matches = sorted_matches[is_class]
true_positives = limited_matches.sum(0)
@@ -626,14 +666,18 @@ def _compute_recall(
result_recall: npt.NDArray[np.float64] = recall
return result_recall
- def _detections_content(self, detections: Detections) -> npt.NDArray[Any]:
+ def _detections_content(self, detections: Detections) -> npt.NDArray[np.generic]:
"""Return boxes, masks or oriented bounding boxes from detections."""
if self._metric_target == MetricTarget.BOXES:
- result_boxes: npt.NDArray[np.float32] = detections.xyxy
+ result_boxes: npt.NDArray[np.float32] = np.asarray(
+ detections.xyxy, dtype=np.float32
+ )
return result_boxes
if self._metric_target == MetricTarget.MASKS:
if detections.mask is not None:
- result_masks: npt.NDArray[np.bool_] = detections.mask
+ result_masks: npt.NDArray[np.bool_] = np.asarray(
+ detections.mask, dtype=bool
+ )
return result_masks
return self._make_empty_content()
if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
@@ -644,7 +688,7 @@ def _detections_content(self, detections: Detections) -> npt.NDArray[Any]:
return self._make_empty_content()
raise ValueError(f"Invalid metric target: {self._metric_target}")
- def _make_empty_content(self) -> npt.NDArray[Any]:
+ def _make_empty_content(self) -> npt.NDArray[np.generic]:
if self._metric_target == MetricTarget.BOXES:
empty_boxes: npt.NDArray[np.float32] = np.empty((0, 4), dtype=np.float32)
return empty_boxes
@@ -670,18 +714,30 @@ def _filter_detections_by_size(
sizes = get_detection_size_category(new_detections, self._metric_target)
size_mask = sizes == size_category.value
- new_detections.xyxy = new_detections.xyxy[size_mask]
+ new_detections.xyxy = cast(
+ npt.NDArray[np.number], new_detections.xyxy[size_mask]
+ )
if new_detections.mask is not None:
- new_detections.mask = new_detections.mask[size_mask]
+ new_detections.mask = cast(
+ npt.NDArray[np.bool_], new_detections.mask[size_mask]
+ )
if new_detections.class_id is not None:
- new_detections.class_id = new_detections.class_id[size_mask]
+ new_detections.class_id = cast(
+ npt.NDArray[np.int32], new_detections.class_id[size_mask]
+ )
if new_detections.confidence is not None:
- new_detections.confidence = new_detections.confidence[size_mask]
+ new_detections.confidence = cast(
+ npt.NDArray[np.float32], new_detections.confidence[size_mask]
+ )
if new_detections.tracker_id is not None:
- new_detections.tracker_id = new_detections.tracker_id[size_mask]
+ new_detections.tracker_id = cast(
+ npt.NDArray[np.int32], new_detections.tracker_id[size_mask]
+ )
if new_detections.data is not None:
for key, value in new_detections.data.items():
- new_detections.data[key] = np.array(value)[size_mask]
+ new_detections.data[key] = cast(
+ npt.NDArray[np.generic], np.array(value)[size_mask]
+ )
return new_detections
diff --git a/src/supervision/metrics/precision.py b/src/supervision/metrics/precision.py
index 223bfdbc3d..bebbd67049 100644
--- a/src/supervision/metrics/precision.py
+++ b/src/supervision/metrics/precision.py
@@ -2,7 +2,7 @@
from copy import deepcopy
from dataclasses import dataclass
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, cast
import numpy as np
import numpy.typing as npt
@@ -26,8 +26,20 @@
if TYPE_CHECKING:
import pandas as pd
+PrecisionStats = tuple[
+ npt.NDArray[np.bool_],
+ npt.NDArray[np.float32],
+ npt.NDArray[np.int32],
+ npt.NDArray[np.int32],
+]
-class Precision(Metric):
+
+class Precision(
+ Metric[
+ [Detections | list[Detections], Detections | list[Detections]],
+ "PrecisionResult",
+ ]
+):
"""
Precision is a metric used to evaluate object detection models. It is the ratio of
true positive detections to the total number of predicted detections. We calculate
@@ -70,7 +82,7 @@ def __init__(
self,
metric_target: MetricTarget = MetricTarget.BOXES,
averaging_method: AveragingMethod = AveragingMethod.WEIGHTED,
- ):
+ ) -> None:
"""
Initialize the Precision metric.
@@ -155,32 +167,61 @@ def compute(self) -> PrecisionResult:
def _compute(
self, predictions_list: list[Detections], targets_list: list[Detections]
) -> PrecisionResult:
- iou_thresholds = np.linspace(0.5, 0.95, 10)
- stats: list[Any] = []
+ iou_thresholds = np.linspace(0.5, 0.95, 10, dtype=np.float32)
+ stats: list[PrecisionStats] = []
for predictions, targets in zip(predictions_list, targets_list):
prediction_contents = self._detections_content(predictions)
target_contents = self._detections_content(targets)
if len(targets) > 0:
+ if predictions.class_id is None or targets.class_id is None:
+ raise ValueError(
+ "Precision metric requires `class_id` on both predictions "
+ "and targets."
+ )
if len(predictions) == 0:
stats.append(
(
np.zeros((0, iou_thresholds.size), dtype=bool),
np.zeros((0,), dtype=np.float32),
- np.zeros((0,), dtype=int),
- targets.class_id,
+ np.zeros((0,), dtype=np.int32),
+ np.asarray(targets.class_id, dtype=np.int32),
)
)
else:
+ if predictions.confidence is None:
+ raise ValueError(
+ "Precision metric requires `confidence` on predictions."
+ )
+ prediction_class_ids = np.asarray(
+ predictions.class_id, dtype=np.int32
+ )
+ target_class_ids = np.asarray(targets.class_id, dtype=np.int32)
if self._metric_target == MetricTarget.BOXES:
- iou = box_iou_batch(target_contents, prediction_contents)
+ iou: npt.NDArray[np.float64] = np.asarray(
+ box_iou_batch(
+ np.asarray(target_contents, dtype=np.float32),
+ np.asarray(prediction_contents, dtype=np.float32),
+ ),
+ dtype=np.float64,
+ )
elif self._metric_target == MetricTarget.MASKS:
- iou = mask_iou_batch(target_contents, prediction_contents)
+ iou = np.asarray(
+ mask_iou_batch(
+ np.asarray(target_contents, dtype=bool),
+ np.asarray(prediction_contents, dtype=bool),
+ ),
+ dtype=np.float64,
+ )
elif self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
- iou = oriented_box_iou_batch(
- target_contents, prediction_contents
+ iou = np.asarray(
+ oriented_box_iou_batch(
+ np.asarray(target_contents, dtype=np.float32),
+ np.asarray(prediction_contents, dtype=np.float32),
+ ),
+ dtype=np.float64,
)
else:
raise ValueError(
@@ -188,21 +229,17 @@ def _compute(
)
matches = self._match_detection_batch(
- predictions.class_id
- if predictions.class_id is not None
- else np.array([]),
- targets.class_id
- if targets.class_id is not None
- else np.array([]),
+ prediction_class_ids,
+ target_class_ids,
iou,
iou_thresholds,
)
stats.append(
(
matches,
- predictions.confidence,
- predictions.class_id,
- targets.class_id,
+ np.asarray(predictions.confidence, dtype=np.float32),
+ prediction_class_ids,
+ target_class_ids,
)
)
@@ -213,7 +250,7 @@ def _compute(
precision_scores=np.zeros(iou_thresholds.shape[0]),
precision_per_class=np.zeros((0, iou_thresholds.shape[0])),
iou_thresholds=iou_thresholds,
- matched_classes=np.array([], dtype=int),
+ matched_classes=np.array([], dtype=np.int32),
small_objects=None,
medium_objects=None,
large_objects=None,
@@ -251,6 +288,8 @@ def _compute_precision_for_classes(
matches = matches[sorted_indices]
prediction_class_ids = prediction_class_ids[sorted_indices]
unique_classes, class_counts = np.unique(true_class_ids, return_counts=True)
+ unique_classes = np.asarray(unique_classes, dtype=np.int32)
+ class_counts = np.asarray(class_counts, dtype=np.int32)
# Shape: PxTh,P,C,C -> CxThx3
confusion_matrix = self._compute_confusion_matrix(
@@ -278,7 +317,7 @@ def _compute_precision_for_classes(
def _match_detection_batch(
predictions_classes: npt.NDArray[np.int32],
target_classes: npt.NDArray[np.int32],
- iou: npt.NDArray[np.float32],
+ iou: npt.NDArray[np.floating],
iou_thresholds: npt.NDArray[np.float32],
) -> npt.NDArray[np.bool_]:
num_predictions, num_iou_levels = (
@@ -308,9 +347,9 @@ def _match_detection_batch(
@staticmethod
def _compute_confusion_matrix(
sorted_matches: npt.NDArray[np.bool_],
- sorted_prediction_class_ids: npt.NDArray[np.int32],
- unique_classes: npt.NDArray[np.int32],
- class_counts: npt.NDArray[np.int32],
+ sorted_prediction_class_ids: npt.NDArray[np.integer],
+ unique_classes: npt.NDArray[np.integer],
+ class_counts: npt.NDArray[np.integer],
) -> npt.NDArray[np.float64]:
"""
Compute the confusion matrix for each class and IoU threshold.
@@ -345,13 +384,15 @@ class ids.
num_predictions = is_class.sum()
if num_predictions == 0:
- true_positives = np.zeros(num_thresholds)
- false_positives = np.zeros(num_thresholds)
- false_negatives = np.full(num_thresholds, num_true)
+ true_positives = np.zeros(num_thresholds, dtype=np.float64)
+ false_positives = np.zeros(num_thresholds, dtype=np.float64)
+ false_negatives = np.full(num_thresholds, num_true, dtype=np.float64)
elif num_true == 0:
- true_positives = np.zeros(num_thresholds)
- false_positives = np.full(num_thresholds, num_predictions)
- false_negatives = np.zeros(num_thresholds)
+ true_positives = np.zeros(num_thresholds, dtype=np.float64)
+ false_positives = np.full(
+ num_thresholds, num_predictions, dtype=np.float64
+ )
+ false_negatives = np.zeros(num_thresholds, dtype=np.float64)
else:
true_positives = sorted_matches[is_class].sum(0)
false_positives = (1 - sorted_matches[is_class]).sum(0)
@@ -395,14 +436,18 @@ def _compute_precision(
result_precision: npt.NDArray[np.float64] = precision
return result_precision
- def _detections_content(self, detections: Detections) -> npt.NDArray[Any]:
+ def _detections_content(self, detections: Detections) -> npt.NDArray[np.generic]:
"""Return boxes, masks or oriented bounding boxes from detections."""
if self._metric_target == MetricTarget.BOXES:
- result_boxes: npt.NDArray[np.float32] = detections.xyxy
+ result_boxes: npt.NDArray[np.float32] = np.asarray(
+ detections.xyxy, dtype=np.float32
+ )
return result_boxes
if self._metric_target == MetricTarget.MASKS:
if detections.mask is not None:
- result_masks: npt.NDArray[np.bool_] = detections.mask
+ result_masks: npt.NDArray[np.bool_] = np.asarray(
+ detections.mask, dtype=bool
+ )
return result_masks
return self._make_empty_content()
if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
@@ -413,7 +458,7 @@ def _detections_content(self, detections: Detections) -> npt.NDArray[Any]:
return self._make_empty_content()
raise ValueError(f"Invalid metric target: {self._metric_target}")
- def _make_empty_content(self) -> npt.NDArray[Any]:
+ def _make_empty_content(self) -> npt.NDArray[np.generic]:
if self._metric_target == MetricTarget.BOXES:
empty_boxes: npt.NDArray[np.float32] = np.empty((0, 4), dtype=np.float32)
return empty_boxes
@@ -441,18 +486,30 @@ def _filter_detections_by_size(
sizes = get_detection_size_category(new_detections, self._metric_target)
size_mask = sizes == size_category.value
- new_detections.xyxy = new_detections.xyxy[size_mask]
+ new_detections.xyxy = cast(
+ npt.NDArray[np.number], new_detections.xyxy[size_mask]
+ )
if new_detections.mask is not None:
- new_detections.mask = new_detections.mask[size_mask]
+ new_detections.mask = cast(
+ npt.NDArray[np.bool_], new_detections.mask[size_mask]
+ )
if new_detections.class_id is not None:
- new_detections.class_id = new_detections.class_id[size_mask]
+ new_detections.class_id = cast(
+ npt.NDArray[np.int32], new_detections.class_id[size_mask]
+ )
if new_detections.confidence is not None:
- new_detections.confidence = new_detections.confidence[size_mask]
+ new_detections.confidence = cast(
+ npt.NDArray[np.float32], new_detections.confidence[size_mask]
+ )
if new_detections.tracker_id is not None:
- new_detections.tracker_id = new_detections.tracker_id[size_mask]
+ new_detections.tracker_id = cast(
+ npt.NDArray[np.int32], new_detections.tracker_id[size_mask]
+ )
if new_detections.data is not None:
for key, value in new_detections.data.items():
- new_detections.data[key] = np.array(value)[size_mask]
+ new_detections.data[key] = cast(
+ npt.NDArray[np.generic], np.asarray(value)[size_mask]
+ )
return new_detections
@@ -608,7 +665,7 @@ def to_pandas(self) -> pd.DataFrame:
ensure_pandas_installed()
import pandas as pd
- pandas_data = {
+ pandas_data: dict[str, object] = {
"P@50": self.precision_at_50,
"P@75": self.precision_at_75,
}
diff --git a/src/supervision/metrics/recall.py b/src/supervision/metrics/recall.py
index 2d428cbbf5..409e5c4d4e 100644
--- a/src/supervision/metrics/recall.py
+++ b/src/supervision/metrics/recall.py
@@ -2,7 +2,7 @@
from copy import deepcopy
from dataclasses import dataclass
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, cast
import numpy as np
import numpy.typing as npt
@@ -26,8 +26,20 @@
if TYPE_CHECKING:
import pandas as pd
+RecallStats = tuple[
+ npt.NDArray[np.bool_],
+ npt.NDArray[np.float32],
+ npt.NDArray[np.int32],
+ npt.NDArray[np.int32],
+]
-class Recall(Metric):
+
+class Recall(
+ Metric[
+ [Detections | list[Detections], Detections | list[Detections]],
+ "RecallResult",
+ ]
+):
"""
Recall is a metric used to evaluate object detection models. It is the ratio of
true positive detections to the total number of ground truth instances. We calculate
@@ -70,7 +82,7 @@ def __init__(
self,
metric_target: MetricTarget = MetricTarget.BOXES,
averaging_method: AveragingMethod = AveragingMethod.WEIGHTED,
- ):
+ ) -> None:
"""
Initialize the Recall metric.
@@ -155,32 +167,61 @@ def compute(self) -> RecallResult:
def _compute(
self, predictions_list: list[Detections], targets_list: list[Detections]
) -> RecallResult:
- iou_thresholds = np.linspace(0.5, 0.95, 10)
- stats: list[Any] = []
+ iou_thresholds = np.linspace(0.5, 0.95, 10, dtype=np.float32)
+ stats: list[RecallStats] = []
for predictions, targets in zip(predictions_list, targets_list):
prediction_contents = self._detections_content(predictions)
target_contents = self._detections_content(targets)
if len(targets) > 0:
+ if predictions.class_id is None or targets.class_id is None:
+ raise ValueError(
+ "Recall metric requires `class_id` on both predictions "
+ "and targets."
+ )
if len(predictions) == 0:
stats.append(
(
np.zeros((0, iou_thresholds.size), dtype=bool),
np.zeros((0,), dtype=np.float32),
- np.zeros((0,), dtype=int),
- targets.class_id,
+ np.zeros((0,), dtype=np.int32),
+ np.asarray(targets.class_id, dtype=np.int32),
)
)
else:
+ if predictions.confidence is None:
+ raise ValueError(
+ "Recall metric requires `confidence` on predictions."
+ )
+ prediction_class_ids = np.asarray(
+ predictions.class_id, dtype=np.int32
+ )
+ target_class_ids = np.asarray(targets.class_id, dtype=np.int32)
if self._metric_target == MetricTarget.BOXES:
- iou = box_iou_batch(target_contents, prediction_contents)
+ iou: npt.NDArray[np.float64] = np.asarray(
+ box_iou_batch(
+ np.asarray(target_contents, dtype=np.float32),
+ np.asarray(prediction_contents, dtype=np.float32),
+ ),
+ dtype=np.float64,
+ )
elif self._metric_target == MetricTarget.MASKS:
- iou = mask_iou_batch(target_contents, prediction_contents)
+ iou = np.asarray(
+ mask_iou_batch(
+ np.asarray(target_contents, dtype=bool),
+ np.asarray(prediction_contents, dtype=bool),
+ ),
+ dtype=np.float64,
+ )
elif self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
- iou = oriented_box_iou_batch(
- target_contents, prediction_contents
+ iou = np.asarray(
+ oriented_box_iou_batch(
+ np.asarray(target_contents, dtype=np.float32),
+ np.asarray(prediction_contents, dtype=np.float32),
+ ),
+ dtype=np.float64,
)
else:
raise ValueError(
@@ -188,21 +229,17 @@ def _compute(
)
matches = self._match_detection_batch(
- predictions.class_id
- if predictions.class_id is not None
- else np.array([]),
- targets.class_id
- if targets.class_id is not None
- else np.array([]),
+ prediction_class_ids,
+ target_class_ids,
iou,
iou_thresholds,
)
stats.append(
(
matches,
- predictions.confidence,
- predictions.class_id,
- targets.class_id,
+ np.asarray(predictions.confidence, dtype=np.float32),
+ prediction_class_ids,
+ target_class_ids,
)
)
@@ -213,7 +250,7 @@ def _compute(
recall_scores=np.zeros(iou_thresholds.shape[0]),
recall_per_class=np.zeros((0, iou_thresholds.shape[0])),
iou_thresholds=iou_thresholds,
- matched_classes=np.array([], dtype=int),
+ matched_classes=np.array([], dtype=np.int32),
small_objects=None,
medium_objects=None,
large_objects=None,
@@ -251,6 +288,8 @@ def _compute_recall_for_classes(
matches = matches[sorted_indices]
prediction_class_ids = prediction_class_ids[sorted_indices]
unique_classes, class_counts = np.unique(true_class_ids, return_counts=True)
+ unique_classes = np.asarray(unique_classes, dtype=np.int32)
+ class_counts = np.asarray(class_counts, dtype=np.int32)
# Shape: PxTh,P,C,C -> CxThx3
confusion_matrix = self._compute_confusion_matrix(
@@ -276,7 +315,7 @@ def _compute_recall_for_classes(
def _match_detection_batch(
predictions_classes: npt.NDArray[np.int32],
target_classes: npt.NDArray[np.int32],
- iou: npt.NDArray[np.float32],
+ iou: npt.NDArray[np.floating],
iou_thresholds: npt.NDArray[np.float32],
) -> npt.NDArray[np.bool_]:
num_predictions, num_iou_levels = (
@@ -306,9 +345,9 @@ def _match_detection_batch(
@staticmethod
def _compute_confusion_matrix(
sorted_matches: npt.NDArray[np.bool_],
- sorted_prediction_class_ids: npt.NDArray[np.int32],
- unique_classes: npt.NDArray[np.int32],
- class_counts: npt.NDArray[np.int32],
+ sorted_prediction_class_ids: npt.NDArray[np.integer],
+ unique_classes: npt.NDArray[np.integer],
+ class_counts: npt.NDArray[np.integer],
) -> npt.NDArray[np.float64]:
"""
Compute the confusion matrix for each class and IoU threshold.
@@ -343,13 +382,15 @@ class ids.
num_predictions = is_class.sum()
if num_predictions == 0:
- true_positives = np.zeros(num_thresholds)
- false_positives = np.zeros(num_thresholds)
- false_negatives = np.full(num_thresholds, num_true)
+ true_positives = np.zeros(num_thresholds, dtype=np.float64)
+ false_positives = np.zeros(num_thresholds, dtype=np.float64)
+ false_negatives = np.full(num_thresholds, num_true, dtype=np.float64)
elif num_true == 0:
- true_positives = np.zeros(num_thresholds)
- false_positives = np.full(num_thresholds, num_predictions)
- false_negatives = np.zeros(num_thresholds)
+ true_positives = np.zeros(num_thresholds, dtype=np.float64)
+ false_positives = np.full(
+ num_thresholds, num_predictions, dtype=np.float64
+ )
+ false_negatives = np.zeros(num_thresholds, dtype=np.float64)
else:
true_positives = sorted_matches[is_class].sum(0)
false_positives = (1 - sorted_matches[is_class]).sum(0)
@@ -393,14 +434,18 @@ def _compute_recall(
result_recall: npt.NDArray[np.float64] = recall
return result_recall
- def _detections_content(self, detections: Detections) -> npt.NDArray[Any]:
+ def _detections_content(self, detections: Detections) -> npt.NDArray[np.generic]:
"""Return boxes, masks or oriented bounding boxes from detections."""
if self._metric_target == MetricTarget.BOXES:
- result_boxes: npt.NDArray[np.float32] = detections.xyxy
+ result_boxes: npt.NDArray[np.float32] = np.asarray(
+ detections.xyxy, dtype=np.float32
+ )
return result_boxes
if self._metric_target == MetricTarget.MASKS:
if detections.mask is not None:
- result_masks: npt.NDArray[np.bool_] = detections.mask
+ result_masks: npt.NDArray[np.bool_] = np.asarray(
+ detections.mask, dtype=bool
+ )
return result_masks
return self._make_empty_content()
if self._metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
@@ -411,7 +456,7 @@ def _detections_content(self, detections: Detections) -> npt.NDArray[Any]:
return self._make_empty_content()
raise ValueError(f"Invalid metric target: {self._metric_target}")
- def _make_empty_content(self) -> npt.NDArray[Any]:
+ def _make_empty_content(self) -> npt.NDArray[np.generic]:
if self._metric_target == MetricTarget.BOXES:
empty_boxes: npt.NDArray[np.float32] = np.empty((0, 4), dtype=np.float32)
return empty_boxes
@@ -439,18 +484,30 @@ def _filter_detections_by_size(
sizes = get_detection_size_category(new_detections, self._metric_target)
size_mask = sizes == size_category.value
- new_detections.xyxy = new_detections.xyxy[size_mask]
+ new_detections.xyxy = cast(
+ npt.NDArray[np.number], new_detections.xyxy[size_mask]
+ )
if new_detections.mask is not None:
- new_detections.mask = new_detections.mask[size_mask]
+ new_detections.mask = cast(
+ npt.NDArray[np.bool_], new_detections.mask[size_mask]
+ )
if new_detections.class_id is not None:
- new_detections.class_id = new_detections.class_id[size_mask]
+ new_detections.class_id = cast(
+ npt.NDArray[np.int32], new_detections.class_id[size_mask]
+ )
if new_detections.confidence is not None:
- new_detections.confidence = new_detections.confidence[size_mask]
+ new_detections.confidence = cast(
+ npt.NDArray[np.float32], new_detections.confidence[size_mask]
+ )
if new_detections.tracker_id is not None:
- new_detections.tracker_id = new_detections.tracker_id[size_mask]
+ new_detections.tracker_id = cast(
+ npt.NDArray[np.int32], new_detections.tracker_id[size_mask]
+ )
if new_detections.data is not None:
for key, value in new_detections.data.items():
- new_detections.data[key] = np.array(value)[size_mask]
+ new_detections.data[key] = cast(
+ npt.NDArray[np.generic], np.asarray(value)[size_mask]
+ )
return new_detections
@@ -604,7 +661,7 @@ def to_pandas(self) -> pd.DataFrame:
ensure_pandas_installed()
import pandas as pd
- pandas_data = {
+ pandas_data: dict[str, object] = {
"R@50": self.recall_at_50,
"R@75": self.recall_at_75,
}
diff --git a/src/supervision/metrics/utils/object_size.py b/src/supervision/metrics/utils/object_size.py
index 03f005a112..131dd5e41f 100644
--- a/src/supervision/metrics/utils/object_size.py
+++ b/src/supervision/metrics/utils/object_size.py
@@ -1,7 +1,7 @@
from __future__ import annotations
from enum import Enum
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, cast
import numpy as np
import numpy.typing as npt
@@ -44,7 +44,8 @@ class ObjectSizeCategory(Enum):
def get_object_size_category(
- data: npt.NDArray, metric_target: MetricTarget
+ data: npt.NDArray[np.number] | npt.NDArray[np.bool_],
+ metric_target: MetricTarget,
) -> npt.NDArray[np.int_]:
"""
Get the size category of an object. Distinguish based on the metric target.
@@ -74,15 +75,18 @@ def get_object_size_category(
```
"""
if metric_target == MetricTarget.BOXES:
- return get_bbox_size_category(data)
+ bbox_data = cast(npt.NDArray[np.number], data)
+ return get_bbox_size_category(bbox_data)
if metric_target == MetricTarget.MASKS:
- return get_mask_size_category(data)
+ mask_data = cast(npt.NDArray[np.bool_], data)
+ return get_mask_size_category(mask_data)
if metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
- return get_obb_size_category(data)
+ obb_data = cast(npt.NDArray[np.number], data)
+ return get_obb_size_category(obb_data)
raise ValueError("Invalid metric type")
-def get_bbox_size_category(xyxy: npt.NDArray[np.float32]) -> npt.NDArray[np.int_]:
+def get_bbox_size_category(xyxy: npt.NDArray[np.number]) -> npt.NDArray[np.int_]:
"""
Get the size category of a bounding boxes array.
@@ -165,7 +169,7 @@ def get_mask_size_category(
return result
-def get_obb_size_category(xyxyxyxy: npt.NDArray[np.float32]) -> npt.NDArray[np.int_]:
+def get_obb_size_category(xyxyxyxy: npt.NDArray[np.number]) -> npt.NDArray[np.int_]:
"""
Get the size category of a oriented bounding boxes array.
@@ -229,13 +233,18 @@ def get_detection_size_category(
if metric_target == MetricTarget.BOXES:
return get_bbox_size_category(detections.xyxy)
if metric_target == MetricTarget.MASKS:
- if detections.mask is None:
+ mask = detections.mask
+ if mask is None:
raise ValueError("Detections mask is not available")
- return get_mask_size_category(detections.mask)
+ return get_mask_size_category(mask)
if metric_target == MetricTarget.ORIENTED_BOUNDING_BOXES:
- if detections.data.get(ORIENTED_BOX_COORDINATES) is None:
+ oriented_box_coordinates = detections.data.get(ORIENTED_BOX_COORDINATES)
+ if oriented_box_coordinates is None:
raise ValueError("Detections oriented bounding boxes are not available")
return get_obb_size_category(
- np.array(detections.data[ORIENTED_BOX_COORDINATES])
+ cast(
+ npt.NDArray[np.number],
+ np.asarray(oriented_box_coordinates, dtype=np.float32),
+ )
)
raise ValueError("Invalid metric type")
diff --git a/src/supervision/tracker/byte_tracker/core.py b/src/supervision/tracker/byte_tracker/core.py
index 3b84c2af77..66dfbb1051 100644
--- a/src/supervision/tracker/byte_tracker/core.py
+++ b/src/supervision/tracker/byte_tracker/core.py
@@ -60,7 +60,7 @@ def __init__(
minimum_matching_threshold: float = 0.8,
frame_rate: float = 30,
minimum_consecutive_frames: int = 1,
- ):
+ ) -> None:
self.track_activation_threshold = track_activation_threshold
self.minimum_matching_threshold = minimum_matching_threshold
diff --git a/src/supervision/tracker/byte_tracker/kalman_filter.py b/src/supervision/tracker/byte_tracker/kalman_filter.py
index fbaf779c41..45b713b4cd 100644
--- a/src/supervision/tracker/byte_tracker/kalman_filter.py
+++ b/src/supervision/tracker/byte_tracker/kalman_filter.py
@@ -146,10 +146,10 @@ def multi_predict(
]
sqr = np.square(np.r_[std_pos, std_vel]).T
- motion_cov = []
+ motion_cov_list: list[npt.NDArray[np.float32]] = []
for i in range(len(mean)):
- motion_cov.append(np.diag(sqr[i]))
- motion_cov = np.asarray(motion_cov)
+ motion_cov_list.append(np.diag(sqr[i]))
+ motion_cov = np.asarray(motion_cov_list)
mean = np.dot(mean, self._motion_mat.T)
left = np.dot(self._motion_mat, covariance).transpose((1, 0, 2))
diff --git a/src/supervision/tracker/byte_tracker/matching.py b/src/supervision/tracker/byte_tracker/matching.py
index 9d2c7dc690..729376926f 100644
--- a/src/supervision/tracker/byte_tracker/matching.py
+++ b/src/supervision/tracker/byte_tracker/matching.py
@@ -1,15 +1,13 @@
from __future__ import annotations
-from typing import TYPE_CHECKING
+from typing import cast
import numpy as np
import numpy.typing as npt
from scipy.optimize import linear_sum_assignment
from supervision.detection.utils.iou_and_nms import box_iou_batch
-
-if TYPE_CHECKING:
- from supervision.tracker.byte_tracker.single_object_track import STrack
+from supervision.tracker.byte_tracker.single_object_track import STrack
def indices_to_matches(
@@ -48,17 +46,23 @@ def iou_distance(
if (len(atracks) > 0 and isinstance(atracks[0], np.ndarray)) or (
len(btracks) > 0 and isinstance(btracks[0], np.ndarray)
):
- atlbrs = atracks
- btlbrs = btracks
+ atlbrs = cast(list[npt.NDArray[np.float32]], atracks)
+ btlbrs = cast(list[npt.NDArray[np.float32]], btracks)
else:
- atlbrs = [track.tlbr for track in atracks]
- btlbrs = [track.tlbr for track in btracks]
+ atlbrs = [track.tlbr for track in cast(list[STrack], atracks)]
+ btlbrs = [track.tlbr for track in cast(list[STrack], btracks)]
- _ious = np.zeros((len(atlbrs), len(btlbrs)), dtype=np.float32)
- if _ious.size != 0:
- _ious = box_iou_batch(np.asarray(atlbrs), np.asarray(btlbrs))
- cost_matrix = 1 - _ious
+ if len(atlbrs) == 0 or len(btlbrs) == 0:
+ return cast(
+ npt.NDArray[np.float32],
+ np.empty((len(atlbrs), len(btlbrs)), dtype=np.float32),
+ )
+ ious = box_iou_batch(
+ np.asarray(atlbrs, dtype=np.float32),
+ np.asarray(btlbrs, dtype=np.float32),
+ )
+ cost_matrix = np.asarray(1 - ious, dtype=np.float32)
return cost_matrix
@@ -68,8 +72,8 @@ def fuse_score(
if cost_matrix.size == 0:
return cost_matrix
iou_sim = 1 - cost_matrix
- det_scores = np.array([strack.score for strack in stracks])
+ det_scores = np.array([strack.score for strack in stracks], dtype=np.float32)
det_scores = np.expand_dims(det_scores, axis=0).repeat(cost_matrix.shape[0], axis=0)
fuse_sim = iou_sim * det_scores
- fuse_cost = 1 - fuse_sim
+ fuse_cost = np.asarray(1 - fuse_sim, dtype=np.float32)
return fuse_cost
diff --git a/src/supervision/tracker/byte_tracker/single_object_track.py b/src/supervision/tracker/byte_tracker/single_object_track.py
index 26f6bb6831..084e2d257f 100644
--- a/src/supervision/tracker/byte_tracker/single_object_track.py
+++ b/src/supervision/tracker/byte_tracker/single_object_track.py
@@ -1,6 +1,7 @@
from __future__ import annotations
from enum import Enum
+from typing import cast
import numpy as np
import numpy.typing as npt
@@ -25,7 +26,7 @@ def __init__(
shared_kalman: KalmanFilter,
internal_id_counter: IdCounter,
external_id_counter: IdCounter,
- ):
+ ) -> None:
self.state = TrackState.New
self.is_activated = False
self.start_frame = 0
@@ -62,8 +63,8 @@ def predict(self) -> None:
@staticmethod
def multi_predict(stracks: list[STrack], shared_kalman: KalmanFilter) -> None:
if len(stracks) > 0:
- multi_mean = []
- multi_covariance = []
+ multi_mean: list[npt.NDArray[np.float32]] = []
+ multi_covariance: list[npt.NDArray[np.float32]] = []
for i, st in enumerate(stracks):
assert st.mean is not None
assert st.covariance is not None
@@ -72,10 +73,10 @@ def multi_predict(stracks: list[STrack], shared_kalman: KalmanFilter) -> None:
if st.state != TrackState.Tracked:
multi_mean[i][7] = 0
- multi_mean, multi_covariance = shared_kalman.multi_predict(
+ multi_mean_arr, multi_covariance_arr = shared_kalman.multi_predict(
np.asarray(multi_mean), np.asarray(multi_covariance)
)
- for i, (mean, cov) in enumerate(zip(multi_mean, multi_covariance)):
+ for i, (mean, cov) in enumerate(zip(multi_mean_arr, multi_covariance_arr)):
stracks[i].mean = mean
stracks[i].covariance = cov
@@ -142,11 +143,14 @@ def tlwh(self) -> npt.NDArray[np.float32]:
width, height)`.
"""
if self.mean is None:
- return self._tlwh.copy()
+ return cast(
+ npt.NDArray[np.float32],
+ np.asarray(self._tlwh.copy(), dtype=np.float32),
+ )
ret = self.mean[:4].copy()
ret[2] *= ret[3]
ret[:2] -= ret[2:] / 2
- return ret
+ return cast(npt.NDArray[np.float32], np.asarray(ret, dtype=np.float32))
@property
def tlbr(self) -> npt.NDArray[np.float32]:
@@ -155,7 +159,7 @@ def tlbr(self) -> npt.NDArray[np.float32]:
"""
ret = self.tlwh.copy()
ret[2:] += ret[:2]
- return ret
+ return cast(npt.NDArray[np.float32], np.asarray(ret, dtype=np.float32))
@staticmethod
def tlwh_to_xyah(tlwh: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]:
@@ -165,7 +169,7 @@ def tlwh_to_xyah(tlwh: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]:
ret = np.asarray(tlwh).copy()
ret[:2] += ret[2:] / 2
ret[2] /= ret[3]
- return ret
+ return cast(npt.NDArray[np.float32], np.asarray(ret, dtype=np.float32))
def to_xyah(self) -> npt.NDArray[np.float32]:
return self.tlwh_to_xyah(self.tlwh)
@@ -174,13 +178,13 @@ def to_xyah(self) -> npt.NDArray[np.float32]:
def tlbr_to_tlwh(tlbr: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]:
ret = np.asarray(tlbr).copy()
ret[2:] -= ret[:2]
- return ret
+ return cast(npt.NDArray[np.float32], np.asarray(ret, dtype=np.float32))
@staticmethod
def tlwh_to_tlbr(tlwh: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]:
ret = np.asarray(tlwh).copy()
ret[2:] += ret[:2]
- return ret
+ return cast(npt.NDArray[np.float32], np.asarray(ret, dtype=np.float32))
def __repr__(self) -> str:
return f"OT_{self.internal_track_id}_({self.start_frame}-{self.frame_id})"
diff --git a/src/supervision/tracker/byte_tracker/utils.py b/src/supervision/tracker/byte_tracker/utils.py
index 3404f8090e..f7d33738f5 100644
--- a/src/supervision/tracker/byte_tracker/utils.py
+++ b/src/supervision/tracker/byte_tracker/utils.py
@@ -2,7 +2,7 @@
class IdCounter:
- def __init__(self, start_id: int = 0):
+ def __init__(self, start_id: int = 0) -> None:
"""
Initialize the ID counter.
diff --git a/src/supervision/utils/conversion.py b/src/supervision/utils/conversion.py
index 00a57818bf..1e46061cd0 100644
--- a/src/supervision/utils/conversion.py
+++ b/src/supervision/utils/conversion.py
@@ -1,6 +1,6 @@
import functools
from collections.abc import Callable
-from typing import Any, TypeVar, cast
+from typing import TypeVar, cast
import cv2
import numpy as np
@@ -10,7 +10,7 @@
from supervision.draw.base import ImageType
-F = TypeVar("F", bound=Callable[..., Any])
+F = TypeVar("F", bound=Callable[..., object])
def ensure_cv2_image_for_class_method(
@@ -25,13 +25,25 @@ def ensure_cv2_image_for_class_method(
"""
@functools.wraps(annotate_func)
- def wrapper(self: Any, scene: ImageType, *args: Any, **kwargs: Any) -> ImageType:
+ def wrapper(
+ self: object,
+ scene: ImageType,
+ *args: object,
+ **kwargs: object,
+ ) -> object:
if isinstance(scene, np.ndarray):
- return annotate_func(self, scene, *args, **kwargs)
+ annotated_np = cast(
+ npt.NDArray[np.uint8],
+ annotate_func(self, scene, *args, **kwargs),
+ )
+ return annotated_np
if isinstance(scene, Image.Image):
scene_np = pillow_to_cv2(scene)
- annotated_np = annotate_func(self, scene_np, *args, **kwargs)
+ annotated_np = cast(
+ npt.NDArray[np.uint8],
+ annotate_func(self, scene_np, *args, **kwargs),
+ )
scene.paste(cv2_to_pillow(annotated_np))
return scene
@@ -62,13 +74,20 @@ def ensure_cv2_image_for_standalone_function(
"""
@functools.wraps(image_processing_fun)
- def wrapper(image: ImageType, *args: Any, **kwargs: Any) -> ImageType:
+ def wrapper(image: ImageType, *args: object, **kwargs: object) -> object:
if isinstance(image, np.ndarray):
- return image_processing_fun(image, *args, **kwargs)
+ annotated_np = cast(
+ npt.NDArray[np.uint8],
+ image_processing_fun(image, *args, **kwargs),
+ )
+ return annotated_np
if isinstance(image, Image.Image):
scene = pillow_to_cv2(image)
- annotated = image_processing_fun(scene, *args, **kwargs)
+ annotated = cast(
+ npt.NDArray[np.uint8],
+ image_processing_fun(scene, *args, **kwargs),
+ )
return cv2_to_pillow(annotated)
raise ValueError(f"Unsupported image type: {type(image)}")
@@ -87,15 +106,22 @@ def ensure_pil_image_for_class_method(
"""
@functools.wraps(annotate_func)
- def wrapper(self: Any, scene: ImageType, *args: Any, **kwargs: Any) -> ImageType:
+ def wrapper(
+ self: object,
+ scene: ImageType,
+ *args: object,
+ **kwargs: object,
+ ) -> object:
if isinstance(scene, np.ndarray):
scene_pil = cv2_to_pillow(scene)
- annotated_pil = annotate_func(self, scene_pil, *args, **kwargs)
+ annotated_pil = cast(
+ Image.Image, annotate_func(self, scene_pil, *args, **kwargs)
+ )
np.copyto(scene, pillow_to_cv2(annotated_pil))
return scene
if isinstance(scene, Image.Image):
- return cast(ImageType, annotate_func(self, scene, *args, **kwargs))
+ return cast(Image.Image, annotate_func(self, scene, *args, **kwargs))
raise ValueError(f"Unsupported image type: {type(scene)}")
@@ -137,11 +163,12 @@ def images_to_cv2(images: list[ImageType]) -> list[npt.NDArray[np.uint8]]:
(with order preserved).
"""
- result = []
+ result: list[npt.NDArray[np.uint8]] = []
for image in images:
- if issubclass(type(image), Image.Image):
- image = pillow_to_cv2(image)
- result.append(image)
+ if isinstance(image, Image.Image):
+ result.append(pillow_to_cv2(image))
+ else:
+ result.append(image)
return result
@@ -156,9 +183,9 @@ def pillow_to_cv2(image: Image.Image) -> npt.NDArray[np.uint8]:
Returns:
Input image converted to OpenCV format.
"""
- scene = np.array(image)
- scene = cv2.cvtColor(scene, cv2.COLOR_RGB2BGR)
- return scene.astype(np.uint8)
+ scene: npt.NDArray[np.uint8] = cast(npt.NDArray[np.uint8], np.array(image))
+ scene = cast(npt.NDArray[np.uint8], cv2.cvtColor(scene, cv2.COLOR_RGB2BGR))
+ return cast(npt.NDArray[np.uint8], scene.astype(np.uint8))
def cv2_to_pillow(image: npt.NDArray[np.uint8]) -> Image.Image:
@@ -172,5 +199,5 @@ def cv2_to_pillow(image: npt.NDArray[np.uint8]) -> Image.Image:
Returns:
Input image converted to Pillow format.
"""
- image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
- return Image.fromarray(image)
+ converted = cast(npt.NDArray[np.uint8], cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
+ return Image.fromarray(converted)
diff --git a/src/supervision/utils/image.py b/src/supervision/utils/image.py
index f8010d5a7e..c142f40844 100644
--- a/src/supervision/utils/image.py
+++ b/src/supervision/utils/image.py
@@ -6,7 +6,8 @@
import shutil
from collections.abc import Callable
from functools import partial
-from typing import Any, Literal, cast
+from types import TracebackType
+from typing import Literal, cast
import cv2
import numpy as np
@@ -33,7 +34,7 @@
@ensure_cv2_image_for_standalone_function
def crop_image(
image: ImageType,
- xyxy: npt.NDArray[int] | list[int] | tuple[int, int, int, int],
+ xyxy: npt.NDArray[np.number] | list[int] | tuple[int, int, int, int],
) -> ImageType:
"""
Crop image based on bounding box coordinates.
@@ -74,17 +75,14 @@ def crop_image(
{ align=center width="1000" }
""" # noqa E501 // docs
- if isinstance(xyxy, (list, tuple)):
- xyxy = np.array(xyxy)
-
- xyxy = np.round(xyxy).astype(int)
- x_min, y_min, x_max, y_max = xyxy.flatten()
+ xyxy_arr = np.asarray(xyxy, dtype=np.float64).round().astype(np.int32)
+ x_min, y_min, x_max, y_max = xyxy_arr.flatten()
if isinstance(image, np.ndarray):
return image[y_min:y_max, x_min:x_max]
if isinstance(image, Image.Image):
- return image.crop((x_min, y_min, x_max, y_max))
+ return image.crop((float(x_min), float(y_min), float(x_max), float(y_max)))
raise TypeError(
f"`image` must be a numpy.ndarray or PIL.Image.Image. Received {type(image)}"
@@ -139,7 +137,10 @@ def scale_image(image: ImageType, scale_factor: float) -> ImageType:
width_old, height_old = image.shape[1], image.shape[0]
width_new = int(width_old * scale_factor)
height_new = int(height_old * scale_factor)
- return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR)
+ return cast(
+ npt.NDArray[np.uint8],
+ cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR),
+ )
@ensure_cv2_image_for_standalone_function
@@ -203,7 +204,10 @@ def resize_image(
else:
width_new, height_new = resolution_wh
- return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR)
+ return cast(
+ npt.NDArray[np.uint8],
+ cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR),
+ )
@ensure_cv2_image_for_standalone_function
@@ -253,14 +257,17 @@ def letterbox_image(
padding_bottom = resolution_wh[1] - height_new - padding_top
padding_left = (resolution_wh[0] - width_new) // 2
padding_right = resolution_wh[0] - width_new - padding_left
- image_with_borders = cv2.copyMakeBorder(
- resized_image,
- padding_top,
- padding_bottom,
- padding_left,
- padding_right,
- cv2.BORDER_CONSTANT,
- value=color,
+ image_with_borders = cast(
+ npt.NDArray[np.uint8],
+ cv2.copyMakeBorder(
+ resized_image,
+ padding_top,
+ padding_bottom,
+ padding_left,
+ padding_right,
+ cv2.BORDER_CONSTANT,
+ value=color,
+ ),
)
if image.shape[2] == 4:
@@ -335,12 +342,12 @@ def overlay_image(
b, g, r, alpha = cv2.split(
overlay[crop_y_min:crop_y_max, crop_x_min:crop_x_max]
)
- alpha = alpha[:, :, None] / 255.0
- overlay_color = cv2.merge((b, g, r))
+ alpha_f32 = alpha[:, :, None].astype(np.float32) / 255.0
+ overlay_color = cv2.merge((b, g, r)).astype(np.float32)
- roi = image[y_min:y_max, x_min:x_max]
- roi[:] = roi * (1 - alpha) + overlay_color * alpha
- image[y_min:y_max, x_min:x_max] = roi
+ roi = image[y_min:y_max, x_min:x_max].astype(np.float32)
+ blended = roi * (1 - alpha_f32) + overlay_color * alpha_f32
+ image[y_min:y_max, x_min:x_max] = np.clip(blended, 0, 255).astype(np.uint8)
else:
image[y_min:y_max, x_min:x_max] = overlay[
crop_y_min:crop_y_max, crop_x_min:crop_x_max
@@ -424,8 +431,9 @@ def grayscale_image(image: ImageType) -> ImageType:
{ align=center width="1000" }
""" # noqa E501 // docs
- grayscaled = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
- return cv2.cvtColor(grayscaled, cv2.COLOR_GRAY2BGR)
+ assert isinstance(image, np.ndarray)
+ grayscaled = cast(npt.NDArray[np.uint8], cv2.cvtColor(image, cv2.COLOR_BGR2GRAY))
+ return cast(npt.NDArray[np.uint8], cv2.cvtColor(grayscaled, cv2.COLOR_GRAY2BGR))
def get_image_resolution_wh(image: ImageType) -> tuple[int, int]:
@@ -481,7 +489,7 @@ def __init__(
target_dir_path: str,
overwrite: bool = False,
image_name_pattern: str = "image_{:05d}.png",
- ):
+ ) -> None:
"""
Initialize context manager for saving images to directory.
@@ -549,7 +557,7 @@ def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
- exc_traceback: Any,
+ exc_traceback: TracebackType | None,
) -> None:
pass
@@ -653,32 +661,32 @@ def create_tiles(
return_type = _negotiate_tiles_format(images=images)
tile_padding_color = unify_to_bgr(color=tile_padding_color)
tile_margin_color = unify_to_bgr(color=tile_margin_color)
- images = images_to_cv2(images=images)
+ images_cv2 = images_to_cv2(images=images)
if single_tile_size is None:
- single_tile_size = _aggregate_images_shape(images=images, mode=tile_scaling)
+ single_tile_size = _aggregate_images_shape(images=images_cv2, mode=tile_scaling)
resized_images = [
letterbox_image(
image=i, resolution_wh=single_tile_size, color=tile_padding_color
)
- for i in images
+ for i in images_cv2
]
- grid_size = _establish_grid_size(images=images, grid_size=grid_size)
- if len(images) > grid_size[0] * grid_size[1]:
+ grid_size = _establish_grid_size(images=images_cv2, grid_size=grid_size)
+ if len(images_cv2) > grid_size[0] * grid_size[1]:
raise ValueError(
- f"Could not place {len(images)} in grid with size: {grid_size}."
+ f"Could not place {len(images_cv2)} in grid with size: {grid_size}."
)
if titles is not None:
- titles = fill(sequence=titles, desired_size=len(images), content=None)
+ titles = fill(sequence=titles, desired_size=len(images_cv2), content=None)
if isinstance(titles_anchors, list):
titles_anchors_sequence = titles_anchors
else:
titles_anchors_sequence = [titles_anchors]
titles_anchors = fill(
- sequence=titles_anchors_sequence, desired_size=len(images), content=None
+ sequence=titles_anchors_sequence, desired_size=len(images_cv2), content=None
)
titles_color = unify_to_bgr(color=titles_color)
titles_background_color = unify_to_bgr(color=titles_background_color)
- tiles = _generate_tiles(
+ tiles_image = _generate_tiles(
images=resized_images,
grid_size=grid_size,
single_tile_size=single_tile_size,
@@ -696,8 +704,10 @@ def create_tiles(
default_title_placement=default_title_placement,
)
if return_type == "pillow":
- tiles = cv2_to_pillow(image=tiles)
- return cast(ImageType, tiles)
+ tiles_image_pillow: object = cv2_to_pillow(image=tiles_image)
+ return cast(ImageType, tiles_image_pillow)
+ tiles_image_cv2: object = tiles_image
+ return cast(ImageType, tiles_image_cv2)
def _negotiate_tiles_format(images: list[ImageType]) -> Literal["cv2", "pillow"]:
@@ -880,9 +890,10 @@ def _merge_tiles_elements(
tile_margin: int,
tile_margin_color: tuple[int, int, int],
) -> npt.NDArray[np.uint8]:
- vertical_padding: npt.NDArray[np.uint8] = (
- np.ones((single_tile_size[1], tile_margin, 3), dtype=np.uint8)
- * tile_margin_color
+ vertical_padding: npt.NDArray[np.uint8] = np.full(
+ (single_tile_size[1], tile_margin, 3),
+ tile_margin_color,
+ dtype=np.uint8,
)
merged_rows = [
np.concatenate(
@@ -896,26 +907,19 @@ def _merge_tiles_elements(
for row in tiles_elements
]
row_width = merged_rows[0].shape[1]
- horizontal_padding = (
- np.ones((tile_margin, row_width, 3), dtype=np.uint8) * tile_margin_color
+ horizontal_padding: npt.NDArray[np.uint8] = np.full(
+ (tile_margin, row_width, 3),
+ tile_margin_color,
+ dtype=np.uint8,
)
- rows_with_paddings = []
+ rows_with_paddings: list[npt.NDArray[np.uint8]] = []
for row in merged_rows:
rows_with_paddings.append(row)
rows_with_paddings.append(horizontal_padding)
- return cast(
- npt.NDArray[np.uint8],
- np.concatenate(
- rows_with_paddings[:-1],
- axis=0,
- ).astype(np.uint8),
- )
+ return np.concatenate(rows_with_paddings[:-1], axis=0).astype(np.uint8, copy=False)
def _generate_color_image(
shape: tuple[int, int], color: tuple[int, int, int]
) -> npt.NDArray[np.uint8]:
- return cast(
- npt.NDArray[np.uint8],
- np.ones((*shape[::-1], 3), dtype=np.uint8) * color,
- )
+ return np.full((*shape[::-1], 3), color, dtype=np.uint8)
diff --git a/src/supervision/utils/internal.py b/src/supervision/utils/internal.py
index 2ceddb476b..59c7bd2eaf 100644
--- a/src/supervision/utils/internal.py
+++ b/src/supervision/utils/internal.py
@@ -142,7 +142,7 @@ def my_method(cls):
...
"""
- def __init__(self, fget: Callable[..., T]):
+ def __init__(self, fget: Callable[..., T]) -> None:
"""
Args:
The function that is called when the property is accessed.
diff --git a/src/supervision/utils/notebook.py b/src/supervision/utils/notebook.py
index ed8f4f68f7..f95dd64935 100644
--- a/src/supervision/utils/notebook.py
+++ b/src/supervision/utils/notebook.py
@@ -2,6 +2,8 @@
import cv2
import matplotlib.pyplot as plt
+import numpy as np
+import numpy.typing as npt
from PIL import Image
from supervision.draw.base import ImageType
@@ -34,14 +36,16 @@ def plot_image(
```
"""
if isinstance(image, Image.Image):
- image = pillow_to_cv2(image)
+ image_np = pillow_to_cv2(image)
+ else:
+ image_np = image
plt.figure(figsize=size)
- if image.ndim == 2:
- plt.imshow(image, cmap=cmap)
+ if image_np.ndim == 2:
+ plt.imshow(image_np, cmap=cmap)
else:
- plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
+ plt.imshow(cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB))
plt.axis("off")
plt.show()
@@ -91,11 +95,11 @@ def plot_images_grid(
"""
nrows, ncols = grid_size
- for idx, img in enumerate(images):
- if isinstance(img, Image.Image):
- images[idx] = pillow_to_cv2(img)
+ images_np: list[npt.NDArray[np.uint8]] = [
+ pillow_to_cv2(img) if isinstance(img, Image.Image) else img for img in images
+ ]
- if len(images) > nrows * ncols:
+ if len(images_np) > nrows * ncols:
raise ValueError(
"The number of images exceeds the grid size. Please increase the grid size"
" or reduce the number of images."
@@ -104,11 +108,11 @@ def plot_images_grid(
_fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=size)
for idx, ax in enumerate(axes.flat):
- if idx < len(images):
- if images[idx].ndim == 2:
- ax.imshow(images[idx], cmap=cmap)
+ if idx < len(images_np):
+ if images_np[idx].ndim == 2:
+ ax.imshow(images_np[idx], cmap=cmap)
else:
- ax.imshow(cv2.cvtColor(images[idx], cv2.COLOR_BGR2RGB))
+ ax.imshow(cv2.cvtColor(images_np[idx], cv2.COLOR_BGR2RGB))
if titles is not None and idx < len(titles):
ax.set_title(titles[idx])
diff --git a/src/supervision/utils/video.py b/src/supervision/utils/video.py
index 1d3fe2a464..51a2e16d26 100644
--- a/src/supervision/utils/video.py
+++ b/src/supervision/utils/video.py
@@ -10,7 +10,8 @@
from collections.abc import Callable, Generator
from dataclasses import dataclass
from queue import Empty, Full, Queue
-from typing import Any
+from types import TracebackType
+from typing import cast
import cv2
import numpy as np
@@ -96,18 +97,24 @@ class VideoSink:
```
""" # noqa: E501 // docs
- def __init__(self, target_path: str, video_info: VideoInfo, codec: str = "mp4v"):
+ def __init__(
+ self, target_path: str, video_info: VideoInfo, codec: str = "mp4v"
+ ) -> None:
self.target_path = target_path
self.video_info = video_info
self.__codec = codec
- self.__writer = None
+ self.__fourcc: int = 0
+ self.__writer: cv2.VideoWriter | None = None
def __enter__(self) -> VideoSink:
+ fourcc_fn = cast(
+ Callable[[str, str, str, str], int], getattr(cv2, "VideoWriter_fourcc")
+ )
try:
- self.__fourcc = cv2.VideoWriter_fourcc(*self.__codec)
+ self.__fourcc = int(fourcc_fn(*self.__codec))
except TypeError as e:
logger.warning("%s. Defaulting to mp4v...", str(e))
- self.__fourcc = cv2.VideoWriter_fourcc(*"mp4v")
+ self.__fourcc = int(fourcc_fn(*"mp4v"))
self.__writer = cv2.VideoWriter(
self.target_path,
self.__fourcc,
@@ -131,7 +138,7 @@ def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
- exc_traceback: Any,
+ exc_traceback: TracebackType | None,
) -> None:
if self.__writer is not None:
self.__writer.release()
@@ -271,7 +278,7 @@ def get_video_frames_generator(
if not success or frame_position >= end:
break
if frame is not None:
- yield frame
+ yield cast(npt.NDArray[np.uint8], frame)
for _ in range(stride - 1):
success = video.grab()
if not success:
@@ -461,7 +468,7 @@ class FPSMonitor:
A class for monitoring frames per second (FPS) to benchmark latency.
"""
- def __init__(self, sample_size: int = 30):
+ def __init__(self, sample_size: int = 30) -> None:
"""
Args:
sample_size: The maximum number of observations for latency
diff --git a/tests/assets/test_downloader.py b/tests/assets/test_downloader.py
index 344d78dba5..ca0c677d6e 100644
--- a/tests/assets/test_downloader.py
+++ b/tests/assets/test_downloader.py
@@ -7,7 +7,7 @@
class TestMD5HashMatching:
- def test_file_exists_matching_hash(self):
+ def test_file_exists_matching_hash(self) -> None:
"""Test is_md5_hash_matching when file exists and hash matches."""
test_content = b"test content"
test_hash = "9473fdd0d880a43c21b7778d34872157" # MD5 of "test content"
@@ -18,7 +18,7 @@ def test_file_exists_matching_hash(self):
):
assert is_md5_hash_matching("dummy_file", test_hash)
- def test_file_exists_not_matching_hash(self):
+ def test_file_exists_not_matching_hash(self) -> None:
"""Test is_md5_hash_matching when file exists but hash doesn't match."""
test_content = b"test content"
wrong_hash = "wrong_hash"
@@ -29,7 +29,7 @@ def test_file_exists_not_matching_hash(self):
):
assert not is_md5_hash_matching("dummy_file", wrong_hash)
- def test_file_not_exists(self):
+ def test_file_not_exists(self) -> None:
"""Test is_md5_hash_matching when file doesn't exist."""
with patch("os.path.exists", return_value=False):
assert not is_md5_hash_matching("nonexistent_file", "some_hash")
@@ -39,7 +39,7 @@ class TestDownloadAssets:
@patch("supervision.assets.downloader.logger")
@patch("supervision.assets.downloader.is_md5_hash_matching", return_value=True)
@patch("pathlib.Path.exists", return_value=True)
- def test_already_exists_and_valid(self, mock_exists, mock_md5, mock_logger):
+ def test_already_exists_and_valid(self, mock_exists, mock_md5, mock_logger) -> None:
"""Test download_assets when file already exists and is valid."""
filename = "vehicles.mp4"
result = download_assets(filename)
@@ -55,7 +55,7 @@ def test_already_exists_and_valid(self, mock_exists, mock_md5, mock_logger):
@patch("pathlib.Path.exists", return_value=True)
def test_already_exists_but_corrupted(
self, mock_exists, mock_md5, mock_remove, mock_logger
- ):
+ ) -> None:
"""Test download_assets when file exists but is corrupted (re-downloads)."""
filename = "vehicles.mp4"
result = download_assets(filename)
@@ -79,7 +79,7 @@ def test_download_new_file(
mock_mkdir,
mock_open_file,
mock_logger,
- ):
+ ) -> None:
"""Test download_assets downloading a new file."""
filename = "vehicles.mp4"
@@ -102,7 +102,7 @@ def test_download_new_file(
mock_copyfileobj.assert_called_once()
@patch("pathlib.Path.exists", return_value=False)
- def test_invalid_asset(self, mock_exists):
+ def test_invalid_asset(self, mock_exists) -> None:
"""Test download_assets with invalid asset name."""
invalid_filename = "invalid.mp4"
@@ -113,7 +113,7 @@ def test_invalid_asset(self, mock_exists):
assert "vehicles.mp4" in str(exc_info.value)
@patch("pathlib.Path.exists", return_value=True)
- def test_invalid_asset_when_file_exists(self, mock_exists):
+ def test_invalid_asset_when_file_exists(self, mock_exists) -> None:
"""Test download_assets with invalid asset name that already exists."""
invalid_filename = "invalid.mp4"
@@ -139,7 +139,7 @@ def test_with_video_enum(
mock_mkdir,
mock_open_file,
mock_logger,
- ):
+ ) -> None:
"""Test download_assets with VideoAssets enum."""
asset = VideoAssets.VEHICLES
@@ -172,7 +172,7 @@ def test_with_image_enum(
mock_mkdir,
mock_open_file,
mock_logger,
- ):
+ ) -> None:
"""Test download_assets with ImageAssets enum."""
asset = ImageAssets.SOCCER
diff --git a/tests/assets/test_list.py b/tests/assets/test_list.py
index 0447af287a..db143ed210 100644
--- a/tests/assets/test_list.py
+++ b/tests/assets/test_list.py
@@ -7,7 +7,7 @@
)
-def test_video_assets_list():
+def test_video_assets_list() -> None:
"""Test that VideoAssets.list() returns all video filenames."""
expected_filenames = [
"vehicles.mp4",
@@ -24,7 +24,7 @@ def test_video_assets_list():
assert VideoAssets.list() == expected_filenames
-def test_image_assets_list():
+def test_image_assets_list() -> None:
"""Test that ImageAssets.list() returns all image filenames."""
expected_filenames = [
"people-walking.jpg",
@@ -33,21 +33,21 @@ def test_image_assets_list():
assert ImageAssets.list() == expected_filenames
-def test_video_assets_values():
+def test_video_assets_values() -> None:
"""Test that VideoAssets enum members have correct attributes."""
assert VideoAssets.VEHICLES.filename == "vehicles.mp4"
assert VideoAssets.VEHICLES.md5_hash == "8155ff4e4de08cfa25f39de96483f918"
assert VideoAssets.VEHICLES.value == "vehicles.mp4"
-def test_image_assets_values():
+def test_image_assets_values() -> None:
"""Test that ImageAssets enum members have correct attributes."""
assert ImageAssets.SOCCER.filename == "soccer.jpg"
assert ImageAssets.SOCCER.md5_hash == "0f5a4b98abf3e3973faf9e9260a7d876"
assert ImageAssets.SOCCER.value == "soccer.jpg"
-def test_media_assets_dict_keys():
+def test_media_assets_dict_keys() -> None:
"""Test that MEDIA_ASSETS has all VideoAssets and ImageAssets as keys."""
expected_keys = {asset.filename for asset in VideoAssets} | {
asset.filename for asset in ImageAssets
@@ -55,7 +55,7 @@ def test_media_assets_dict_keys():
assert set(MEDIA_ASSETS.keys()) == expected_keys
-def test_media_assets_dict_values():
+def test_media_assets_dict_values() -> None:
"""Test that MEDIA_ASSETS values are tuples of (url, md5_hash)."""
for filename, (url, md5_hash) in MEDIA_ASSETS.items():
assert isinstance(url, str)
diff --git a/tests/dataset/formats/test_coco.py b/tests/dataset/formats/test_coco.py
index d7420054df..14cc855695 100644
--- a/tests/dataset/formats/test_coco.py
+++ b/tests/dataset/formats/test_coco.py
@@ -12,6 +12,7 @@
build_coco_class_index_mapping,
classes_to_coco_categories,
coco_annotations_to_detections,
+ coco_annotations_to_masks,
coco_categories_to_classes,
detections_to_coco_annotations,
group_coco_annotations_by_image_id,
@@ -1159,6 +1160,31 @@ def test_load_coco_annotations_force_masks_with_no_annotations(
assert annotations[no_annotations_path] == Detections.empty()
+def test_coco_annotations_to_masks_handles_rle_polygon_and_invalid_dict() -> None:
+ image_annotations = [
+ {"id": 1, "segmentation": {"size": [5, 5], "counts": [15, 2, 3, 2, 3]}},
+ {"id": 2, "segmentation": [[0, 0, 4, 0, 4, 4, 0, 4]]},
+ {"id": 3, "segmentation": {"size": [5, 5]}},
+ ]
+
+ with pytest.warns(
+ UserWarning,
+ match=(
+ "Skipping annotation 3: segmentation is a dict but missing 'counts' key "
+ r"\(expected RLE format\)"
+ ),
+ ):
+ masks = coco_annotations_to_masks(
+ image_annotations=image_annotations,
+ resolution_wh=(5, 5),
+ )
+
+ assert masks.shape == (3, 5, 5)
+ assert masks[0].any()
+ assert masks[1].any()
+ assert not masks[2].any()
+
+
@pytest.mark.parametrize(
"file_name",
[".", "", "subdir/.."],
diff --git a/tests/dataset/formats/test_pascal_voc.py b/tests/dataset/formats/test_pascal_voc.py
index aca08b82e8..1f4a8260e1 100644
--- a/tests/dataset/formats/test_pascal_voc.py
+++ b/tests/dataset/formats/test_pascal_voc.py
@@ -14,7 +14,7 @@
from tests.helpers import _create_detections
-def are_xml_elements_equal(elem1, elem2):
+def are_xml_elements_equal(elem1, elem2) -> bool:
if (
elem1.tag != elem2.tag
or elem1.attrib != elem2.attrib
@@ -66,7 +66,7 @@ def test_object_to_pascal_voc(
polygon: np.ndarray | None,
expected_result,
exception: Exception,
-):
+) -> None:
with exception:
result = object_to_pascal_voc(xyxy=xyxy, name=name, polygon=polygon)
assert are_xml_elements_equal(result, expected_result)
@@ -90,7 +90,7 @@ def test_parse_polygon_points(
polygon_element,
expected_result: list[list],
exception,
-):
+) -> None:
with exception:
result = parse_polygon_points(polygon_element)
assert np.array_equal(result, expected_result)
@@ -179,7 +179,7 @@ def test_parse_polygon_points(
)
def test_detections_from_xml_obj(
xml_string, classes, resolution_wh, force_masks, expected_result, exception
-):
+) -> None:
with exception:
root = ElementTree.fromstring(xml_string)
result, _ = detections_from_xml_obj(root, classes, resolution_wh, force_masks)
diff --git a/tests/detection/test_core.py b/tests/detection/test_core.py
index 94434c6f76..54eaac6311 100644
--- a/tests/detection/test_core.py
+++ b/tests/detection/test_core.py
@@ -874,7 +874,7 @@ def test_merge_inner_detection_object_pair(
detection_2: Detections,
expected_result: Detections | None,
exception: Exception,
-):
+) -> None:
with exception:
result = merge_inner_detection_object_pair(detection_1, detection_2)
assert result == expected_result
diff --git a/tests/detection/test_csv.py b/tests/detection/test_csv.py
index be83766606..9137dfdfae 100644
--- a/tests/detection/test_csv.py
+++ b/tests/detection/test_csv.py
@@ -515,7 +515,7 @@ def test_csv_sink_manual(
assert_csv_equal(file_name, expected_result)
-def assert_csv_equal(file_name, expected_rows):
+def assert_csv_equal(file_name, expected_rows) -> None:
with open(file_name, newline="") as file:
reader = csv.reader(file)
for i, row in enumerate(reader):
diff --git a/tests/detection/test_json.py b/tests/detection/test_json.py
index 3da34865fc..b6f0ffea2b 100644
--- a/tests/detection/test_json.py
+++ b/tests/detection/test_json.py
@@ -395,7 +395,7 @@ def test_json_sink(
assert_json_equal(file_name, expected_result)
-def assert_json_equal(file_name, expected_rows):
+def assert_json_equal(file_name, expected_rows) -> None:
with open(file_name) as file:
data = json.load(file)
assert data == expected_rows, (
diff --git a/tests/detection/test_polygonzone.py b/tests/detection/test_polygonzone.py
index 6e952ec7d9..45a2a98ec5 100644
--- a/tests/detection/test_polygonzone.py
+++ b/tests/detection/test_polygonzone.py
@@ -40,7 +40,7 @@ class TestPolygonZoneInit:
),
],
)
- def test_empty_anchors_raises(self, polygon, triggering_anchors, exception):
+ def test_empty_anchors_raises(self, polygon, triggering_anchors, exception) -> None:
with exception:
sv.PolygonZone(polygon, triggering_anchors=triggering_anchors)
diff --git a/tests/detection/test_vlm.py b/tests/detection/test_vlm.py
index b5d035b065..82a6cc2444 100644
--- a/tests/detection/test_vlm.py
+++ b/tests/detection/test_vlm.py
@@ -1153,7 +1153,7 @@ def test_from_google_gemini_2_5(
classes: list[str] | None,
expected_results: None
| (tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]),
-):
+) -> None:
with exception:
(
xyxy,
@@ -1274,7 +1274,7 @@ def test_from_deepseek_vl_2(
resolution_wh: tuple[int, int],
classes: list[str] | None,
expected_detections: Detections,
-):
+) -> None:
with exception:
detections = Detections.from_vlm(
vlm=VLM.DEEPSEEK_VL_2,
diff --git a/tests/detection/tools/test_smoother.py b/tests/detection/tools/test_smoother.py
new file mode 100644
index 0000000000..6c9b6e01de
--- /dev/null
+++ b/tests/detection/tools/test_smoother.py
@@ -0,0 +1,76 @@
+from __future__ import annotations
+
+import numpy as np
+
+from supervision.detection.core import Detections
+from supervision.detection.tools.smoother import DetectionsSmoother
+
+
+def test_smoother_confidence_averaged_when_all_frames_have_confidence() -> None:
+ smoother = DetectionsSmoother()
+
+ first = Detections(
+ xyxy=np.array([[0, 0, 10, 10]], dtype=float),
+ confidence=np.array([0.2], dtype=float),
+ tracker_id=np.array([1]),
+ )
+ second = Detections(
+ xyxy=np.array([[2, 2, 12, 12]], dtype=float),
+ confidence=np.array([0.8], dtype=float),
+ tracker_id=np.array([1]),
+ )
+
+ smoother.update_with_detections(first)
+ smoothed = smoother.update_with_detections(second)
+
+ assert smoothed.confidence is not None
+ np.testing.assert_allclose(smoothed.confidence, np.array([0.5], dtype=float))
+
+
+def test_detections_smoother_confidence_is_none_if_any_frame_missing() -> None:
+ smoother = DetectionsSmoother()
+
+ with_confidence = Detections(
+ xyxy=np.array([[0, 0, 10, 10]], dtype=float),
+ confidence=np.array([0.2], dtype=float),
+ tracker_id=np.array([1]),
+ )
+ without_confidence = Detections(
+ xyxy=np.array([[2, 2, 12, 12]], dtype=float),
+ confidence=None,
+ tracker_id=np.array([1]),
+ )
+
+ smoother.update_with_detections(with_confidence)
+ smoothed = smoother.update_with_detections(without_confidence)
+
+ assert smoothed.confidence is None
+
+
+def test_detections_smoother_averages_xyxy_over_two_frames() -> None:
+ smoother = DetectionsSmoother()
+
+ first = Detections(
+ xyxy=np.array([[0, 0, 10, 10]], dtype=float),
+ confidence=np.array([0.8], dtype=float),
+ tracker_id=np.array([1]),
+ )
+ second = Detections(
+ xyxy=np.array([[2, 2, 12, 12]], dtype=float),
+ confidence=np.array([0.8], dtype=float),
+ tracker_id=np.array([1]),
+ )
+
+ smoother.update_with_detections(first)
+ smoothed = smoother.update_with_detections(second)
+
+ expected_xyxy = np.array([[1, 1, 11, 11]], dtype=float)
+ np.testing.assert_allclose(smoothed.xyxy, expected_xyxy)
+
+
+def test_detections_smoother_empty_detections_does_not_raise() -> None:
+ smoother = DetectionsSmoother()
+
+ smoothed = smoother.update_with_detections(Detections.empty())
+
+ assert len(smoothed) == 0
diff --git a/tests/detection/utils/test_internal.py b/tests/detection/utils/test_internal.py
index c5bc08dd85..2f5e354325 100644
--- a/tests/detection/utils/test_internal.py
+++ b/tests/detection/utils/test_internal.py
@@ -671,7 +671,7 @@ def test_merge_data(
data_list: list[dict[str, Any]],
expected_result: dict[str, Any] | None,
exception: Exception,
-):
+) -> None:
with exception:
result = merge_data(data_list=data_list)
if expected_result is None:
@@ -847,7 +847,7 @@ def test_get_data_item(
index: Any,
expected_result: dict[str, Any] | None,
exception: Exception,
-):
+) -> None:
with exception:
result = get_data_item(data=data, index=index)
for key in result:
@@ -1024,7 +1024,7 @@ def test_get_data_item(
),
],
)
-def test_merge_metadata(metadata_list, expected_result, exception):
+def test_merge_metadata(metadata_list, expected_result, exception) -> None:
with exception:
result = merge_metadata(metadata_list)
if expected_result is None:
diff --git a/tests/detection/utils/test_vlms.py b/tests/detection/utils/test_vlms.py
index 805c9681d6..4eb314afee 100644
--- a/tests/detection/utils/test_vlms.py
+++ b/tests/detection/utils/test_vlms.py
@@ -60,7 +60,7 @@
("123ABC!", "123abc!", False, 0),
],
)
-def test_edit_distance(string_1, string_2, case_sensitive, expected_result):
+def test_edit_distance(string_1, string_2, case_sensitive, expected_result) -> None:
assert (
edit_distance(string_1, string_2, case_sensitive=case_sensitive)
== expected_result
@@ -105,7 +105,7 @@ def test_edit_distance(string_1, string_2, case_sensitive, expected_result):
)
def test_fuzzy_match_index(
candidates, query, threshold, case_sensitive, expected_result
-):
+) -> None:
assert (
fuzzy_match_index(
candidates=candidates,
diff --git a/tests/draw/test_utils.py b/tests/draw/test_utils.py
new file mode 100644
index 0000000000..b62862088e
--- /dev/null
+++ b/tests/draw/test_utils.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+import cv2
+import numpy as np
+import pytest
+
+from supervision.draw.utils import draw_image
+from supervision.geometry.core import Rect
+
+
+def test_draw_image_invalid_path_raises_oserror(tmp_path) -> None:
+ """
+ Verify that drawing from an invalid image file raises OSError.
+ """
+
+ invalid_image_path = tmp_path / "invalid_image.dat"
+ invalid_image_path.write_bytes(b"not an image")
+ scene = np.zeros((100, 100, 3), dtype=np.uint8)
+ rect = Rect(x=0, y=0, width=100, height=100)
+
+ with pytest.raises(OSError, match="Could not decode image path"):
+ draw_image(
+ scene=scene,
+ image=str(invalid_image_path),
+ opacity=1.0,
+ rect=rect,
+ )
+
+
+def test_draw_image_valid_image(tmp_path) -> None:
+ """
+ Verify that drawing a valid image from disk returns an ndarray.
+ """
+ image = np.zeros((100, 100, 3), dtype=np.uint8)
+ image_path = tmp_path / "image.png"
+ cv2.imwrite(str(image_path), image)
+
+ scene = np.zeros((100, 100, 3), dtype=np.uint8)
+ rect = Rect(x=0, y=0, width=100, height=100)
+
+ result = draw_image(
+ scene=scene,
+ image=str(image_path),
+ opacity=1.0,
+ rect=rect,
+ )
+
+ assert isinstance(result, np.ndarray)
diff --git a/tests/helpers.py b/tests/helpers.py
index 1ce209b39c..f6841c0301 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -185,7 +185,7 @@ def _generate_random_boxes(
return out
-def assert_almost_equal(actual, expected, tolerance=1e-5):
+def assert_almost_equal(actual, expected, tolerance=1e-5) -> None:
"""
Assert that two values are equal within a specified tolerance.
@@ -239,7 +239,7 @@ def assert_image_mostly_same(
class _FakeTensor:
"""Minimal tensor wrapper for cpu().numpy() and int()."""
- def __init__(self, arr: np.ndarray):
+ def __init__(self, arr: np.ndarray) -> None:
self._arr = np.asarray(arr)
def cpu(self) -> _FakeTensor:
@@ -255,7 +255,7 @@ def int(self) -> _FakeTensor:
class _FakeYOLOv5Results:
"""YOLOv5-like results exposing pred list."""
- def __init__(self, pred0: np.ndarray):
+ def __init__(self, pred0: np.ndarray) -> None:
self.pred = [_FakeTensor(pred0)]
@@ -268,7 +268,7 @@ def __init__(
conf: np.ndarray,
cls: np.ndarray,
id_: np.ndarray | None = None,
- ):
+ ) -> None:
self.xyxy = _FakeTensor(xyxy)
self.conf = _FakeTensor(conf)
self.cls = _FakeTensor(cls)
@@ -278,7 +278,7 @@ def __init__(
class _FakeUltralyticsResults:
"""Ultralytics-like results container used by from_ultralytics."""
- def __init__(self, boxes, names: dict[int, str], length: int = 0):
+ def __init__(self, boxes, names: dict[int, str], length: int = 0) -> None:
self.boxes = boxes
self.names = names
self.obb = None
@@ -292,7 +292,7 @@ def __len__(self) -> int:
class _FakeYoloNasPrediction:
"""YOLO-NAS-like prediction struct."""
- def __init__(self, bboxes_xyxy, confidence, labels):
+ def __init__(self, bboxes_xyxy, confidence, labels) -> None:
self.bboxes_xyxy = bboxes_xyxy
self.confidence = confidence
self.labels = labels
@@ -301,14 +301,14 @@ def __init__(self, bboxes_xyxy, confidence, labels):
class _FakeYoloNasResults:
"""YOLO-NAS-like results exposing prediction."""
- def __init__(self, prediction: _FakeYoloNasPrediction):
+ def __init__(self, prediction: _FakeYoloNasPrediction) -> None:
self.prediction = prediction
class _FakeYoloNasKeyPoint:
"""YOLO-NAS-like key point struct."""
- def __init__(self, poses, labels=None):
+ def __init__(self, poses, labels=None) -> None:
self.poses = np.array(poses, dtype=np.float32)
if labels is not None:
self.labels = np.array(labels, dtype=int)
@@ -317,20 +317,20 @@ def __init__(self, poses, labels=None):
class _FakeYoloNasKeyPointResults:
"""YOLO-NAS-like results exposing key points."""
- def __init__(self, prediction: _FakeYoloNasKeyPoint, class_names=None):
+ def __init__(self, prediction: _FakeYoloNasKeyPoint, class_names=None) -> None:
self.prediction = prediction
self.class_names = class_names
class _FakeMediapipeLandmark:
- def __init__(self, x, y, visibility=1.0):
+ def __init__(self, x, y, visibility=1.0) -> None:
self.x = x
self.y = y
self.visibility = visibility
class _FakeMediapipePose:
- def __init__(self, landmarks: list[_FakeMediapipeLandmark]):
+ def __init__(self, landmarks: list[_FakeMediapipeLandmark]) -> None:
self.landmark = landmarks
@@ -342,7 +342,7 @@ def __init__(
| None = None,
face_landmarks: _FakeMediapipeLandmark | None = None,
multi_face_landmarks: list[_FakeMediapipeLandmark] | None = None,
- ):
+ ) -> None:
self.pose_landmarks = pose_landmarks
self.face_landmarks = face_landmarks
self.multi_face_landmarks = multi_face_landmarks
diff --git a/tests/key_points/test_annotators.py b/tests/key_points/test_annotators.py
index 3ecc40ce64..62e576c04b 100644
--- a/tests/key_points/test_annotators.py
+++ b/tests/key_points/test_annotators.py
@@ -12,7 +12,7 @@ class TestVertexAnnotator:
which is essential for human pose estimation or similar tasks.
"""
- def test_annotate_with_default_parameters(self, scene, sample_key_points):
+ def test_annotate_with_default_parameters(self, scene, sample_key_points) -> None:
"""
Verify that VertexAnnotator correctly draws keypoints with default parameters.
@@ -27,7 +27,9 @@ def test_annotate_with_default_parameters(self, scene, sample_key_points):
original=scene, annotated=result, similarity_threshold=0.8
)
- def test_annotate_with_custom_color_and_radius(self, scene, sample_key_points):
+ def test_annotate_with_custom_color_and_radius(
+ self, scene, sample_key_points
+ ) -> None:
"""
Verify that VertexAnnotator respects custom color and radius settings.
@@ -45,7 +47,7 @@ def test_annotate_with_custom_color_and_radius(self, scene, sample_key_points):
original=scene, annotated=result, similarity_threshold=0.7
)
- def test_annotate_empty_key_points(self, scene, empty_key_points):
+ def test_annotate_empty_key_points(self, scene, empty_key_points) -> None:
"""
Verify that VertexAnnotator handles empty keypoints without modifying the scene.
@@ -67,7 +69,7 @@ class TestEdgeAnnotator:
forming skeletons that help users interpret spatial relationships.
"""
- def test_annotate_with_default_parameters(self, scene, sample_key_points):
+ def test_annotate_with_default_parameters(self, scene, sample_key_points) -> None:
"""
Verify correctly draw skeleton edges with default parameters.
@@ -82,7 +84,7 @@ def test_annotate_with_default_parameters(self, scene, sample_key_points):
original=scene, annotated=result, similarity_threshold=0.7
)
- def test_annotate_with_custom_edges(self, scene, sample_key_points):
+ def test_annotate_with_custom_edges(self, scene, sample_key_points) -> None:
"""
Verify that EdgeAnnotator respects custom-defined skeleton structures.
@@ -99,7 +101,7 @@ def test_annotate_with_custom_edges(self, scene, sample_key_points):
original=scene, annotated=result, similarity_threshold=0.8
)
- def test_annotate_empty_key_points(self, scene, empty_key_points):
+ def test_annotate_empty_key_points(self, scene, empty_key_points) -> None:
"""
Verify that EdgeAnnotator handles empty keypoints without modifying the scene.
@@ -112,7 +114,7 @@ def test_annotate_empty_key_points(self, scene, empty_key_points):
# Should return the original scene unchanged
assert np.array_equal(result, scene)
- def test_annotate_no_edges_found(self, scene):
+ def test_annotate_no_edges_found(self, scene) -> None:
"""
Verify returning unmodified scene when no known skeleton matches.
diff --git a/tests/key_points/test_core.py b/tests/key_points/test_core.py
index bf10b1c29f..cf2ddb4a16 100644
--- a/tests/key_points/test_core.py
+++ b/tests/key_points/test_core.py
@@ -368,13 +368,13 @@
), # kp[kp.confidence > 0.5] — single-object canonical use case
],
)
-def test_key_points_getitem(key_points, index, expected_result, exception):
+def test_key_points_getitem(key_points, index, expected_result, exception) -> None:
with exception:
result = key_points[index]
assert result == expected_result
-def test_key_points_empty():
+def test_key_points_empty() -> None:
"""Test the creation and behavior of an empty KeyPoints object."""
empty_key_points = KeyPoints.empty()
assert len(empty_key_points) == 0
@@ -382,7 +382,7 @@ def test_key_points_empty():
assert empty_key_points.xy.shape == (0, 0, 2)
-def test_key_points_is_empty():
+def test_key_points_is_empty() -> None:
"""Test the is_empty method for KeyPoints objects."""
empty_key_points = KeyPoints.empty()
assert empty_key_points.is_empty()
@@ -395,7 +395,7 @@ def test_key_points_is_empty():
assert not non_empty_key_points.is_empty()
-def test_key_points_setitem():
+def test_key_points_setitem() -> None:
"""Test the __setitem__ method for KeyPoints objects."""
key_points = _create_key_points(
xy=[[[0, 1], [2, 3]]],
@@ -438,7 +438,7 @@ def test_key_points_setitem():
)
def test_key_points_as_detections(
key_points, expected_xyxy, expected_confidence, expected_class_id
-):
+) -> None:
"""Test the as_detections method for KeyPoints objects."""
detections = key_points.as_detections()
assert len(detections) == len(expected_xyxy)
@@ -447,14 +447,14 @@ def test_key_points_as_detections(
assert np.array_equal(detections.class_id, expected_class_id)
-def test_key_points_as_detections_empty():
+def test_key_points_as_detections_empty() -> None:
"""Test the as_detections method for empty KeyPoints objects."""
empty_key_points = KeyPoints.empty()
empty_detections = empty_key_points.as_detections()
assert empty_detections.is_empty()
-def test_key_points_as_detections_with_data():
+def test_key_points_as_detections_with_data() -> None:
"""Test the as_detections method preserves data."""
key_points = _create_key_points(
xy=[[[0, 1], [2, 3], [4, 5]]],
@@ -467,7 +467,7 @@ def test_key_points_as_detections_with_data():
assert np.array_equal(detections.data["custom_data"], np.array(["value1"]))
-def test_key_points_iteration():
+def test_key_points_iteration() -> None:
"""Test the iteration over KeyPoints objects."""
key_points = _create_key_points(
xy=[[[0, 1], [2, 3]], [[4, 5], [6, 7]]],
@@ -485,7 +485,7 @@ def test_key_points_iteration():
assert iterations == 2
-def test_key_points_iteration_no_confidence():
+def test_key_points_iteration_no_confidence() -> None:
"""Test the iteration over KeyPoints objects without confidence."""
key_points_no_conf = _create_key_points(
xy=[[[0, 1], [2, 3]]],
@@ -537,13 +537,13 @@ def test_key_points_iteration_no_confidence():
),
],
)
-def test_key_points_equality(key_points1, key_points2, expected_equal):
+def test_key_points_equality(key_points1, key_points2, expected_equal) -> None:
"""Test the equality comparison for KeyPoints objects."""
status = key_points1 == key_points2
assert status is expected_equal
-def test_key_points_equality_with_data():
+def test_key_points_equality_with_data() -> None:
"""Test the equality comparison for KeyPoints objects with data."""
key_points1 = _create_key_points(
xy=[[[0, 1], [2, 3]]], confidence=[[0.8, 0.9]], class_id=[0]
@@ -581,13 +581,13 @@ def test_key_points_equality_with_data():
({"predictions": []}, KeyPoints.empty()),
],
)
-def test_from_inference_input(inference_results, expected_key_points):
+def test_from_inference_input(inference_results, expected_key_points) -> None:
"""Test the from_inference method with valid input."""
key_points = KeyPoints.from_inference(inference_results)
assert key_points == expected_key_points
-def test_from_inference_invalid_input():
+def test_from_inference_invalid_input() -> None:
"""Test the from_inference method with invalid input."""
key_points = _create_key_points(
xy=[[[0, 1], [2, 3]]], confidence=[[0.8, 0.9]], class_id=[0]
@@ -624,7 +624,7 @@ def test_from_inference_invalid_input():
),
],
)
-def test_from_yolo_nas_input(yolo_nas_results, expected_key_points):
+def test_from_yolo_nas_input(yolo_nas_results, expected_key_points) -> None:
"""Test the from_yolo_nas method with valid input."""
key_points = KeyPoints.from_yolo_nas(yolo_nas_results)
assert key_points == expected_key_points
@@ -667,7 +667,9 @@ def test_from_yolo_nas_input(yolo_nas_results, expected_key_points):
),
],
)
-def test_from_mediapipe_input(mediapipe_results, resolution_wh, expected_key_points):
+def test_from_mediapipe_input(
+ mediapipe_results, resolution_wh, expected_key_points
+) -> None:
"""Test the from_mediapipe method with valid input."""
key_points = KeyPoints.from_mediapipe(
mediapipe_results, resolution_wh=resolution_wh
diff --git a/tests/key_points/test_skeletons.py b/tests/key_points/test_skeletons.py
index e97562888f..b4bc4f7eb1 100644
--- a/tests/key_points/test_skeletons.py
+++ b/tests/key_points/test_skeletons.py
@@ -6,7 +6,7 @@
class TestSkeletons:
- def test_skeleton_enum_values(self):
+ def test_skeleton_enum_values(self) -> None:
"""Test skeleton enum has correct structure."""
for skeleton in Skeleton:
assert isinstance(skeleton.value, tuple)
@@ -14,7 +14,7 @@ def test_skeleton_enum_values(self):
isinstance(edge, tuple) and len(edge) == 2 for edge in skeleton.value
)
- def test_skeletons_by_vertex_count(self):
+ def test_skeletons_by_vertex_count(self) -> None:
"""Test SKELETONS_BY_VERTEX_COUNT dictionary population."""
# Test that the dictionary is populated
assert len(SKELETONS_BY_VERTEX_COUNT) > 0
@@ -24,7 +24,7 @@ def test_skeletons_by_vertex_count(self):
assert 17 in SKELETONS_BY_VERTEX_COUNT # COCO has 17 keypoints
assert SKELETONS_BY_VERTEX_COUNT[17] == coco_skeleton
- def test_skeletons_by_edge_count(self):
+ def test_skeletons_by_edge_count(self) -> None:
"""Test SKELETONS_BY_EDGE_COUNT dictionary mapping."""
# Test that the dictionary is populated
assert len(SKELETONS_BY_EDGE_COUNT) > 0
@@ -38,13 +38,13 @@ def test_skeletons_by_edge_count(self):
assert SKELETONS_BY_EDGE_COUNT == expected
- def test_unique_vertices_calculation(self):
+ def test_unique_vertices_calculation(self) -> None:
"""Test unique vertices calculation from skeleton edges."""
coco_skeleton = Skeleton.COCO.value
unique_vertices = {vertex for edge in coco_skeleton for vertex in edge}
assert len(unique_vertices) == 17 # COCO has 17 keypoints
- def test_skeletons_by_vertex_count_mapping_behaviour(self):
+ def test_skeletons_by_vertex_count_mapping_behaviour(self) -> None:
"""Test SKELETONS_BY_VERTEX_COUNT uses last-in-wins for duplicate counts."""
expected_mapping = {}
for skeleton in Skeleton:
diff --git a/tests/metrics/conftest.py b/tests/metrics/conftest.py
index c6e2c8e901..7033763b88 100644
--- a/tests/metrics/conftest.py
+++ b/tests/metrics/conftest.py
@@ -1,5 +1,3 @@
-from typing import Optional
-
import numpy as np
import pytest
@@ -145,7 +143,7 @@ def target_class_1():
def _yolo_dataset_factory(
tmp_path,
num_images: int = 20,
- classes: Optional[list[str]] = None,
+ classes: list[str] | None = None,
objects_per_image_range: tuple[int, int] = (1, 3),
):
"""
diff --git a/tests/metrics/test_detection.py b/tests/metrics/test_detection.py
index ba7b1f9d6a..47c65a9578 100644
--- a/tests/metrics/test_detection.py
+++ b/tests/metrics/test_detection.py
@@ -342,7 +342,7 @@ def test_from_tensors(
iou_threshold,
expected_result: np.ndarray | None,
exception: Exception,
- ):
+ ) -> None:
with exception:
result = ConfusionMatrix.from_tensors(
predictions=predictions,
@@ -386,7 +386,7 @@ def test_evaluate_detection_batch(
iou_threshold,
expected_result: np.ndarray | None,
exception: Exception,
- ):
+ ) -> None:
with exception:
result = ConfusionMatrix.evaluate_detection_batch(
predictions=predictions,
@@ -414,7 +414,7 @@ def test_drop_extra_matches(
matches,
expected_result: np.ndarray | None,
exception: Exception,
- ):
+ ) -> None:
with exception:
result = ConfusionMatrix._drop_extra_matches(matches)
@@ -934,7 +934,7 @@ def test_confusion_matrix(
iou_threshold,
expected_result,
exception: Exception,
- ):
+ ) -> None:
with exception:
confusion_matrix = ConfusionMatrix.from_detections(
predictions=predictions,
@@ -948,7 +948,7 @@ def test_confusion_matrix(
# AssertionError if the two arrays are not equal
np.testing.assert_array_equal(confusion_matrix.matrix, expected_result)
- def test_confusion_matrix_on_yolo_dataset(self, yolo_dataset_structure):
+ def test_confusion_matrix_on_yolo_dataset(self, yolo_dataset_structure) -> None:
"""
Test confusion matrix calculation on a YOLO-format dataset.
diff --git a/tests/metrics/test_f1_score.py b/tests/metrics/test_f1_score.py
index 347da196e1..bea901f648 100644
--- a/tests/metrics/test_f1_score.py
+++ b/tests/metrics/test_f1_score.py
@@ -36,7 +36,7 @@ def targets_multiple_classes(self):
class_id=np.array([0, 1]),
)
- def test_initialization_default(self):
+ def test_initialization_default(self) -> None:
"""Test that F1Score can be initialized with default parameters"""
metric = F1Score()
assert metric._metric_target == MetricTarget.BOXES
@@ -44,7 +44,7 @@ def test_initialization_default(self):
assert metric._predictions_list == []
assert metric._targets_list == []
- def test_initialization_custom(self):
+ def test_initialization_custom(self) -> None:
"""Test that F1Score can be initialized with custom parameters"""
metric = F1Score(
metric_target=MetricTarget.MASKS,
@@ -53,7 +53,7 @@ def test_initialization_custom(self):
assert metric._metric_target == MetricTarget.MASKS
assert metric.averaging_method == AveragingMethod.MACRO
- def test_reset(self, dummy_prediction):
+ def test_reset(self, dummy_prediction) -> None:
"""Test that reset() clears all stored data"""
metric = F1Score()
@@ -69,7 +69,7 @@ def test_reset(self, dummy_prediction):
assert metric._predictions_list == []
assert metric._targets_list == []
- def test_perfect_match(self, detections_50_50, targets_50_50):
+ def test_perfect_match(self, detections_50_50, targets_50_50) -> None:
"""Test F1 score with perfect matching predictions and targets"""
metric = F1Score()
result = metric.update(detections_50_50, targets_50_50).compute()
@@ -84,7 +84,7 @@ def test_perfect_match(self, detections_50_50, targets_50_50):
assert len(result.matched_classes) == 1
assert result.matched_classes[0] == 0
- def test_no_overlap(self, predictions_no_overlap, targets_no_overlap):
+ def test_no_overlap(self, predictions_no_overlap, targets_no_overlap) -> None:
"""Test F1 score with predictions that don't overlap with targets"""
metric = F1Score()
result = metric.update(predictions_no_overlap, targets_no_overlap).compute()
@@ -96,7 +96,7 @@ def test_no_overlap(self, predictions_no_overlap, targets_no_overlap):
assert result.f1_50 == 0.0
assert result.f1_75 == 0.0
- def test_empty_predictions(self, targets_50_50):
+ def test_empty_predictions(self, targets_50_50) -> None:
"""Test F1 score with empty predictions but existing targets"""
predictions = Detections.empty()
@@ -110,7 +110,7 @@ def test_empty_predictions(self, targets_50_50):
assert result.f1_50 == 0.0
assert result.f1_75 == 0.0
- def test_empty_targets(self, detections_50_50):
+ def test_empty_targets(self, detections_50_50) -> None:
"""Test F1 score with predictions but no targets"""
targets = Detections.empty()
@@ -126,7 +126,7 @@ def test_empty_targets(self, detections_50_50):
def test_single_class_mixed_results(
self, predictions_confidence_ranking, targets_50_50
- ):
+ ) -> None:
"""Test F1 score calculation with mixed precision and recall"""
metric = F1Score()
result = metric.update(predictions_confidence_ranking, targets_50_50).compute()
@@ -141,7 +141,7 @@ def test_single_class_mixed_results(
def test_precision_recall_imbalance(
self, detections_50_50, targets_two_objects_class_0
- ):
+ ) -> None:
"""Test F1 score with different precision and recall scenarios"""
metric = F1Score()
result = metric.update(detections_50_50, targets_two_objects_class_0).compute()
@@ -156,7 +156,7 @@ def test_precision_recall_imbalance(
def test_multiple_classes(
self, predictions_multiple_classes, targets_multiple_classes
- ):
+ ) -> None:
"""Test F1 score calculation for multiple classes"""
metric = F1Score()
result = metric.update(
@@ -172,7 +172,9 @@ def test_multiple_classes(
assert 0 in result.matched_classes
assert 1 in result.matched_classes
- def test_different_iou_thresholds(self, predictions_iou_064, targets_iou_064):
+ def test_different_iou_thresholds(
+ self, predictions_iou_064, targets_iou_064
+ ) -> None:
"""Test F1 score at different IoU thresholds"""
metric = F1Score()
result = metric.update(predictions_iou_064, targets_iou_064).compute()
@@ -183,7 +185,9 @@ def test_different_iou_thresholds(self, predictions_iou_064, targets_iou_064):
assert result.f1_50 == 1.0
assert result.f1_75 == 0.0
- def test_confidence_ranking(self, predictions_confidence_ranking, targets_50_50):
+ def test_confidence_ranking(
+ self, predictions_confidence_ranking, targets_50_50
+ ) -> None:
"""Test that F1 score respects confidence ranking"""
metric = F1Score()
result = metric.update(predictions_confidence_ranking, targets_50_50).compute()
@@ -197,7 +201,7 @@ def test_confidence_ranking(self, predictions_confidence_ranking, targets_50_50)
def test_list_inputs(
self, detections_50_50, targets_50_50, prediction_class_1, target_class_1
- ):
+ ) -> None:
"""Test F1 score with list inputs"""
metric = F1Score()
result = metric.update(
@@ -208,7 +212,7 @@ def test_list_inputs(
assert result.f1_50 == 1.0
assert result.f1_75 == 1.0
- def test_mismatched_list_lengths(self, detections_50_50, targets_50_50):
+ def test_mismatched_list_lengths(self, detections_50_50, targets_50_50) -> None:
"""Test that mismatched prediction/target list lengths raise error"""
metric = F1Score()
@@ -216,11 +220,52 @@ def test_mismatched_list_lengths(self, detections_50_50, targets_50_50):
with pytest.raises(ValueError, match="number of predictions"):
metric.update([detections_50_50], [targets_50_50, targets_50_50])
+ @pytest.mark.parametrize(
+ "missing_attribute",
+ ["predictions_class_id", "targets_class_id", "predictions_confidence"],
+ )
+ def test_compute_value_error_for_missing_required_fields(
+ self, missing_attribute
+ ) -> None:
+ """Raises ValueError when required detection fields are missing."""
+ metric = F1Score()
+ boxes = np.array([[10, 10, 50, 50]], dtype=np.float32)
+ class_id = np.array([0], dtype=np.int32)
+ confidence = np.array([0.9], dtype=np.float32)
+
+ predictions = Detections(
+ xyxy=boxes,
+ confidence=confidence,
+ class_id=class_id,
+ )
+ targets = Detections(
+ xyxy=boxes,
+ class_id=class_id,
+ )
+
+ if missing_attribute == "predictions_class_id":
+ predictions = Detections(
+ xyxy=boxes,
+ confidence=confidence,
+ )
+ elif missing_attribute == "targets_class_id":
+ targets = Detections(xyxy=boxes)
+ else:
+ predictions = Detections(
+ xyxy=boxes,
+ class_id=class_id,
+ )
+
+ with pytest.raises(ValueError, match="F1Score metric requires"):
+ metric.update(predictions, targets).compute()
+
@pytest.mark.parametrize(
"averaging_method",
[AveragingMethod.MACRO, AveragingMethod.MICRO, AveragingMethod.WEIGHTED],
)
- def test_averaging_methods(self, averaging_method, detections_50_50, targets_50_50):
+ def test_averaging_methods(
+ self, averaging_method, detections_50_50, targets_50_50
+ ) -> None:
"""Test different averaging methods"""
metric = F1Score(averaging_method=averaging_method)
result = metric.update(detections_50_50, targets_50_50).compute()
@@ -231,7 +276,7 @@ def test_averaging_methods(self, averaging_method, detections_50_50, targets_50_
def test_macro_averaging(
self, predictions_multiple_classes, targets_multiple_classes
- ):
+ ) -> None:
"""Test MACRO averaging with specific example"""
metric = F1Score(averaging_method=AveragingMethod.MACRO)
result = metric.update(
@@ -244,7 +289,7 @@ def test_macro_averaging(
def test_micro_averaging(
self, predictions_multiple_classes, targets_multiple_classes
- ):
+ ) -> None:
"""Test MICRO averaging with specific example"""
metric = F1Score(averaging_method=AveragingMethod.MICRO)
result = metric.update(
@@ -257,7 +302,7 @@ def test_micro_averaging(
def test_weighted_averaging(
self, predictions_multiple_classes, targets_multiple_classes
- ):
+ ) -> None:
"""Test WEIGHTED averaging with specific example"""
metric = F1Score(averaging_method=AveragingMethod.WEIGHTED)
result = metric.update(
diff --git a/tests/metrics/test_mean_average_precision.py b/tests/metrics/test_mean_average_precision.py
index 9d19fd7904..9c1238b745 100644
--- a/tests/metrics/test_mean_average_precision.py
+++ b/tests/metrics/test_mean_average_precision.py
@@ -5,7 +5,7 @@
class TestMeanAveragePrecision:
- def test_single_perfect_detection(self, detections_50_50, targets_50_50):
+ def test_single_perfect_detection(self, detections_50_50, targets_50_50) -> None:
"""Test that single perfect detection gets 1.0 mAP (not 0.0 due to ID=0 bug)"""
metric = MeanAveragePrecision()
metric.update([detections_50_50], [targets_50_50])
@@ -14,7 +14,7 @@ def test_single_perfect_detection(self, detections_50_50, targets_50_50):
# Should be perfect 1.0 mAP, not 0.0 due to ID=0 bug
assert abs(result.map50_95 - 1.0) < 1e-6
- def test_multiple_perfect_detections(self):
+ def test_multiple_perfect_detections(self) -> None:
"""Test that multiple perfect detections get 1.0 mAP"""
# Multiple perfect detections in one image
detections = Detections(
@@ -33,7 +33,9 @@ def test_multiple_perfect_detections(self):
# Should be perfect 1.0 mAP
assert abs(result.map50_95 - 1.0) < 1e-6
- def test_batch_updates_perfect_detections(self, detections_50_50, targets_50_50):
+ def test_batch_updates_perfect_detections(
+ self, detections_50_50, targets_50_50
+ ) -> None:
"""Test that batch updates with perfect detections get 1.0 mAP"""
metric = MeanAveragePrecision()
# Add 3 batch updates
@@ -45,7 +47,7 @@ def test_batch_updates_perfect_detections(self, detections_50_50, targets_50_50)
# Should be perfect 1.0 mAP across all batches
assert abs(result.map50_95 - 1.0) < 1e-6
- def test_scenario_1_success_case_imperfect_match(self):
+ def test_scenario_1_success_case_imperfect_match(self) -> None:
"""Scenario 1: Success Case with imperfect match"""
# Small object (class 0) - area = 30*30 = 900 < 1024
small_perfect = Detections(
@@ -99,7 +101,7 @@ def test_scenario_1_success_case_imperfect_match(self):
result.medium_objects.map50_95 < 1.0
) # Medium should be less than perfect
- def test_scenario_2_missed_detection(self):
+ def test_scenario_2_missed_detection(self) -> None:
"""Scenario 2: GT Present, No Prediction (Missed Detection)"""
# Small object - area = 30*30 = 900 < 1024
small_detection = Detections(
@@ -137,7 +139,7 @@ def test_scenario_2_missed_detection(self):
# Medium objects should have 0.0 mAP (missed detection)
assert abs(result.medium_objects.map50_95 - 0.0) < 1e-6
- def test_scenario_3_false_positive(self):
+ def test_scenario_3_false_positive(self) -> None:
"""Scenario 3: No GT, Prediction Present (False Positive)"""
# Small object - area = 30*30 = 900 < 1024
small_detection = Detections(
@@ -176,7 +178,7 @@ def test_scenario_3_false_positive(self):
# Medium objects should have -1 mAP (false positive, matching pycocotools)
assert result.medium_objects.map50_95 == -1
- def test_scenario_4_no_data(self):
+ def test_scenario_4_no_data(self) -> None:
"""Scenario 4: No GT, No Prediction (Category has no data)"""
# Small object - area = 30*30 = 900 < 1024
small_detection = Detections(
@@ -226,7 +228,7 @@ def test_scenario_4_no_data(self):
# Medium objects should have -1 mAP (no data, matching pycocotools)
assert result.medium_objects.map50_95 == -1
- def test_scenario_5_only_one_class_present(self):
+ def test_scenario_5_only_one_class_present(self) -> None:
"""Scenario 5: Only 1 of 3 Classes Present (Perfect Match)"""
# Only class 0 objects with perfect matches
detections_class_0 = [
@@ -255,7 +257,7 @@ def test_scenario_5_only_one_class_present(self):
def test_mixed_classes_with_missing_detections(
self, detections_50_50, targets_50_50
- ):
+ ) -> None:
"""Test mixed scenario with some classes having no detections"""
# Class 1: GT exists but no prediction
class_1_target = Detections(
@@ -283,7 +285,7 @@ def test_mixed_classes_with_missing_detections(
# Should be less than 1.0 due to missed detection and false positive
assert result.map50_95 < 1.0
- def test_empty_predictions_and_targets(self):
+ def test_empty_predictions_and_targets(self) -> None:
"""Test completely empty predictions and targets"""
metric = MeanAveragePrecision()
metric.update([Detections.empty()], [Detections.empty()])
diff --git a/tests/metrics/test_mean_average_precision_area.py b/tests/metrics/test_mean_average_precision_area.py
index fcc3991a2d..f2e9e0b156 100644
--- a/tests/metrics/test_mean_average_precision_area.py
+++ b/tests/metrics/test_mean_average_precision_area.py
@@ -44,7 +44,7 @@ class TestMeanAveragePrecisionArea:
)
def test_area_calculation_and_size_specific_map(
self, xyxy, expected_areas, expected_size_maps
- ):
+ ) -> None:
"""Test area calculation and size-specific mAP functionality."""
gt = Detections(
xyxy=xyxy,
@@ -96,7 +96,7 @@ def test_area_calculation_and_size_specific_map(
"Large objects should have no data"
)
- def test_area_preserved_from_data(self):
+ def test_area_preserved_from_data(self) -> None:
"""Test that area from data field is preserved (COCO case)."""
gt = Detections(
xyxy=np.array(
diff --git a/tests/metrics/test_mean_average_recall.py b/tests/metrics/test_mean_average_recall.py
index ab36e70b66..727faba184 100644
--- a/tests/metrics/test_mean_average_recall.py
+++ b/tests/metrics/test_mean_average_recall.py
@@ -359,7 +359,45 @@ def three_class_single_image_detections():
return predictions, targets
-def test_single_perfect_detection():
+@pytest.mark.parametrize(
+ "missing_attribute",
+ ["predictions_class_id", "targets_class_id", "predictions_confidence"],
+)
+def test_compute_value_error_for_missing_required_fields(missing_attribute) -> None:
+ """Raises ValueError when required detection fields are missing."""
+ metric = MeanAverageRecall()
+ boxes = np.array([[10, 10, 50, 50]], dtype=np.float32)
+ class_id = np.array([0], dtype=np.int32)
+ confidence = np.array([0.9], dtype=np.float32)
+
+ predictions = Detections(
+ xyxy=boxes,
+ confidence=confidence,
+ class_id=class_id,
+ )
+ targets = Detections(
+ xyxy=boxes,
+ class_id=class_id,
+ )
+
+ if missing_attribute == "predictions_class_id":
+ predictions = Detections(
+ xyxy=boxes,
+ confidence=confidence,
+ )
+ elif missing_attribute == "targets_class_id":
+ targets = Detections(xyxy=boxes)
+ else:
+ predictions = Detections(
+ xyxy=boxes,
+ class_id=class_id,
+ )
+
+ with pytest.raises(ValueError, match="MeanAverageRecall metric requires"):
+ metric.update(predictions, targets).compute()
+
+
+def test_single_perfect_detection() -> None:
"""Test that a single perfect detection yields 1.0 recall."""
detections = Detections(
xyxy=np.array([[10, 10, 50, 50]], dtype=np.float32),
@@ -377,7 +415,7 @@ def test_single_perfect_detection():
def test_complex_integration_scenario(
complex_scenario_predictions, complex_scenario_targets
-):
+) -> None:
"""Test integration scenario with multiple images and varying performance."""
def mock_detections_list(boxes_list):
@@ -403,7 +441,9 @@ def mock_detections_list(boxes_list):
np.testing.assert_almost_equal(result.recall_scores, expected_result, decimal=6)
-def test_mar_at_k_limits_per_image_not_per_class(two_class_two_image_detections):
+def test_mar_at_k_limits_per_image_not_per_class(
+ two_class_two_image_detections,
+) -> None:
"""
Test that `mAR @ K` limits detections per image, not per class.
@@ -458,7 +498,7 @@ def test_mar_at_k_limits_per_image_not_per_class(two_class_two_image_detections)
)
-def test_three_class_single_image_scenario(three_class_single_image_detections):
+def test_three_class_single_image_scenario(three_class_single_image_detections) -> None:
"""
Test with 3 classes on single image - explicit N x K bug reproduction.
@@ -505,7 +545,7 @@ def test_three_class_single_image_scenario(three_class_single_image_detections):
)
-def test_dataset_split_integration(yolo_dataset_two_classes):
+def test_dataset_split_integration(yolo_dataset_two_classes) -> None:
"""
Test mAR with a roboflow-format dataset loaded from disk.
diff --git a/tests/metrics/test_precision.py b/tests/metrics/test_precision.py
index 5c9303ec84..e57b757628 100644
--- a/tests/metrics/test_precision.py
+++ b/tests/metrics/test_precision.py
@@ -36,7 +36,7 @@ def targets_multiple_classes(self):
class_id=np.array([0, 0, 1]),
)
- def test_initialization_default(self):
+ def test_initialization_default(self) -> None:
"""Test that Precision can be initialized with default parameters"""
metric = Precision()
assert metric._metric_target == MetricTarget.BOXES
@@ -44,7 +44,7 @@ def test_initialization_default(self):
assert metric._predictions_list == []
assert metric._targets_list == []
- def test_initialization_custom(self):
+ def test_initialization_custom(self) -> None:
"""Test that Precision can be initialized with custom parameters"""
metric = Precision(
metric_target=MetricTarget.MASKS,
@@ -53,7 +53,7 @@ def test_initialization_custom(self):
assert metric._metric_target == MetricTarget.MASKS
assert metric.averaging_method == AveragingMethod.MACRO
- def test_reset(self, dummy_prediction):
+ def test_reset(self, dummy_prediction) -> None:
"""Test that reset() clears all stored data"""
metric = Precision()
@@ -69,7 +69,7 @@ def test_reset(self, dummy_prediction):
assert metric._predictions_list == []
assert metric._targets_list == []
- def test_perfect_match(self, detections_50_50, targets_50_50):
+ def test_perfect_match(self, detections_50_50, targets_50_50) -> None:
"""Test precision with perfect matching predictions and targets"""
metric = Precision()
result = metric.update(detections_50_50, targets_50_50).compute()
@@ -82,7 +82,7 @@ def test_perfect_match(self, detections_50_50, targets_50_50):
assert len(result.matched_classes) == 1
assert result.matched_classes[0] == 0
- def test_no_overlap(self, predictions_no_overlap, targets_no_overlap):
+ def test_no_overlap(self, predictions_no_overlap, targets_no_overlap) -> None:
"""Test precision with predictions that don't overlap with targets"""
metric = Precision()
result = metric.update(predictions_no_overlap, targets_no_overlap).compute()
@@ -92,7 +92,7 @@ def test_no_overlap(self, predictions_no_overlap, targets_no_overlap):
assert result.precision_at_50 == 0.0
assert result.precision_at_75 == 0.0
- def test_empty_predictions(self, targets_50_50):
+ def test_empty_predictions(self, targets_50_50) -> None:
"""Test precision with empty predictions but existing targets"""
predictions = Detections.empty()
@@ -103,7 +103,7 @@ def test_empty_predictions(self, targets_50_50):
assert result.precision_at_50 == 0.0
assert result.precision_at_75 == 0.0
- def test_empty_targets(self, detections_50_50):
+ def test_empty_targets(self, detections_50_50) -> None:
"""Test precision with predictions but no targets"""
targets = Detections.empty()
@@ -115,7 +115,7 @@ def test_empty_targets(self, detections_50_50):
assert result.precision_at_50 == 0.0
assert result.precision_at_75 == 0.0
- def test_single_class(self, predictions_confidence_ranking, targets_50_50):
+ def test_single_class(self, predictions_confidence_ranking, targets_50_50) -> None:
"""Test precision calculation for single class with mixed results"""
metric = Precision()
result = metric.update(predictions_confidence_ranking, targets_50_50).compute()
@@ -127,7 +127,7 @@ def test_single_class(self, predictions_confidence_ranking, targets_50_50):
def test_multiple_classes(
self, predictions_multiple_classes, targets_multiple_classes
- ):
+ ) -> None:
"""Test precision calculation for multiple classes"""
metric = Precision()
result = metric.update(
@@ -144,7 +144,9 @@ def test_multiple_classes(
assert 0 in result.matched_classes
assert 1 in result.matched_classes
- def test_different_iou_thresholds(self, predictions_iou_064, targets_iou_064):
+ def test_different_iou_thresholds(
+ self, predictions_iou_064, targets_iou_064
+ ) -> None:
"""Test precision at different IoU thresholds"""
metric = Precision()
result = metric.update(predictions_iou_064, targets_iou_064).compute()
@@ -154,7 +156,9 @@ def test_different_iou_thresholds(self, predictions_iou_064, targets_iou_064):
assert result.precision_at_50 == 1.0 # TP=1, FP=0
assert result.precision_at_75 == 0.0 # TP=0, FP=1
- def test_confidence_ranking(self, predictions_confidence_ranking, targets_50_50):
+ def test_confidence_ranking(
+ self, predictions_confidence_ranking, targets_50_50
+ ) -> None:
"""Test that predictions are ranked by confidence"""
metric = Precision()
result = metric.update(predictions_confidence_ranking, targets_50_50).compute()
@@ -165,7 +169,7 @@ def test_confidence_ranking(self, predictions_confidence_ranking, targets_50_50)
def test_list_inputs(
self, detections_50_50, targets_50_50, prediction_class_1, target_class_1
- ):
+ ) -> None:
"""Test precision with list inputs"""
metric = Precision()
result = metric.update(
@@ -176,7 +180,7 @@ def test_list_inputs(
assert result.precision_at_50 == 1.0
assert result.precision_at_75 == 1.0
- def test_mismatched_list_lengths(self, detections_50_50, targets_50_50):
+ def test_mismatched_list_lengths(self, detections_50_50, targets_50_50) -> None:
"""Test that mismatched prediction/target list lengths raise error"""
metric = Precision()
@@ -184,11 +188,52 @@ def test_mismatched_list_lengths(self, detections_50_50, targets_50_50):
with pytest.raises(ValueError, match="number of predictions"):
metric.update([detections_50_50], [targets_50_50, targets_50_50])
+ @pytest.mark.parametrize(
+ "missing_attribute",
+ ["predictions_class_id", "targets_class_id", "predictions_confidence"],
+ )
+ def test_compute_value_error_for_missing_required_fields(
+ self, missing_attribute
+ ) -> None:
+ """Raises ValueError when required detection fields are missing."""
+ metric = Precision()
+ boxes = np.array([[10, 10, 50, 50]], dtype=np.float32)
+ class_id = np.array([0], dtype=np.int32)
+ confidence = np.array([0.9], dtype=np.float32)
+
+ predictions = Detections(
+ xyxy=boxes,
+ confidence=confidence,
+ class_id=class_id,
+ )
+ targets = Detections(
+ xyxy=boxes,
+ class_id=class_id,
+ )
+
+ if missing_attribute == "predictions_class_id":
+ predictions = Detections(
+ xyxy=boxes,
+ confidence=confidence,
+ )
+ elif missing_attribute == "targets_class_id":
+ targets = Detections(xyxy=boxes)
+ else:
+ predictions = Detections(
+ xyxy=boxes,
+ class_id=class_id,
+ )
+
+ with pytest.raises(ValueError, match="Precision metric requires"):
+ metric.update(predictions, targets).compute()
+
@pytest.mark.parametrize(
"averaging_method",
[AveragingMethod.MACRO, AveragingMethod.MICRO, AveragingMethod.WEIGHTED],
)
- def test_averaging_methods(self, averaging_method, detections_50_50, targets_50_50):
+ def test_averaging_methods(
+ self, averaging_method, detections_50_50, targets_50_50
+ ) -> None:
"""Test different averaging methods"""
metric = Precision(averaging_method=averaging_method)
result = metric.update(detections_50_50, targets_50_50).compute()
diff --git a/tests/metrics/test_recall.py b/tests/metrics/test_recall.py
index 4772ee3aa0..8f83e1677f 100644
--- a/tests/metrics/test_recall.py
+++ b/tests/metrics/test_recall.py
@@ -36,7 +36,7 @@ def targets_multiple_classes(self):
class_id=np.array([0, 0, 1]),
)
- def test_initialization_default(self):
+ def test_initialization_default(self) -> None:
"""Test that Recall can be initialized with default parameters"""
metric = Recall()
assert metric._metric_target == MetricTarget.BOXES
@@ -44,7 +44,7 @@ def test_initialization_default(self):
assert metric._predictions_list == []
assert metric._targets_list == []
- def test_initialization_custom(self):
+ def test_initialization_custom(self) -> None:
"""Test that Recall can be initialized with custom parameters"""
metric = Recall(
metric_target=MetricTarget.MASKS,
@@ -53,7 +53,7 @@ def test_initialization_custom(self):
assert metric._metric_target == MetricTarget.MASKS
assert metric.averaging_method == AveragingMethod.MACRO
- def test_reset(self, dummy_prediction):
+ def test_reset(self, dummy_prediction) -> None:
"""Test that reset() clears all stored data"""
metric = Recall()
@@ -69,7 +69,7 @@ def test_reset(self, dummy_prediction):
assert metric._predictions_list == []
assert metric._targets_list == []
- def test_perfect_match(self, detections_50_50, targets_50_50):
+ def test_perfect_match(self, detections_50_50, targets_50_50) -> None:
"""Test recall with perfect matching predictions and targets"""
metric = Recall()
result = metric.update(detections_50_50, targets_50_50).compute()
@@ -81,7 +81,7 @@ def test_perfect_match(self, detections_50_50, targets_50_50):
assert len(result.matched_classes) == 1
assert result.matched_classes[0] == 0
- def test_no_overlap(self, predictions_no_overlap, targets_no_overlap):
+ def test_no_overlap(self, predictions_no_overlap, targets_no_overlap) -> None:
"""Test recall with predictions that don't overlap with targets"""
metric = Recall()
result = metric.update(predictions_no_overlap, targets_no_overlap).compute()
@@ -91,7 +91,7 @@ def test_no_overlap(self, predictions_no_overlap, targets_no_overlap):
assert result.recall_at_50 == 0.0
assert result.recall_at_75 == 0.0
- def test_empty_predictions(self, targets_50_50):
+ def test_empty_predictions(self, targets_50_50) -> None:
"""Test recall with empty predictions but existing targets"""
predictions = Detections.empty()
@@ -102,7 +102,7 @@ def test_empty_predictions(self, targets_50_50):
assert result.recall_at_50 == 0.0
assert result.recall_at_75 == 0.0
- def test_empty_targets(self, detections_50_50):
+ def test_empty_targets(self, detections_50_50) -> None:
"""Test recall with predictions but no targets"""
targets = Detections.empty()
@@ -115,7 +115,7 @@ def test_empty_targets(self, detections_50_50):
def test_single_class_missed_detections(
self, detections_50_50, targets_two_objects_class_0
- ):
+ ) -> None:
"""Test recall calculation with some missed detections"""
metric = Recall()
result = metric.update(detections_50_50, targets_two_objects_class_0).compute()
@@ -127,7 +127,7 @@ def test_single_class_missed_detections(
def test_multiple_classes(
self, predictions_multiple_classes, targets_multiple_classes
- ):
+ ) -> None:
"""Test recall calculation for multiple classes"""
metric = Recall()
result = metric.update(
@@ -144,7 +144,9 @@ def test_multiple_classes(
assert 0 in result.matched_classes
assert 1 in result.matched_classes
- def test_different_iou_thresholds(self, predictions_iou_064, targets_iou_064):
+ def test_different_iou_thresholds(
+ self, predictions_iou_064, targets_iou_064
+ ) -> None:
"""Test recall at different IoU thresholds"""
metric = Recall()
result = metric.update(predictions_iou_064, targets_iou_064).compute()
@@ -154,7 +156,9 @@ def test_different_iou_thresholds(self, predictions_iou_064, targets_iou_064):
assert result.recall_at_50 == 1.0 # TP=1, FN=0
assert result.recall_at_75 == 0.0 # TP=0, FN=1
- def test_confidence_ranking(self, predictions_confidence_ranking, targets_50_50):
+ def test_confidence_ranking(
+ self, predictions_confidence_ranking, targets_50_50
+ ) -> None:
"""Test that higher confidence predictions are preferred for matching"""
metric = Recall()
result = metric.update(predictions_confidence_ranking, targets_50_50).compute()
@@ -165,7 +169,7 @@ def test_confidence_ranking(self, predictions_confidence_ranking, targets_50_50)
def test_multiple_predictions_one_target(
self, predictions_confidence_ranking, targets_50_50
- ):
+ ) -> None:
"""Test recall when multiple predictions compete for one target"""
metric = Recall()
result = metric.update(predictions_confidence_ranking, targets_50_50).compute()
@@ -176,7 +180,7 @@ def test_multiple_predictions_one_target(
def test_list_inputs(
self, detections_50_50, targets_50_50, prediction_class_1, target_class_1
- ):
+ ) -> None:
"""Test recall with list inputs"""
metric = Recall()
result = metric.update(
@@ -187,7 +191,7 @@ def test_list_inputs(
assert result.recall_at_50 == 1.0
assert result.recall_at_75 == 1.0
- def test_mismatched_list_lengths(self, detections_50_50, targets_50_50):
+ def test_mismatched_list_lengths(self, detections_50_50, targets_50_50) -> None:
"""Test that mismatched prediction/target list lengths raise error"""
metric = Recall()
@@ -195,11 +199,52 @@ def test_mismatched_list_lengths(self, detections_50_50, targets_50_50):
with pytest.raises(ValueError, match="number of predictions"):
metric.update([detections_50_50], [targets_50_50, targets_50_50])
+ @pytest.mark.parametrize(
+ "missing_attribute",
+ ["predictions_class_id", "targets_class_id", "predictions_confidence"],
+ )
+ def test_compute_value_error_for_missing_required_fields(
+ self, missing_attribute
+ ) -> None:
+ """Raises ValueError when required detection fields are missing."""
+ metric = Recall()
+ boxes = np.array([[10, 10, 50, 50]], dtype=np.float32)
+ class_id = np.array([0], dtype=np.int32)
+ confidence = np.array([0.9], dtype=np.float32)
+
+ predictions = Detections(
+ xyxy=boxes,
+ confidence=confidence,
+ class_id=class_id,
+ )
+ targets = Detections(
+ xyxy=boxes,
+ class_id=class_id,
+ )
+
+ if missing_attribute == "predictions_class_id":
+ predictions = Detections(
+ xyxy=boxes,
+ confidence=confidence,
+ )
+ elif missing_attribute == "targets_class_id":
+ targets = Detections(xyxy=boxes)
+ else:
+ predictions = Detections(
+ xyxy=boxes,
+ class_id=class_id,
+ )
+
+ with pytest.raises(ValueError, match="Recall metric requires"):
+ metric.update(predictions, targets).compute()
+
@pytest.mark.parametrize(
"averaging_method",
[AveragingMethod.MACRO, AveragingMethod.MICRO, AveragingMethod.WEIGHTED],
)
- def test_averaging_methods(self, averaging_method, detections_50_50, targets_50_50):
+ def test_averaging_methods(
+ self, averaging_method, detections_50_50, targets_50_50
+ ) -> None:
"""Test different averaging methods"""
metric = Recall(averaging_method=averaging_method)
result = metric.update(detections_50_50, targets_50_50).compute()
@@ -208,7 +253,7 @@ def test_averaging_methods(self, averaging_method, detections_50_50, targets_50_
assert result.recall_at_50 == 1.0
assert result.averaging_method == averaging_method
- def test_macro_averaging(self):
+ def test_macro_averaging(self) -> None:
"""Test MACRO averaging with specific example"""
# Class 0: 1/2 targets matched -> recall = 0.5
# Class 1: 1/1 targets matched -> recall = 1.0
diff --git a/tests/utils/test_file.py b/tests/utils/test_file.py
index 3b88a6e87b..79a9701da0 100644
--- a/tests/utils/test_file.py
+++ b/tests/utils/test_file.py
@@ -63,7 +63,7 @@ def test_read_txt_file(
skip_empty: bool,
expected_result: list[str] | None,
exception: Exception,
-):
+) -> None:
with exception:
result = read_txt_file(file_name, skip_empty)
assert result == expected_result
diff --git a/tests/utils/test_image.py b/tests/utils/test_image.py
index f7e68ecd4e..2d0226eaf5 100644
--- a/tests/utils/test_image.py
+++ b/tests/utils/test_image.py
@@ -131,7 +131,7 @@ def test_letterbox_image_for_pillow_image() -> None:
),
],
)
-def test_crop_image(image, xyxy, expected_size):
+def test_crop_image(image, xyxy, expected_size) -> None:
cropped = crop_image(image=image, xyxy=xyxy)
if isinstance(image, np.ndarray):
assert isinstance(cropped, np.ndarray)
@@ -155,6 +155,6 @@ def test_crop_image(image, xyxy, expected_size):
(Image.new("L", (20, 10), color=0), (20, 10)),
],
)
-def test_get_image_resolution_wh(image, expected):
+def test_get_image_resolution_wh(image, expected) -> None:
resolution = get_image_resolution_wh(image)
assert resolution == expected
diff --git a/tests/utils/test_internal.py b/tests/utils/test_internal.py
index eb4cab411d..6db27855c5 100644
--- a/tests/utils/test_internal.py
+++ b/tests/utils/test_internal.py
@@ -10,30 +10,30 @@
class MockClass:
- def __init__(self):
+ def __init__(self) -> None:
self.public = 0
self._protected = 1
self.__private = 2
- def public_method(self):
+ def public_method(self) -> None:
pass
- def _protected_method(self):
+ def _protected_method(self) -> None:
pass
- def __private_method(self):
+ def __private_method(self) -> None:
pass
@property
- def public_property(self):
+ def public_property(self) -> int:
return 0
@property
- def _protected_property(self):
+ def _protected_property(self) -> int:
return 1
@property
- def __private_property(self):
+ def __private_property(self) -> int:
return 2
@@ -51,25 +51,25 @@ class MockDataclass:
_protected_field_with_factory: dict = field(default_factory=dict)
__private_field_with_factory: dict = field(default_factory=dict)
- def public_method(self):
+ def public_method(self) -> None:
pass
- def _protected_method(self):
+ def _protected_method(self) -> None:
pass
- def __private_method(self):
+ def __private_method(self) -> None:
pass
@property
- def public_property(self):
+ def public_property(self) -> int:
return 0
@property
- def _protected_property(self):
+ def _protected_property(self) -> int:
return 1
@property
- def __private_property(self):
+ def __private_property(self) -> int:
return 2
diff --git a/tests/utils/test_logger.py b/tests/utils/test_logger.py
index bf3bc1df2f..8370d5e8a6 100644
--- a/tests/utils/test_logger.py
+++ b/tests/utils/test_logger.py
@@ -8,46 +8,46 @@
class TestGetLogger:
- def test_default_name(self):
+ def test_default_name(self) -> None:
"""Logger is created with default name."""
logger = _get_logger()
assert logger.name == "supervision"
- def test_custom_name(self):
+ def test_custom_name(self) -> None:
"""Logger is created with provided name."""
logger = _get_logger("supervision.test_module")
assert logger.name == "supervision.test_module"
- def test_default_level_is_info(self):
+ def test_default_level_is_info(self) -> None:
"""Logger defaults to INFO level when LOG_LEVEL env var is not set."""
with patch.dict("os.environ", {}, clear=True):
# Use a unique name to avoid cached logger state from other tests
logger = _get_logger("supervision.test_default_level")
assert logger.level == logging.INFO
- def test_explicit_level(self):
+ def test_explicit_level(self) -> None:
"""Logger uses the explicitly provided level."""
logger = _get_logger("supervision.test_explicit_level", level=logging.DEBUG)
assert logger.level == logging.DEBUG
- def test_log_level_env_var(self):
+ def test_log_level_env_var(self) -> None:
"""Logger respects the LOG_LEVEL environment variable."""
with patch.dict("os.environ", {"LOG_LEVEL": "DEBUG"}):
logger = _get_logger("supervision.test_env_level")
assert logger.level == logging.DEBUG
- def test_log_level_env_var_warning(self):
+ def test_log_level_env_var_warning(self) -> None:
"""Logger respects the LOG_LEVEL=WARNING environment variable."""
with patch.dict("os.environ", {"LOG_LEVEL": "WARNING"}):
logger = _get_logger("supervision.test_env_warning")
assert logger.level == logging.WARNING
- def test_two_handlers_configured(self):
+ def test_two_handlers_configured(self) -> None:
"""Logger has exactly two handlers: one for stdout, one for stderr."""
logger = _get_logger("supervision.test_handlers")
assert len(logger.handlers) == 2
- def test_stdout_handler_present(self):
+ def test_stdout_handler_present(self) -> None:
"""Logger has a StreamHandler pointing to stdout."""
logger = _get_logger("supervision.test_stdout")
stdout_handlers = [
@@ -57,7 +57,7 @@ def test_stdout_handler_present(self):
]
assert len(stdout_handlers) == 1
- def test_stderr_handler_present(self):
+ def test_stderr_handler_present(self) -> None:
"""Logger has a StreamHandler pointing to stderr for warnings."""
logger = _get_logger("supervision.test_stderr")
stderr_handlers = [
@@ -67,12 +67,12 @@ def test_stderr_handler_present(self):
]
assert len(stderr_handlers) == 1
- def test_no_propagation(self):
+ def test_no_propagation(self) -> None:
"""Logger does not propagate to the root logger."""
logger = _get_logger("supervision.test_propagation")
assert not logger.propagate
- def test_idempotent_no_duplicate_handlers(self):
+ def test_idempotent_no_duplicate_handlers(self) -> None:
"""Calling _get_logger twice with the same name does not add extra handlers."""
name = "supervision.test_idempotent"
logger1 = _get_logger(name)
diff --git a/tests/utils/test_video.py b/tests/utils/test_video.py
index c2df5a4607..2310ec8020 100644
--- a/tests/utils/test_video.py
+++ b/tests/utils/test_video.py
@@ -26,7 +26,7 @@ def dummy_video_path(tmp_path):
return path
-def test_process_video_exception_handling(dummy_video_path, tmp_path):
+def test_process_video_exception_handling(dummy_video_path, tmp_path) -> None:
"""
Verify that process_video correctly propagates exceptions from the callback.
@@ -49,7 +49,7 @@ def callback_with_exception(frame, index):
)
-def test_process_video_success(dummy_video_path, tmp_path):
+def test_process_video_success(dummy_video_path, tmp_path) -> None:
"""
Verify successful video processing with a pass-through callback.
@@ -70,7 +70,7 @@ def callback_success(frame, index):
assert os.path.exists(target_path)
-def test_process_video_exception_with_small_buffer(dummy_video_path, tmp_path):
+def test_process_video_exception_with_small_buffer(dummy_video_path, tmp_path) -> None:
"""
Verify that process_video handles exceptions correctly even with small buffers.
@@ -94,7 +94,7 @@ def callback_with_exception(frame, index):
)
-def test_process_video_max_frames(dummy_video_path, tmp_path):
+def test_process_video_max_frames(dummy_video_path, tmp_path) -> None:
"""
Verify that process_video respects the max_frames parameter.
@@ -120,7 +120,7 @@ def callback(frame, index):
assert processed_indices == [0, 1, 2, 3, 4]
-def test_process_video_custom_params(dummy_video_path, tmp_path):
+def test_process_video_custom_params(dummy_video_path, tmp_path) -> None:
"""
Verify that process_video works correctly with custom performance parameters.
@@ -145,7 +145,7 @@ def callback(frame, index):
assert os.path.exists(target_path)
-def test_video_info(dummy_video_path):
+def test_video_info(dummy_video_path) -> None:
"""
Verify that VideoInfo correctly retrieves metadata from a video file.
@@ -162,7 +162,7 @@ def test_video_info(dummy_video_path):
assert video_info.resolution_wh == (640, 480)
-def test_video_info_float_fps(dummy_video_path, monkeypatch):
+def test_video_info_float_fps(dummy_video_path, monkeypatch) -> None:
"""
Verify that VideoInfo preserves non-integer FPS values as floats.
@@ -185,7 +185,7 @@ def mocked_get(self, prop_id):
assert video_info.fps != int(video_info.fps)
-def test_get_video_frames_generator(dummy_video_path):
+def test_get_video_frames_generator(dummy_video_path) -> None:
"""
Verify that get_video_frames_generator yields frames with correct shapes.
@@ -200,7 +200,7 @@ def test_get_video_frames_generator(dummy_video_path):
assert all(frame.shape == (480, 640, 3) for frame in frames)
-def test_get_video_frames_generator_with_stride(dummy_video_path):
+def test_get_video_frames_generator_with_stride(dummy_video_path) -> None:
"""
Verify that get_video_frames_generator correctly handles the stride parameter.
@@ -213,7 +213,7 @@ def test_get_video_frames_generator_with_stride(dummy_video_path):
assert len(frames) == 5
-def test_process_video_preserve_audio_calls_mux(dummy_video_path, tmp_path):
+def test_process_video_preserve_audio_calls_mux(dummy_video_path, tmp_path) -> None:
"""
Verify that process_video calls _mux_audio when preserve_audio=True.
@@ -235,7 +235,7 @@ def test_process_video_preserve_audio_calls_mux(dummy_video_path, tmp_path):
)
-def test_process_video_no_audio_by_default(dummy_video_path, tmp_path):
+def test_process_video_no_audio_by_default(dummy_video_path, tmp_path) -> None:
"""
Verify that process_video does not call _mux_audio when preserve_audio=False.
@@ -272,7 +272,7 @@ def test_process_video_no_audio_by_default(dummy_video_path, tmp_path):
)
def test_mux_audio_file_unchanged_on_failure(
dummy_video_path, tmp_path, which_rv, run_kwargs
-):
+) -> None:
"""_mux_audio leaves the output file unchanged when muxing cannot complete."""
target_path = str(tmp_path / "video.mp4")
shutil.copy(dummy_video_path, target_path)
@@ -287,7 +287,7 @@ def test_mux_audio_file_unchanged_on_failure(
assert os.path.getsize(target_path) == original_size
-def test_mux_audio_replaces_file_on_success(dummy_video_path, tmp_path):
+def test_mux_audio_replaces_file_on_success(dummy_video_path, tmp_path) -> None:
"""_mux_audio calls os.replace with video_path as destination on success."""
target_path = str(tmp_path / "video.mp4")
shutil.copy(dummy_video_path, target_path)
@@ -307,7 +307,7 @@ def test_mux_audio_replaces_file_on_success(dummy_video_path, tmp_path):
assert mock_replace.call_args[0][1] == target_path
-def test_get_video_frames_generator_with_start_end(dummy_video_path):
+def test_get_video_frames_generator_with_start_end(dummy_video_path) -> None:
"""
Verify that get_video_frames_generator respects start and end frame indices.
diff --git a/uv.lock b/uv.lock
index bd3804448b..77be3138f4 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,6 +1,6 @@
version = 1
revision = 3
-requires-python = ">=3.9"
+requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.12' and sys_platform == 'darwin'",
"python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'",
@@ -8,12 +8,9 @@ resolution-markers = [
"python_full_version == '3.11.*' and sys_platform == 'darwin'",
"python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
+ "python_full_version < '3.11' and sys_platform == 'darwin'",
+ "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",
+ "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
[[package]]
@@ -57,8 +54,8 @@ name = "argon2-cffi-bindings"
version = "21.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "cffi", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and sys_platform == 'darwin') or (platform_machine == 'arm64' and sys_platform == 'darwin')" },
- { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.10' and platform_machine != 'arm64') or sys_platform != 'darwin'" },
+ { name = "cffi", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" },
+ { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'darwin'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911, upload-time = "2021-12-01T08:52:55.68Z" }
wheels = [
@@ -184,7 +181,7 @@ name = "build"
version = "1.2.2.post1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "colorama", marker = "(python_full_version < '3.10' and os_name == 'nt' and platform_machine != 'arm64' and sys_platform == 'darwin') or (os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" },
+ { name = "colorama", marker = "(os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" },
{ name = "importlib-metadata", marker = "python_full_version < '3.10.2'" },
{ name = "packaging" },
{ name = "pyproject-hooks" },
@@ -209,8 +206,8 @@ name = "cairocffi"
version = "1.7.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "cffi", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and sys_platform == 'darwin') or (platform_machine == 'arm64' and sys_platform == 'darwin')" },
- { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.10' and platform_machine != 'arm64') or sys_platform != 'darwin'" },
+ { name = "cffi", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" },
+ { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'darwin'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/70/c5/1a4dc131459e68a173cbdab5fad6b524f53f9c1ef7861b7698e998b837cc/cairocffi-1.7.1.tar.gz", hash = "sha256:2e48ee864884ec4a3a34bfa8c9ab9999f688286eb714a15a43ec9d068c36557b", size = 88096, upload-time = "2024-06-18T10:56:06.741Z" }
wheels = [
@@ -249,11 +246,10 @@ source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.12' and sys_platform == 'darwin'",
"python_full_version == '3.11.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
+ "python_full_version < '3.11' and sys_platform == 'darwin'",
]
dependencies = [
- { name = "pycparser", marker = "(python_full_version >= '3.10' and sys_platform == 'darwin') or (platform_machine == 'arm64' and sys_platform == 'darwin')" },
+ { name = "pycparser", marker = "sys_platform == 'darwin'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" }
wheels = [
@@ -265,8 +261,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" },
{ url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" },
{ url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" },
- { url = "https://files.pythonhosted.org/packages/b9/ea/8bb50596b8ffbc49ddd7a1ad305035daa770202a6b782fc164647c2673ad/cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", size = 182220, upload-time = "2024-09-04T20:45:01.577Z" },
- { url = "https://files.pythonhosted.org/packages/ae/11/e77c8cd24f58285a82c23af484cf5b124a376b32644e445960d1a4654c3a/cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", size = 178605, upload-time = "2024-09-04T20:45:03.837Z" },
]
[[package]]
@@ -278,18 +272,14 @@ resolution-markers = [
"(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
+ "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",
+ "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
dependencies = [
- { name = "pycparser", marker = "(python_full_version < '3.10' and implementation_name != 'PyPy' and platform_machine != 'arm64') or (implementation_name != 'PyPy' and sys_platform != 'darwin')" },
+ { name = "pycparser", marker = "implementation_name != 'PyPy' and sys_platform != 'darwin'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" },
- { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" },
{ url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" },
{ url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" },
{ url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" },
@@ -300,8 +290,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" },
{ url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" },
{ url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" },
- { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" },
- { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" },
{ url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" },
{ url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" },
{ url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" },
@@ -313,8 +301,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" },
{ url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" },
{ url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" },
- { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
- { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
{ url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
{ url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
{ url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
@@ -325,8 +311,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
{ url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
{ url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
- { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
- { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
{ url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
{ url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
{ url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
@@ -337,8 +321,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
- { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
- { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
@@ -348,8 +330,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
- { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
- { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
@@ -359,49 +339,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
- { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", size = 184288, upload-time = "2025-09-08T23:23:48.404Z" },
- { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", size = 180509, upload-time = "2025-09-08T23:23:49.73Z" },
- { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813, upload-time = "2025-09-08T23:23:51.263Z" },
- { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", size = 216498, upload-time = "2025-09-08T23:23:52.494Z" },
- { url = "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", size = 203243, upload-time = "2025-09-08T23:23:53.836Z" },
- { url = "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", size = 203158, upload-time = "2025-09-08T23:23:55.169Z" },
- { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", size = 216548, upload-time = "2025-09-08T23:23:56.506Z" },
- { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", size = 218897, upload-time = "2025-09-08T23:23:57.825Z" },
- { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", size = 211249, upload-time = "2025-09-08T23:23:59.139Z" },
- { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", size = 218041, upload-time = "2025-09-08T23:24:00.496Z" },
- { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", size = 172138, upload-time = "2025-09-08T23:24:01.7Z" },
- { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794, upload-time = "2025-09-08T23:24:02.943Z" },
-]
-
-[[package]]
-name = "cfgv"
-version = "3.4.0"
-source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
-sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" },
]
[[package]]
name = "cfgv"
version = "3.5.0"
source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version >= '3.12' and sys_platform == 'darwin'",
- "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.11.*' and sys_platform == 'darwin'",
- "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
@@ -474,56 +417,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
{ url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
{ url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
- { url = "https://files.pythonhosted.org/packages/28/f8/dfb01ff6cc9af38552c69c9027501ff5a5117c4cc18dcd27cb5259fa1888/charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", size = 201671, upload-time = "2025-05-02T08:34:12.696Z" },
- { url = "https://files.pythonhosted.org/packages/32/fb/74e26ee556a9dbfe3bd264289b67be1e6d616329403036f6507bb9f3f29c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", size = 144744, upload-time = "2025-05-02T08:34:14.665Z" },
- { url = "https://files.pythonhosted.org/packages/ad/06/8499ee5aa7addc6f6d72e068691826ff093329fe59891e83b092ae4c851c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", size = 154993, upload-time = "2025-05-02T08:34:17.134Z" },
- { url = "https://files.pythonhosted.org/packages/f1/a2/5e4c187680728219254ef107a6949c60ee0e9a916a5dadb148c7ae82459c/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", size = 147382, upload-time = "2025-05-02T08:34:19.081Z" },
- { url = "https://files.pythonhosted.org/packages/4c/fe/56aca740dda674f0cc1ba1418c4d84534be51f639b5f98f538b332dc9a95/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", size = 149536, upload-time = "2025-05-02T08:34:21.073Z" },
- { url = "https://files.pythonhosted.org/packages/53/13/db2e7779f892386b589173dd689c1b1e304621c5792046edd8a978cbf9e0/charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", size = 151349, upload-time = "2025-05-02T08:34:23.193Z" },
- { url = "https://files.pythonhosted.org/packages/69/35/e52ab9a276186f729bce7a0638585d2982f50402046e4b0faa5d2c3ef2da/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", size = 146365, upload-time = "2025-05-02T08:34:25.187Z" },
- { url = "https://files.pythonhosted.org/packages/a6/d8/af7333f732fc2e7635867d56cb7c349c28c7094910c72267586947561b4b/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", size = 154499, upload-time = "2025-05-02T08:34:27.359Z" },
- { url = "https://files.pythonhosted.org/packages/7a/3d/a5b2e48acef264d71e036ff30bcc49e51bde80219bb628ba3e00cf59baac/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", size = 157735, upload-time = "2025-05-02T08:34:29.798Z" },
- { url = "https://files.pythonhosted.org/packages/85/d8/23e2c112532a29f3eef374375a8684a4f3b8e784f62b01da931186f43494/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", size = 154786, upload-time = "2025-05-02T08:34:31.858Z" },
- { url = "https://files.pythonhosted.org/packages/c7/57/93e0169f08ecc20fe82d12254a200dfaceddc1c12a4077bf454ecc597e33/charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", size = 150203, upload-time = "2025-05-02T08:34:33.88Z" },
- { url = "https://files.pythonhosted.org/packages/2c/9d/9bf2b005138e7e060d7ebdec7503d0ef3240141587651f4b445bdf7286c2/charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", size = 98436, upload-time = "2025-05-02T08:34:35.907Z" },
- { url = "https://files.pythonhosted.org/packages/6d/24/5849d46cf4311bbf21b424c443b09b459f5b436b1558c04e45dbb7cc478b/charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", size = 105772, upload-time = "2025-05-02T08:34:37.935Z" },
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
]
-[[package]]
-name = "click"
-version = "8.1.8"
-source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
-dependencies = [
- { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
-]
-
[[package]]
name = "click"
version = "8.2.1"
source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version >= '3.12' and sys_platform == 'darwin'",
- "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.11.*' and sys_platform == 'darwin'",
- "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
dependencies = [
- { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
+ { name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" }
wheels = [
@@ -551,103 +453,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180, upload-time = "2024-03-12T16:53:39.226Z" },
]
-[[package]]
-name = "contourpy"
-version = "1.3.0"
-source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
-dependencies = [
- { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/f5/f6/31a8f28b4a2a4fa0e01085e542f3081ab0588eff8e589d39d775172c9792/contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4", size = 13464370, upload-time = "2024-08-27T21:00:03.328Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/6c/e0/be8dcc796cfdd96708933e0e2da99ba4bb8f9b2caa9d560a50f3f09a65f3/contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7", size = 265366, upload-time = "2024-08-27T20:50:09.947Z" },
- { url = "https://files.pythonhosted.org/packages/50/d6/c953b400219443535d412fcbbc42e7a5e823291236bc0bb88936e3cc9317/contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42", size = 249226, upload-time = "2024-08-27T20:50:16.1Z" },
- { url = "https://files.pythonhosted.org/packages/6f/b4/6fffdf213ffccc28483c524b9dad46bb78332851133b36ad354b856ddc7c/contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7", size = 308460, upload-time = "2024-08-27T20:50:22.536Z" },
- { url = "https://files.pythonhosted.org/packages/cf/6c/118fc917b4050f0afe07179a6dcbe4f3f4ec69b94f36c9e128c4af480fb8/contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab", size = 347623, upload-time = "2024-08-27T20:50:28.806Z" },
- { url = "https://files.pythonhosted.org/packages/f9/a4/30ff110a81bfe3abf7b9673284d21ddce8cc1278f6f77393c91199da4c90/contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589", size = 317761, upload-time = "2024-08-27T20:50:35.126Z" },
- { url = "https://files.pythonhosted.org/packages/99/e6/d11966962b1aa515f5586d3907ad019f4b812c04e4546cc19ebf62b5178e/contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41", size = 322015, upload-time = "2024-08-27T20:50:40.318Z" },
- { url = "https://files.pythonhosted.org/packages/4d/e3/182383743751d22b7b59c3c753277b6aee3637049197624f333dac5b4c80/contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d", size = 1262672, upload-time = "2024-08-27T20:50:55.643Z" },
- { url = "https://files.pythonhosted.org/packages/78/53/974400c815b2e605f252c8fb9297e2204347d1755a5374354ee77b1ea259/contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223", size = 1321688, upload-time = "2024-08-27T20:51:11.293Z" },
- { url = "https://files.pythonhosted.org/packages/52/29/99f849faed5593b2926a68a31882af98afbeac39c7fdf7de491d9c85ec6a/contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f", size = 171145, upload-time = "2024-08-27T20:51:15.2Z" },
- { url = "https://files.pythonhosted.org/packages/a9/97/3f89bba79ff6ff2b07a3cbc40aa693c360d5efa90d66e914f0ff03b95ec7/contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b", size = 216019, upload-time = "2024-08-27T20:51:19.365Z" },
- { url = "https://files.pythonhosted.org/packages/b3/1f/9375917786cb39270b0ee6634536c0e22abf225825602688990d8f5c6c19/contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad", size = 266356, upload-time = "2024-08-27T20:51:24.146Z" },
- { url = "https://files.pythonhosted.org/packages/05/46/9256dd162ea52790c127cb58cfc3b9e3413a6e3478917d1f811d420772ec/contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49", size = 250915, upload-time = "2024-08-27T20:51:28.683Z" },
- { url = "https://files.pythonhosted.org/packages/e1/5d/3056c167fa4486900dfbd7e26a2fdc2338dc58eee36d490a0ed3ddda5ded/contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66", size = 310443, upload-time = "2024-08-27T20:51:33.675Z" },
- { url = "https://files.pythonhosted.org/packages/ca/c2/1a612e475492e07f11c8e267ea5ec1ce0d89971be496c195e27afa97e14a/contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081", size = 348548, upload-time = "2024-08-27T20:51:39.322Z" },
- { url = "https://files.pythonhosted.org/packages/45/cf/2c2fc6bb5874158277b4faf136847f0689e1b1a1f640a36d76d52e78907c/contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1", size = 319118, upload-time = "2024-08-27T20:51:44.717Z" },
- { url = "https://files.pythonhosted.org/packages/03/33/003065374f38894cdf1040cef474ad0546368eea7e3a51d48b8a423961f8/contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d", size = 323162, upload-time = "2024-08-27T20:51:49.683Z" },
- { url = "https://files.pythonhosted.org/packages/42/80/e637326e85e4105a802e42959f56cff2cd39a6b5ef68d5d9aee3ea5f0e4c/contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c", size = 1265396, upload-time = "2024-08-27T20:52:04.926Z" },
- { url = "https://files.pythonhosted.org/packages/7c/3b/8cbd6416ca1bbc0202b50f9c13b2e0b922b64be888f9d9ee88e6cfabfb51/contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb", size = 1324297, upload-time = "2024-08-27T20:52:21.843Z" },
- { url = "https://files.pythonhosted.org/packages/4d/2c/021a7afaa52fe891f25535506cc861c30c3c4e5a1c1ce94215e04b293e72/contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c", size = 171808, upload-time = "2024-08-27T20:52:25.163Z" },
- { url = "https://files.pythonhosted.org/packages/8d/2f/804f02ff30a7fae21f98198828d0857439ec4c91a96e20cf2d6c49372966/contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67", size = 217181, upload-time = "2024-08-27T20:52:29.13Z" },
- { url = "https://files.pythonhosted.org/packages/c9/92/8e0bbfe6b70c0e2d3d81272b58c98ac69ff1a4329f18c73bd64824d8b12e/contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f", size = 267838, upload-time = "2024-08-27T20:52:33.911Z" },
- { url = "https://files.pythonhosted.org/packages/e3/04/33351c5d5108460a8ce6d512307690b023f0cfcad5899499f5c83b9d63b1/contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6", size = 251549, upload-time = "2024-08-27T20:52:39.179Z" },
- { url = "https://files.pythonhosted.org/packages/51/3d/aa0fe6ae67e3ef9f178389e4caaaa68daf2f9024092aa3c6032e3d174670/contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639", size = 303177, upload-time = "2024-08-27T20:52:44.789Z" },
- { url = "https://files.pythonhosted.org/packages/56/c3/c85a7e3e0cab635575d3b657f9535443a6f5d20fac1a1911eaa4bbe1aceb/contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c", size = 341735, upload-time = "2024-08-27T20:52:51.05Z" },
- { url = "https://files.pythonhosted.org/packages/dd/8d/20f7a211a7be966a53f474bc90b1a8202e9844b3f1ef85f3ae45a77151ee/contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06", size = 314679, upload-time = "2024-08-27T20:52:58.473Z" },
- { url = "https://files.pythonhosted.org/packages/6e/be/524e377567defac0e21a46e2a529652d165fed130a0d8a863219303cee18/contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09", size = 320549, upload-time = "2024-08-27T20:53:06.593Z" },
- { url = "https://files.pythonhosted.org/packages/0f/96/fdb2552a172942d888915f3a6663812e9bc3d359d53dafd4289a0fb462f0/contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd", size = 1263068, upload-time = "2024-08-27T20:53:23.442Z" },
- { url = "https://files.pythonhosted.org/packages/2a/25/632eab595e3140adfa92f1322bf8915f68c932bac468e89eae9974cf1c00/contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35", size = 1322833, upload-time = "2024-08-27T20:53:39.243Z" },
- { url = "https://files.pythonhosted.org/packages/73/e3/69738782e315a1d26d29d71a550dbbe3eb6c653b028b150f70c1a5f4f229/contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb", size = 172681, upload-time = "2024-08-27T20:53:43.05Z" },
- { url = "https://files.pythonhosted.org/packages/0c/89/9830ba00d88e43d15e53d64931e66b8792b46eb25e2050a88fec4a0df3d5/contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b", size = 218283, upload-time = "2024-08-27T20:53:47.232Z" },
- { url = "https://files.pythonhosted.org/packages/53/a1/d20415febfb2267af2d7f06338e82171824d08614084714fb2c1dac9901f/contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3", size = 267879, upload-time = "2024-08-27T20:53:51.597Z" },
- { url = "https://files.pythonhosted.org/packages/aa/45/5a28a3570ff6218d8bdfc291a272a20d2648104815f01f0177d103d985e1/contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7", size = 251573, upload-time = "2024-08-27T20:53:55.659Z" },
- { url = "https://files.pythonhosted.org/packages/39/1c/d3f51540108e3affa84f095c8b04f0aa833bb797bc8baa218a952a98117d/contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84", size = 303184, upload-time = "2024-08-27T20:54:00.225Z" },
- { url = "https://files.pythonhosted.org/packages/00/56/1348a44fb6c3a558c1a3a0cd23d329d604c99d81bf5a4b58c6b71aab328f/contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0", size = 340262, upload-time = "2024-08-27T20:54:05.234Z" },
- { url = "https://files.pythonhosted.org/packages/2b/23/00d665ba67e1bb666152131da07e0f24c95c3632d7722caa97fb61470eca/contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b", size = 313806, upload-time = "2024-08-27T20:54:09.889Z" },
- { url = "https://files.pythonhosted.org/packages/5a/42/3cf40f7040bb8362aea19af9a5fb7b32ce420f645dd1590edcee2c657cd5/contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da", size = 319710, upload-time = "2024-08-27T20:54:14.536Z" },
- { url = "https://files.pythonhosted.org/packages/05/32/f3bfa3fc083b25e1a7ae09197f897476ee68e7386e10404bdf9aac7391f0/contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14", size = 1264107, upload-time = "2024-08-27T20:54:29.735Z" },
- { url = "https://files.pythonhosted.org/packages/1c/1e/1019d34473a736664f2439542b890b2dc4c6245f5c0d8cdfc0ccc2cab80c/contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8", size = 1322458, upload-time = "2024-08-27T20:54:45.507Z" },
- { url = "https://files.pythonhosted.org/packages/22/85/4f8bfd83972cf8909a4d36d16b177f7b8bdd942178ea4bf877d4a380a91c/contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294", size = 172643, upload-time = "2024-08-27T20:55:52.754Z" },
- { url = "https://files.pythonhosted.org/packages/cc/4a/fb3c83c1baba64ba90443626c228ca14f19a87c51975d3b1de308dd2cf08/contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087", size = 218301, upload-time = "2024-08-27T20:55:56.509Z" },
- { url = "https://files.pythonhosted.org/packages/76/65/702f4064f397821fea0cb493f7d3bc95a5d703e20954dce7d6d39bacf378/contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8", size = 278972, upload-time = "2024-08-27T20:54:50.347Z" },
- { url = "https://files.pythonhosted.org/packages/80/85/21f5bba56dba75c10a45ec00ad3b8190dbac7fd9a8a8c46c6116c933e9cf/contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b", size = 263375, upload-time = "2024-08-27T20:54:54.909Z" },
- { url = "https://files.pythonhosted.org/packages/0a/64/084c86ab71d43149f91ab3a4054ccf18565f0a8af36abfa92b1467813ed6/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973", size = 307188, upload-time = "2024-08-27T20:55:00.184Z" },
- { url = "https://files.pythonhosted.org/packages/3d/ff/d61a4c288dc42da0084b8d9dc2aa219a850767165d7d9a9c364ff530b509/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18", size = 345644, upload-time = "2024-08-27T20:55:05.673Z" },
- { url = "https://files.pythonhosted.org/packages/ca/aa/00d2313d35ec03f188e8f0786c2fc61f589306e02fdc158233697546fd58/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8", size = 317141, upload-time = "2024-08-27T20:55:11.047Z" },
- { url = "https://files.pythonhosted.org/packages/8d/6a/b5242c8cb32d87f6abf4f5e3044ca397cb1a76712e3fa2424772e3ff495f/contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6", size = 323469, upload-time = "2024-08-27T20:55:15.914Z" },
- { url = "https://files.pythonhosted.org/packages/6f/a6/73e929d43028a9079aca4bde107494864d54f0d72d9db508a51ff0878593/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2", size = 1260894, upload-time = "2024-08-27T20:55:31.553Z" },
- { url = "https://files.pythonhosted.org/packages/2b/1e/1e726ba66eddf21c940821df8cf1a7d15cb165f0682d62161eaa5e93dae1/contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927", size = 1314829, upload-time = "2024-08-27T20:55:47.837Z" },
- { url = "https://files.pythonhosted.org/packages/b3/e3/b9f72758adb6ef7397327ceb8b9c39c75711affb220e4f53c745ea1d5a9a/contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8", size = 265518, upload-time = "2024-08-27T20:56:01.333Z" },
- { url = "https://files.pythonhosted.org/packages/ec/22/19f5b948367ab5260fb41d842c7a78dae645603881ea6bc39738bcfcabf6/contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c", size = 249350, upload-time = "2024-08-27T20:56:05.432Z" },
- { url = "https://files.pythonhosted.org/packages/26/76/0c7d43263dd00ae21a91a24381b7e813d286a3294d95d179ef3a7b9fb1d7/contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca", size = 309167, upload-time = "2024-08-27T20:56:10.034Z" },
- { url = "https://files.pythonhosted.org/packages/96/3b/cadff6773e89f2a5a492c1a8068e21d3fccaf1a1c1df7d65e7c8e3ef60ba/contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f", size = 348279, upload-time = "2024-08-27T20:56:15.41Z" },
- { url = "https://files.pythonhosted.org/packages/e1/86/158cc43aa549d2081a955ab11c6bdccc7a22caacc2af93186d26f5f48746/contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc", size = 318519, upload-time = "2024-08-27T20:56:21.813Z" },
- { url = "https://files.pythonhosted.org/packages/05/11/57335544a3027e9b96a05948c32e566328e3a2f84b7b99a325b7a06d2b06/contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2", size = 321922, upload-time = "2024-08-27T20:56:26.983Z" },
- { url = "https://files.pythonhosted.org/packages/0b/e3/02114f96543f4a1b694333b92a6dcd4f8eebbefcc3a5f3bbb1316634178f/contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e", size = 1258017, upload-time = "2024-08-27T20:56:42.246Z" },
- { url = "https://files.pythonhosted.org/packages/f3/3b/bfe4c81c6d5881c1c643dde6620be0b42bf8aab155976dd644595cfab95c/contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800", size = 1316773, upload-time = "2024-08-27T20:56:58.58Z" },
- { url = "https://files.pythonhosted.org/packages/f1/17/c52d2970784383cafb0bd918b6fb036d98d96bbf0bc1befb5d1e31a07a70/contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5", size = 171353, upload-time = "2024-08-27T20:57:02.718Z" },
- { url = "https://files.pythonhosted.org/packages/53/23/db9f69676308e094d3c45f20cc52e12d10d64f027541c995d89c11ad5c75/contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843", size = 211817, upload-time = "2024-08-27T20:57:06.328Z" },
- { url = "https://files.pythonhosted.org/packages/d1/09/60e486dc2b64c94ed33e58dcfb6f808192c03dfc5574c016218b9b7680dc/contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c", size = 261886, upload-time = "2024-08-27T20:57:10.863Z" },
- { url = "https://files.pythonhosted.org/packages/19/20/b57f9f7174fcd439a7789fb47d764974ab646fa34d1790551de386457a8e/contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779", size = 311008, upload-time = "2024-08-27T20:57:15.588Z" },
- { url = "https://files.pythonhosted.org/packages/74/fc/5040d42623a1845d4f17a418e590fd7a79ae8cb2bad2b2f83de63c3bdca4/contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4", size = 215690, upload-time = "2024-08-27T20:57:19.321Z" },
- { url = "https://files.pythonhosted.org/packages/2b/24/dc3dcd77ac7460ab7e9d2b01a618cb31406902e50e605a8d6091f0a8f7cc/contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0", size = 261894, upload-time = "2024-08-27T20:57:23.873Z" },
- { url = "https://files.pythonhosted.org/packages/b1/db/531642a01cfec39d1682e46b5457b07cf805e3c3c584ec27e2a6223f8f6c/contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102", size = 311099, upload-time = "2024-08-27T20:57:28.58Z" },
- { url = "https://files.pythonhosted.org/packages/38/1e/94bda024d629f254143a134eead69e21c836429a2a6ce82209a00ddcb79a/contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb", size = 215838, upload-time = "2024-08-27T20:57:32.913Z" },
-]
-
[[package]]
name = "contourpy"
version = "1.3.2"
source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version >= '3.12' and sys_platform == 'darwin'",
- "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.11.*' and sys_platform == 'darwin'",
- "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
dependencies = [
- { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" }
@@ -710,142 +521,10 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" },
]
-[[package]]
-name = "coverage"
-version = "7.10.7"
-source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
-sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" },
- { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" },
- { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" },
- { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" },
- { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" },
- { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" },
- { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" },
- { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" },
- { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" },
- { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" },
- { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" },
- { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" },
- { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" },
- { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" },
- { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" },
- { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" },
- { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" },
- { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" },
- { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" },
- { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" },
- { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" },
- { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" },
- { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" },
- { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" },
- { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" },
- { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" },
- { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" },
- { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" },
- { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" },
- { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" },
- { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" },
- { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" },
- { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" },
- { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" },
- { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" },
- { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" },
- { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" },
- { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" },
- { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" },
- { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" },
- { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" },
- { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" },
- { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" },
- { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" },
- { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" },
- { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" },
- { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" },
- { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" },
- { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" },
- { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" },
- { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" },
- { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" },
- { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" },
- { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" },
- { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" },
- { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" },
- { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" },
- { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" },
- { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" },
- { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" },
- { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" },
- { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" },
- { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" },
- { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" },
- { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" },
- { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" },
- { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" },
- { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" },
- { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" },
- { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" },
- { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" },
- { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" },
- { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" },
- { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" },
- { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" },
- { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" },
- { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" },
- { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" },
- { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" },
- { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" },
- { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" },
- { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" },
- { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" },
- { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" },
- { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" },
- { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" },
- { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" },
- { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" },
- { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" },
- { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" },
- { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" },
- { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" },
- { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" },
- { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" },
- { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" },
- { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" },
- { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" },
- { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" },
- { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" },
- { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" },
- { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" },
- { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" },
- { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" },
-]
-
-[package.optional-dependencies]
-toml = [
- { name = "tomli", marker = "python_full_version < '3.10'" },
-]
-
[[package]]
name = "coverage"
version = "7.13.1"
source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version >= '3.12' and sys_platform == 'darwin'",
- "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.11.*' and sys_platform == 'darwin'",
- "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2d/9a/3742e58fd04b233df95c012ee9f3dfe04708a5e1d32613bd2d47d4e1be0d/coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147", size = 218633, upload-time = "2025-12-28T15:40:10.165Z" },
@@ -943,7 +622,7 @@ wheels = [
[package.optional-dependencies]
toml = [
- { name = "tomli", marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" },
+ { name = "tomli", marker = "python_full_version <= '3.11'" },
]
[[package]]
@@ -951,8 +630,8 @@ name = "cryptography"
version = "46.0.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.10' and platform_machine != 'arm64' and platform_python_implementation != 'PyPy') or (platform_python_implementation != 'PyPy' and sys_platform != 'darwin')" },
- { name = "typing-extensions", marker = "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform != 'darwin')" },
+ { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_python_implementation != 'PyPy' and sys_platform != 'darwin'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.11' and sys_platform != 'darwin'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" }
wheels = [
@@ -1033,10 +712,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/f1/6f2ee3f991327ad9e4c2f8b82611a467052a0fb0e247390192580e89f7ff/debugpy-1.8.14-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f920c7f9af409d90f5fd26e313e119d908b0dd2952c2393cd3247a462331f15", size = 4217514, upload-time = "2025-04-10T19:46:34.336Z" },
{ url = "https://files.pythonhosted.org/packages/79/28/b9d146f8f2dc535c236ee09ad3e5ac899adb39d7a19b49f03ac95d216beb/debugpy-1.8.14-cp313-cp313-win32.whl", hash = "sha256:3784ec6e8600c66cbdd4ca2726c72d8ca781e94bce2f396cc606d458146f8f4e", size = 5254756, upload-time = "2025-04-10T19:46:36.199Z" },
{ url = "https://files.pythonhosted.org/packages/e0/62/a7b4a57013eac4ccaef6977966e6bec5c63906dd25a86e35f155952e29a1/debugpy-1.8.14-cp313-cp313-win_amd64.whl", hash = "sha256:684eaf43c95a3ec39a96f1f5195a7ff3d4144e4a18d69bb66beeb1a6de605d6e", size = 5297119, upload-time = "2025-04-10T19:46:38.141Z" },
- { url = "https://files.pythonhosted.org/packages/85/6f/96ba96545f55b6a675afa08c96b42810de9b18c7ad17446bbec82762127a/debugpy-1.8.14-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:413512d35ff52c2fb0fd2d65e69f373ffd24f0ecb1fac514c04a668599c5ce7f", size = 2077696, upload-time = "2025-04-10T19:46:46.817Z" },
- { url = "https://files.pythonhosted.org/packages/fa/84/f378a2dd837d94de3c85bca14f1db79f8fcad7e20b108b40d59da56a6d22/debugpy-1.8.14-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c9156f7524a0d70b7a7e22b2e311d8ba76a15496fb00730e46dcdeedb9e1eea", size = 3554846, upload-time = "2025-04-10T19:46:48.72Z" },
- { url = "https://files.pythonhosted.org/packages/db/52/88824fe5d6893f59933f664c6e12783749ab537a2101baf5c713164d8aa2/debugpy-1.8.14-cp39-cp39-win32.whl", hash = "sha256:b44985f97cc3dd9d52c42eb59ee9d7ee0c4e7ecd62bca704891f997de4cef23d", size = 5209350, upload-time = "2025-04-10T19:46:50.284Z" },
- { url = "https://files.pythonhosted.org/packages/41/35/72e9399be24a04cb72cfe1284572c9fcd1d742c7fa23786925c18fa54ad8/debugpy-1.8.14-cp39-cp39-win_amd64.whl", hash = "sha256:b1528cfee6c1b1c698eb10b6b096c598738a8238822d218173d21c3086de8123", size = 5241852, upload-time = "2025-04-10T19:46:52.022Z" },
{ url = "https://files.pythonhosted.org/packages/97/1a/481f33c37ee3ac8040d3d51fc4c4e4e7e61cb08b8bc8971d6032acc2279f/debugpy-1.8.14-py2.py3-none-any.whl", hash = "sha256:5cd9a579d553b6cb9759a7908a41988ee6280b961f24f63336835d9418216a20", size = 5256230, upload-time = "2025-04-10T19:46:54.077Z" },
]
@@ -1106,35 +781,10 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/90/2b/0817a2b257fe88725c25589d89aec060581aabf668707a8d03b2e9e0cb2a/fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667", size = 23924, upload-time = "2024-12-02T10:55:07.599Z" },
]
-[[package]]
-name = "filelock"
-version = "3.18.0"
-source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
-sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" },
-]
-
[[package]]
name = "filelock"
version = "3.20.3"
source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version >= '3.12' and sys_platform == 'darwin'",
- "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.11.*' and sys_platform == 'darwin'",
- "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" },
@@ -1194,14 +844,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/42/07/d6f775d950ee8a841012472c7303f8819423d8cc3b4530915de7265ebfa2/fonttools-4.60.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82cceceaf9c09a965a75b84a4b240dd3768e596ffb65ef53852681606fe7c9ba", size = 5002036, upload-time = "2025-12-09T13:37:42.639Z" },
{ url = "https://files.pythonhosted.org/packages/73/f6/ba6458f83ce1a9f8c3b17bd8f7b8a2205a126aac1055796b7e7cfebbd38f/fonttools-4.60.2-cp314-cp314t-win32.whl", hash = "sha256:bbfbc918a75437fe7e6d64d1b1e1f713237df1cf00f3a36dedae910b2ba01cee", size = 2330985, upload-time = "2025-12-09T13:37:45.157Z" },
{ url = "https://files.pythonhosted.org/packages/91/24/fea0ba4d3a32d4ed1103a1098bfd99dc78b5fe3bb97202920744a37b73dc/fonttools-4.60.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0e5cd9b0830f6550d58c84f3ab151a9892b50c4f9d538c5603c0ce6fff2eb3f1", size = 2396226, upload-time = "2025-12-09T13:37:47.355Z" },
- { url = "https://files.pythonhosted.org/packages/55/ae/a6d9446cb258d3fe87e311c2d7bacf8e8da3e5809fbdc3a8306db4f6b14e/fonttools-4.60.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3c75b8b42f7f93906bdba9eb1197bb76aecbe9a0a7cf6feec75f7605b5e8008", size = 2857184, upload-time = "2025-12-09T13:37:49.96Z" },
- { url = "https://files.pythonhosted.org/packages/3a/f3/1b41d0b6a8b908aa07f652111155dd653ebbf0b3385e66562556c5206685/fonttools-4.60.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0f86c8c37bc0ec0b9c141d5e90c717ff614e93c187f06d80f18c7057097f71bc", size = 2401877, upload-time = "2025-12-09T13:37:52.307Z" },
- { url = "https://files.pythonhosted.org/packages/71/57/048fd781680c38b05c5463657d0d95d5f2391a51972176e175c01de29d42/fonttools-4.60.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe905403fe59683b0e9a45f234af2866834376b8821f34633b1c76fb731b6311", size = 4878073, upload-time = "2025-12-09T13:37:56.477Z" },
- { url = "https://files.pythonhosted.org/packages/45/bb/363364f052a893cebd3d449588b21244a9d873620fda03ad92702d2e1bc7/fonttools-4.60.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38ce703b60a906e421e12d9e3a7f064883f5e61bb23e8961f4be33cfe578500b", size = 4835385, upload-time = "2025-12-09T13:37:58.882Z" },
- { url = "https://files.pythonhosted.org/packages/1c/38/e392bb930b2436287e6021672345db26441bf1f85f1e98f8b9784334e41d/fonttools-4.60.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9e810c06f3e79185cecf120e58b343ea5a89b54dd695fd644446bcf8c026da5e", size = 4853084, upload-time = "2025-12-09T13:38:01.578Z" },
- { url = "https://files.pythonhosted.org/packages/65/60/0d77faeaecf7a3276a8a6dc49e2274357e6b3ed6a1774e2fdb2a7f142db0/fonttools-4.60.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:38faec8cc1d12122599814d15a402183f5123fb7608dac956121e7c6742aebc5", size = 4971144, upload-time = "2025-12-09T13:38:03.748Z" },
- { url = "https://files.pythonhosted.org/packages/ba/c7/6d3ac3afbcd598631bce24c3ecb919e7d0644a82fea8ddc4454312fc0be6/fonttools-4.60.2-cp39-cp39-win32.whl", hash = "sha256:80a45cf7bf659acb7b36578f300231873daba67bd3ca8cce181c73f861f14a37", size = 1499411, upload-time = "2025-12-09T13:38:05.586Z" },
- { url = "https://files.pythonhosted.org/packages/5a/1c/9dedf6420e23f9fa630bb97941839dddd2e1e57d1b2b85a902378dbe0bd2/fonttools-4.60.2-cp39-cp39-win_amd64.whl", hash = "sha256:c355d5972071938e1b1e0f5a1df001f68ecf1a62f34a3407dc8e0beccf052501", size = 1547943, upload-time = "2025-12-09T13:38:07.604Z" },
{ url = "https://files.pythonhosted.org/packages/79/6c/10280af05b44fafd1dff69422805061fa1af29270bc52dce031ac69540bf/fonttools-4.60.2-py3-none-any.whl", hash = "sha256:73cf92eeda67cf6ff10c8af56fc8f4f07c1647d989a979be9e388a49be26552a", size = 1144610, upload-time = "2025-12-09T13:38:09.5Z" },
]
@@ -1311,35 +953,10 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/cb/18326d2d89ad3b0dd143da971e77afd1e6ca6674f1b1c3df4b6bec6279fc/id-1.5.0-py3-none-any.whl", hash = "sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658", size = 13611, upload-time = "2024-12-04T19:53:03.02Z" },
]
-[[package]]
-name = "identify"
-version = "2.6.15"
-source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
-sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" },
-]
-
[[package]]
name = "identify"
version = "2.6.16"
source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version >= '3.12' and sys_platform == 'darwin'",
- "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.11.*' and sys_platform == 'darwin'",
- "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360, upload-time = "2026-01-12T18:58:58.201Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" },
@@ -1370,9 +987,6 @@ wheels = [
name = "importlib-resources"
version = "6.5.2"
source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "zipp", marker = "python_full_version < '3.10'" },
-]
sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" },
@@ -1395,8 +1009,7 @@ dependencies = [
{ name = "appnope", marker = "sys_platform == 'darwin'" },
{ name = "comm" },
{ name = "debugpy" },
- { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "ipython", version = "9.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "jupyter-client" },
{ name = "jupyter-core" },
@@ -1413,54 +1026,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173, upload-time = "2024-07-01T14:07:19.603Z" },
]
-[[package]]
-name = "ipython"
-version = "8.18.1"
-source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
-dependencies = [
- { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
- { name = "decorator", marker = "python_full_version < '3.10'" },
- { name = "exceptiongroup", marker = "python_full_version < '3.10'" },
- { name = "jedi", marker = "python_full_version < '3.10'" },
- { name = "matplotlib-inline", marker = "python_full_version < '3.10'" },
- { name = "pexpect", marker = "python_full_version < '3.10' and sys_platform != 'win32'" },
- { name = "prompt-toolkit", marker = "python_full_version < '3.10'" },
- { name = "pygments", marker = "python_full_version < '3.10'" },
- { name = "stack-data", marker = "python_full_version < '3.10'" },
- { name = "traitlets", marker = "python_full_version < '3.10'" },
- { name = "typing-extensions", marker = "python_full_version < '3.10'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/b1/b9/3ba6c45a6df813c09a48bac313c22ff83efa26cbb55011218d925a46e2ad/ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27", size = 5486330, upload-time = "2023-11-27T09:58:34.596Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/47/6b/d9fdcdef2eb6a23f391251fde8781c38d42acd82abe84d054cb74f7863b0/ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397", size = 808161, upload-time = "2023-11-27T09:58:30.538Z" },
-]
-
[[package]]
name = "ipython"
version = "8.37.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
+ "python_full_version < '3.11' and sys_platform == 'darwin'",
+ "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",
+ "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
dependencies = [
- { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" },
- { name = "decorator", marker = "python_full_version == '3.10.*'" },
- { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" },
- { name = "jedi", marker = "python_full_version == '3.10.*'" },
- { name = "matplotlib-inline", marker = "python_full_version == '3.10.*'" },
- { name = "pexpect", marker = "python_full_version == '3.10.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
- { name = "prompt-toolkit", marker = "python_full_version == '3.10.*'" },
- { name = "pygments", marker = "python_full_version == '3.10.*'" },
- { name = "stack-data", marker = "python_full_version == '3.10.*'" },
- { name = "traitlets", marker = "python_full_version == '3.10.*'" },
- { name = "typing-extensions", marker = "python_full_version == '3.10.*'" },
+ { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" },
+ { name = "decorator", marker = "python_full_version < '3.11'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "jedi", marker = "python_full_version < '3.11'" },
+ { name = "matplotlib-inline", marker = "python_full_version < '3.11'" },
+ { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
+ { name = "prompt-toolkit", marker = "python_full_version < '3.11'" },
+ { name = "pygments", marker = "python_full_version < '3.11'" },
+ { name = "stack-data", marker = "python_full_version < '3.11'" },
+ { name = "traitlets", marker = "python_full_version < '3.11'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088, upload-time = "2025-05-31T16:39:09.613Z" }
wheels = [
@@ -1515,8 +1101,7 @@ version = "8.1.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "comm" },
- { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "ipython", version = "9.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "jupyterlab-widgets" },
{ name = "traitlets" },
@@ -1670,7 +1255,6 @@ name = "jupyter-client"
version = "8.6.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "jupyter-core" },
{ name = "python-dateutil" },
{ name = "pyzmq" },
@@ -1720,7 +1304,6 @@ name = "jupyter-lsp"
version = "2.2.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "jupyter-server" },
]
sdist = { url = "https://files.pythonhosted.org/packages/85/b4/3200b0b09c12bc3b72d943d923323c398eff382d1dcc7c0dbc8b74630e40/jupyter-lsp-2.2.5.tar.gz", hash = "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001", size = 48741, upload-time = "2024-04-09T17:59:44.918Z" }
@@ -1745,7 +1328,7 @@ dependencies = [
{ name = "overrides", marker = "python_full_version < '3.12'" },
{ name = "packaging" },
{ name = "prometheus-client" },
- { name = "pywinpty", marker = "(python_full_version < '3.10' and os_name == 'nt' and platform_machine != 'arm64' and sys_platform == 'darwin') or (os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" },
+ { name = "pywinpty", marker = "(os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" },
{ name = "pyzmq" },
{ name = "send2trash" },
{ name = "terminado" },
@@ -1763,7 +1346,7 @@ name = "jupyter-server-terminals"
version = "0.5.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pywinpty", marker = "(python_full_version < '3.10' and os_name == 'nt' and platform_machine != 'arm64' and sys_platform == 'darwin') or (os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" },
+ { name = "pywinpty", marker = "(os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" },
{ name = "terminado" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430, upload-time = "2024-03-12T14:37:03.049Z" }
@@ -1778,7 +1361,6 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "async-lru" },
{ name = "httpx" },
- { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "ipykernel" },
{ name = "jinja2" },
{ name = "jupyter-core" },
@@ -1812,7 +1394,6 @@ version = "2.28.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "babel" },
- { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "jinja2" },
{ name = "json5" },
{ name = "jsonschema" },
@@ -1869,126 +1450,10 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" },
]
-[[package]]
-name = "kiwisolver"
-version = "1.4.7"
-source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
-sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286, upload-time = "2024-09-04T09:39:44.302Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/97/14/fc943dd65268a96347472b4fbe5dcc2f6f55034516f80576cd0dd3a8930f/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6", size = 122440, upload-time = "2024-09-04T09:03:44.9Z" },
- { url = "https://files.pythonhosted.org/packages/1e/46/e68fed66236b69dd02fcdb506218c05ac0e39745d696d22709498896875d/kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17", size = 65758, upload-time = "2024-09-04T09:03:46.582Z" },
- { url = "https://files.pythonhosted.org/packages/ef/fa/65de49c85838681fc9cb05de2a68067a683717321e01ddafb5b8024286f0/kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9", size = 64311, upload-time = "2024-09-04T09:03:47.973Z" },
- { url = "https://files.pythonhosted.org/packages/42/9c/cc8d90f6ef550f65443bad5872ffa68f3dee36de4974768628bea7c14979/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9", size = 1637109, upload-time = "2024-09-04T09:03:49.281Z" },
- { url = "https://files.pythonhosted.org/packages/55/91/0a57ce324caf2ff5403edab71c508dd8f648094b18cfbb4c8cc0fde4a6ac/kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c", size = 1617814, upload-time = "2024-09-04T09:03:51.444Z" },
- { url = "https://files.pythonhosted.org/packages/12/5d/c36140313f2510e20207708adf36ae4919416d697ee0236b0ddfb6fd1050/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599", size = 1400881, upload-time = "2024-09-04T09:03:53.357Z" },
- { url = "https://files.pythonhosted.org/packages/56/d0/786e524f9ed648324a466ca8df86298780ef2b29c25313d9a4f16992d3cf/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05", size = 1512972, upload-time = "2024-09-04T09:03:55.082Z" },
- { url = "https://files.pythonhosted.org/packages/67/5a/77851f2f201e6141d63c10a0708e996a1363efaf9e1609ad0441b343763b/kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407", size = 1444787, upload-time = "2024-09-04T09:03:56.588Z" },
- { url = "https://files.pythonhosted.org/packages/06/5f/1f5eaab84355885e224a6fc8d73089e8713dc7e91c121f00b9a1c58a2195/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278", size = 2199212, upload-time = "2024-09-04T09:03:58.557Z" },
- { url = "https://files.pythonhosted.org/packages/b5/28/9152a3bfe976a0ae21d445415defc9d1cd8614b2910b7614b30b27a47270/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5", size = 2346399, upload-time = "2024-09-04T09:04:00.178Z" },
- { url = "https://files.pythonhosted.org/packages/26/f6/453d1904c52ac3b400f4d5e240ac5fec25263716723e44be65f4d7149d13/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad", size = 2308688, upload-time = "2024-09-04T09:04:02.216Z" },
- { url = "https://files.pythonhosted.org/packages/5a/9a/d4968499441b9ae187e81745e3277a8b4d7c60840a52dc9d535a7909fac3/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895", size = 2445493, upload-time = "2024-09-04T09:04:04.571Z" },
- { url = "https://files.pythonhosted.org/packages/07/c9/032267192e7828520dacb64dfdb1d74f292765f179e467c1cba97687f17d/kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3", size = 2262191, upload-time = "2024-09-04T09:04:05.969Z" },
- { url = "https://files.pythonhosted.org/packages/6c/ad/db0aedb638a58b2951da46ddaeecf204be8b4f5454df020d850c7fa8dca8/kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc", size = 46644, upload-time = "2024-09-04T09:04:07.408Z" },
- { url = "https://files.pythonhosted.org/packages/12/ca/d0f7b7ffbb0be1e7c2258b53554efec1fd652921f10d7d85045aff93ab61/kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c", size = 55877, upload-time = "2024-09-04T09:04:08.869Z" },
- { url = "https://files.pythonhosted.org/packages/97/6c/cfcc128672f47a3e3c0d918ecb67830600078b025bfc32d858f2e2d5c6a4/kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a", size = 48347, upload-time = "2024-09-04T09:04:10.106Z" },
- { url = "https://files.pythonhosted.org/packages/e9/44/77429fa0a58f941d6e1c58da9efe08597d2e86bf2b2cce6626834f49d07b/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54", size = 122442, upload-time = "2024-09-04T09:04:11.432Z" },
- { url = "https://files.pythonhosted.org/packages/e5/20/8c75caed8f2462d63c7fd65e16c832b8f76cda331ac9e615e914ee80bac9/kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95", size = 65762, upload-time = "2024-09-04T09:04:12.468Z" },
- { url = "https://files.pythonhosted.org/packages/f4/98/fe010f15dc7230f45bc4cf367b012d651367fd203caaa992fd1f5963560e/kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935", size = 64319, upload-time = "2024-09-04T09:04:13.635Z" },
- { url = "https://files.pythonhosted.org/packages/8b/1b/b5d618f4e58c0675654c1e5051bcf42c776703edb21c02b8c74135541f60/kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb", size = 1334260, upload-time = "2024-09-04T09:04:14.878Z" },
- { url = "https://files.pythonhosted.org/packages/b8/01/946852b13057a162a8c32c4c8d2e9ed79f0bb5d86569a40c0b5fb103e373/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02", size = 1426589, upload-time = "2024-09-04T09:04:16.514Z" },
- { url = "https://files.pythonhosted.org/packages/70/d1/c9f96df26b459e15cf8a965304e6e6f4eb291e0f7a9460b4ad97b047561e/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51", size = 1541080, upload-time = "2024-09-04T09:04:18.322Z" },
- { url = "https://files.pythonhosted.org/packages/d3/73/2686990eb8b02d05f3de759d6a23a4ee7d491e659007dd4c075fede4b5d0/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052", size = 1470049, upload-time = "2024-09-04T09:04:20.266Z" },
- { url = "https://files.pythonhosted.org/packages/a7/4b/2db7af3ed3af7c35f388d5f53c28e155cd402a55432d800c543dc6deb731/kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18", size = 1426376, upload-time = "2024-09-04T09:04:22.419Z" },
- { url = "https://files.pythonhosted.org/packages/05/83/2857317d04ea46dc5d115f0df7e676997bbd968ced8e2bd6f7f19cfc8d7f/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545", size = 2222231, upload-time = "2024-09-04T09:04:24.526Z" },
- { url = "https://files.pythonhosted.org/packages/0d/b5/866f86f5897cd4ab6d25d22e403404766a123f138bd6a02ecb2cdde52c18/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b", size = 2368634, upload-time = "2024-09-04T09:04:25.899Z" },
- { url = "https://files.pythonhosted.org/packages/c1/ee/73de8385403faba55f782a41260210528fe3273d0cddcf6d51648202d6d0/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36", size = 2329024, upload-time = "2024-09-04T09:04:28.523Z" },
- { url = "https://files.pythonhosted.org/packages/a1/e7/cd101d8cd2cdfaa42dc06c433df17c8303d31129c9fdd16c0ea37672af91/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3", size = 2468484, upload-time = "2024-09-04T09:04:30.547Z" },
- { url = "https://files.pythonhosted.org/packages/e1/72/84f09d45a10bc57a40bb58b81b99d8f22b58b2040c912b7eb97ebf625bf2/kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523", size = 2284078, upload-time = "2024-09-04T09:04:33.218Z" },
- { url = "https://files.pythonhosted.org/packages/d2/d4/71828f32b956612dc36efd7be1788980cb1e66bfb3706e6dec9acad9b4f9/kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d", size = 46645, upload-time = "2024-09-04T09:04:34.371Z" },
- { url = "https://files.pythonhosted.org/packages/a1/65/d43e9a20aabcf2e798ad1aff6c143ae3a42cf506754bcb6a7ed8259c8425/kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b", size = 56022, upload-time = "2024-09-04T09:04:35.786Z" },
- { url = "https://files.pythonhosted.org/packages/35/b3/9f75a2e06f1b4ca00b2b192bc2b739334127d27f1d0625627ff8479302ba/kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376", size = 48536, upload-time = "2024-09-04T09:04:37.525Z" },
- { url = "https://files.pythonhosted.org/packages/97/9c/0a11c714cf8b6ef91001c8212c4ef207f772dd84540104952c45c1f0a249/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2", size = 121808, upload-time = "2024-09-04T09:04:38.637Z" },
- { url = "https://files.pythonhosted.org/packages/f2/d8/0fe8c5f5d35878ddd135f44f2af0e4e1d379e1c7b0716f97cdcb88d4fd27/kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a", size = 65531, upload-time = "2024-09-04T09:04:39.694Z" },
- { url = "https://files.pythonhosted.org/packages/80/c5/57fa58276dfdfa612241d640a64ca2f76adc6ffcebdbd135b4ef60095098/kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee", size = 63894, upload-time = "2024-09-04T09:04:41.6Z" },
- { url = "https://files.pythonhosted.org/packages/8b/e9/26d3edd4c4ad1c5b891d8747a4f81b1b0aba9fb9721de6600a4adc09773b/kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640", size = 1369296, upload-time = "2024-09-04T09:04:42.886Z" },
- { url = "https://files.pythonhosted.org/packages/b6/67/3f4850b5e6cffb75ec40577ddf54f7b82b15269cc5097ff2e968ee32ea7d/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f", size = 1461450, upload-time = "2024-09-04T09:04:46.284Z" },
- { url = "https://files.pythonhosted.org/packages/52/be/86cbb9c9a315e98a8dc6b1d23c43cffd91d97d49318854f9c37b0e41cd68/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483", size = 1579168, upload-time = "2024-09-04T09:04:47.91Z" },
- { url = "https://files.pythonhosted.org/packages/0f/00/65061acf64bd5fd34c1f4ae53f20b43b0a017a541f242a60b135b9d1e301/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258", size = 1507308, upload-time = "2024-09-04T09:04:49.465Z" },
- { url = "https://files.pythonhosted.org/packages/21/e4/c0b6746fd2eb62fe702118b3ca0cb384ce95e1261cfada58ff693aeec08a/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e", size = 1464186, upload-time = "2024-09-04T09:04:50.949Z" },
- { url = "https://files.pythonhosted.org/packages/0a/0f/529d0a9fffb4d514f2782c829b0b4b371f7f441d61aa55f1de1c614c4ef3/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107", size = 2247877, upload-time = "2024-09-04T09:04:52.388Z" },
- { url = "https://files.pythonhosted.org/packages/d1/e1/66603ad779258843036d45adcbe1af0d1a889a07af4635f8b4ec7dccda35/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948", size = 2404204, upload-time = "2024-09-04T09:04:54.385Z" },
- { url = "https://files.pythonhosted.org/packages/8d/61/de5fb1ca7ad1f9ab7970e340a5b833d735df24689047de6ae71ab9d8d0e7/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038", size = 2352461, upload-time = "2024-09-04T09:04:56.307Z" },
- { url = "https://files.pythonhosted.org/packages/ba/d2/0edc00a852e369827f7e05fd008275f550353f1f9bcd55db9363d779fc63/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383", size = 2501358, upload-time = "2024-09-04T09:04:57.922Z" },
- { url = "https://files.pythonhosted.org/packages/84/15/adc15a483506aec6986c01fb7f237c3aec4d9ed4ac10b756e98a76835933/kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520", size = 2314119, upload-time = "2024-09-04T09:04:59.332Z" },
- { url = "https://files.pythonhosted.org/packages/36/08/3a5bb2c53c89660863a5aa1ee236912269f2af8762af04a2e11df851d7b2/kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b", size = 46367, upload-time = "2024-09-04T09:05:00.804Z" },
- { url = "https://files.pythonhosted.org/packages/19/93/c05f0a6d825c643779fc3c70876bff1ac221f0e31e6f701f0e9578690d70/kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb", size = 55884, upload-time = "2024-09-04T09:05:01.924Z" },
- { url = "https://files.pythonhosted.org/packages/d2/f9/3828d8f21b6de4279f0667fb50a9f5215e6fe57d5ec0d61905914f5b6099/kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a", size = 48528, upload-time = "2024-09-04T09:05:02.983Z" },
- { url = "https://files.pythonhosted.org/packages/c4/06/7da99b04259b0f18b557a4effd1b9c901a747f7fdd84cf834ccf520cb0b2/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e", size = 121913, upload-time = "2024-09-04T09:05:04.072Z" },
- { url = "https://files.pythonhosted.org/packages/97/f5/b8a370d1aa593c17882af0a6f6755aaecd643640c0ed72dcfd2eafc388b9/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6", size = 65627, upload-time = "2024-09-04T09:05:05.119Z" },
- { url = "https://files.pythonhosted.org/packages/2a/fc/6c0374f7503522539e2d4d1b497f5ebad3f8ed07ab51aed2af988dd0fb65/kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750", size = 63888, upload-time = "2024-09-04T09:05:06.191Z" },
- { url = "https://files.pythonhosted.org/packages/bf/3e/0b7172793d0f41cae5c923492da89a2ffcd1adf764c16159ca047463ebd3/kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d", size = 1369145, upload-time = "2024-09-04T09:05:07.919Z" },
- { url = "https://files.pythonhosted.org/packages/77/92/47d050d6f6aced2d634258123f2688fbfef8ded3c5baf2c79d94d91f1f58/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379", size = 1461448, upload-time = "2024-09-04T09:05:10.01Z" },
- { url = "https://files.pythonhosted.org/packages/9c/1b/8f80b18e20b3b294546a1adb41701e79ae21915f4175f311a90d042301cf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c", size = 1578750, upload-time = "2024-09-04T09:05:11.598Z" },
- { url = "https://files.pythonhosted.org/packages/a4/fe/fe8e72f3be0a844f257cadd72689c0848c6d5c51bc1d60429e2d14ad776e/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34", size = 1507175, upload-time = "2024-09-04T09:05:13.22Z" },
- { url = "https://files.pythonhosted.org/packages/39/fa/cdc0b6105d90eadc3bee525fecc9179e2b41e1ce0293caaf49cb631a6aaf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1", size = 1463963, upload-time = "2024-09-04T09:05:15.925Z" },
- { url = "https://files.pythonhosted.org/packages/6e/5c/0c03c4e542720c6177d4f408e56d1c8315899db72d46261a4e15b8b33a41/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f", size = 2248220, upload-time = "2024-09-04T09:05:17.434Z" },
- { url = "https://files.pythonhosted.org/packages/3d/ee/55ef86d5a574f4e767df7da3a3a7ff4954c996e12d4fbe9c408170cd7dcc/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b", size = 2404463, upload-time = "2024-09-04T09:05:18.997Z" },
- { url = "https://files.pythonhosted.org/packages/0f/6d/73ad36170b4bff4825dc588acf4f3e6319cb97cd1fb3eb04d9faa6b6f212/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27", size = 2352842, upload-time = "2024-09-04T09:05:21.299Z" },
- { url = "https://files.pythonhosted.org/packages/0b/16/fa531ff9199d3b6473bb4d0f47416cdb08d556c03b8bc1cccf04e756b56d/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a", size = 2501635, upload-time = "2024-09-04T09:05:23.588Z" },
- { url = "https://files.pythonhosted.org/packages/78/7e/aa9422e78419db0cbe75fb86d8e72b433818f2e62e2e394992d23d23a583/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee", size = 2314556, upload-time = "2024-09-04T09:05:25.907Z" },
- { url = "https://files.pythonhosted.org/packages/a8/b2/15f7f556df0a6e5b3772a1e076a9d9f6c538ce5f05bd590eca8106508e06/kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07", size = 46364, upload-time = "2024-09-04T09:05:27.184Z" },
- { url = "https://files.pythonhosted.org/packages/0b/db/32e897e43a330eee8e4770bfd2737a9584b23e33587a0812b8e20aac38f7/kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76", size = 55887, upload-time = "2024-09-04T09:05:28.372Z" },
- { url = "https://files.pythonhosted.org/packages/c8/a4/df2bdca5270ca85fd25253049eb6708d4127be2ed0e5c2650217450b59e9/kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650", size = 48530, upload-time = "2024-09-04T09:05:30.225Z" },
- { url = "https://files.pythonhosted.org/packages/11/88/37ea0ea64512997b13d69772db8dcdc3bfca5442cda3a5e4bb943652ee3e/kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd", size = 122449, upload-time = "2024-09-04T09:05:55.311Z" },
- { url = "https://files.pythonhosted.org/packages/4e/45/5a5c46078362cb3882dcacad687c503089263c017ca1241e0483857791eb/kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583", size = 65757, upload-time = "2024-09-04T09:05:56.906Z" },
- { url = "https://files.pythonhosted.org/packages/8a/be/a6ae58978772f685d48dd2e84460937761c53c4bbd84e42b0336473d9775/kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417", size = 64312, upload-time = "2024-09-04T09:05:58.384Z" },
- { url = "https://files.pythonhosted.org/packages/f4/04/18ef6f452d311e1e1eb180c9bf5589187fa1f042db877e6fe443ef10099c/kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904", size = 1626966, upload-time = "2024-09-04T09:05:59.855Z" },
- { url = "https://files.pythonhosted.org/packages/21/b1/40655f6c3fa11ce740e8a964fa8e4c0479c87d6a7944b95af799c7a55dfe/kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a", size = 1607044, upload-time = "2024-09-04T09:06:02.16Z" },
- { url = "https://files.pythonhosted.org/packages/fd/93/af67dbcfb9b3323bbd2c2db1385a7139d8f77630e4a37bb945b57188eb2d/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8", size = 1391879, upload-time = "2024-09-04T09:06:03.908Z" },
- { url = "https://files.pythonhosted.org/packages/40/6f/d60770ef98e77b365d96061d090c0cd9e23418121c55fff188fa4bdf0b54/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2", size = 1504751, upload-time = "2024-09-04T09:06:05.58Z" },
- { url = "https://files.pythonhosted.org/packages/fa/3a/5f38667d313e983c432f3fcd86932177519ed8790c724e07d77d1de0188a/kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88", size = 1436990, upload-time = "2024-09-04T09:06:08.126Z" },
- { url = "https://files.pythonhosted.org/packages/cb/3b/1520301a47326e6a6043b502647e42892be33b3f051e9791cc8bb43f1a32/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde", size = 2191122, upload-time = "2024-09-04T09:06:10.345Z" },
- { url = "https://files.pythonhosted.org/packages/cf/c4/eb52da300c166239a2233f1f9c4a1b767dfab98fae27681bfb7ea4873cb6/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c", size = 2338126, upload-time = "2024-09-04T09:06:12.321Z" },
- { url = "https://files.pythonhosted.org/packages/1a/cb/42b92fd5eadd708dd9107c089e817945500685f3437ce1fd387efebc6d6e/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2", size = 2298313, upload-time = "2024-09-04T09:06:14.562Z" },
- { url = "https://files.pythonhosted.org/packages/4f/eb/be25aa791fe5fc75a8b1e0c965e00f942496bc04635c9aae8035f6b76dcd/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb", size = 2437784, upload-time = "2024-09-04T09:06:16.767Z" },
- { url = "https://files.pythonhosted.org/packages/c5/22/30a66be7f3368d76ff95689e1c2e28d382383952964ab15330a15d8bfd03/kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327", size = 2253988, upload-time = "2024-09-04T09:06:18.705Z" },
- { url = "https://files.pythonhosted.org/packages/35/d3/5f2ecb94b5211c8a04f218a76133cc8d6d153b0f9cd0b45fad79907f0689/kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644", size = 46980, upload-time = "2024-09-04T09:06:20.106Z" },
- { url = "https://files.pythonhosted.org/packages/ef/17/cd10d020578764ea91740204edc6b3236ed8106228a46f568d716b11feb2/kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4", size = 55847, upload-time = "2024-09-04T09:06:21.407Z" },
- { url = "https://files.pythonhosted.org/packages/91/84/32232502020bd78d1d12be7afde15811c64a95ed1f606c10456db4e4c3ac/kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f", size = 48494, upload-time = "2024-09-04T09:06:22.648Z" },
- { url = "https://files.pythonhosted.org/packages/ac/59/741b79775d67ab67ced9bb38552da688c0305c16e7ee24bba7a2be253fb7/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643", size = 59491, upload-time = "2024-09-04T09:06:24.188Z" },
- { url = "https://files.pythonhosted.org/packages/58/cc/fb239294c29a5656e99e3527f7369b174dd9cc7c3ef2dea7cb3c54a8737b/kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706", size = 57648, upload-time = "2024-09-04T09:06:25.559Z" },
- { url = "https://files.pythonhosted.org/packages/3b/ef/2f009ac1f7aab9f81efb2d837301d255279d618d27b6015780115ac64bdd/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6", size = 84257, upload-time = "2024-09-04T09:06:27.038Z" },
- { url = "https://files.pythonhosted.org/packages/81/e1/c64f50987f85b68b1c52b464bb5bf73e71570c0f7782d626d1eb283ad620/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2", size = 80906, upload-time = "2024-09-04T09:06:28.48Z" },
- { url = "https://files.pythonhosted.org/packages/fd/71/1687c5c0a0be2cee39a5c9c389e546f9c6e215e46b691d00d9f646892083/kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4", size = 79951, upload-time = "2024-09-04T09:06:29.966Z" },
- { url = "https://files.pythonhosted.org/packages/ea/8b/d7497df4a1cae9367adf21665dd1f896c2a7aeb8769ad77b662c5e2bcce7/kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a", size = 55715, upload-time = "2024-09-04T09:06:31.489Z" },
- { url = "https://files.pythonhosted.org/packages/d5/df/ce37d9b26f07ab90880923c94d12a6ff4d27447096b4c849bfc4339ccfdf/kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39", size = 58666, upload-time = "2024-09-04T09:06:43.756Z" },
- { url = "https://files.pythonhosted.org/packages/b0/d3/e4b04f43bc629ac8e186b77b2b1a251cdfa5b7610fa189dc0db622672ce6/kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e", size = 57088, upload-time = "2024-09-04T09:06:45.406Z" },
- { url = "https://files.pythonhosted.org/packages/30/1c/752df58e2d339e670a535514d2db4fe8c842ce459776b8080fbe08ebb98e/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608", size = 84321, upload-time = "2024-09-04T09:06:47.557Z" },
- { url = "https://files.pythonhosted.org/packages/f0/f8/fe6484e847bc6e238ec9f9828089fb2c0bb53f2f5f3a79351fde5b565e4f/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674", size = 80776, upload-time = "2024-09-04T09:06:49.235Z" },
- { url = "https://files.pythonhosted.org/packages/9b/57/d7163c0379f250ef763aba85330a19feefb5ce6cb541ade853aaba881524/kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225", size = 79984, upload-time = "2024-09-04T09:06:51.336Z" },
- { url = "https://files.pythonhosted.org/packages/8c/95/4a103776c265d13b3d2cd24fb0494d4e04ea435a8ef97e1b2c026d43250b/kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0", size = 55811, upload-time = "2024-09-04T09:06:53.078Z" },
-]
-
[[package]]
name = "kiwisolver"
version = "1.4.8"
source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version >= '3.12' and sys_platform == 'darwin'",
- "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.11.*' and sys_platform == 'darwin'",
- "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload-time = "2024-12-24T18:30:51.519Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/47/5f/4d8e9e852d98ecd26cdf8eaf7ed8bc33174033bba5e07001b289f07308fd/kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db", size = 124623, upload-time = "2024-12-24T18:28:17.687Z" },
@@ -2076,9 +1541,6 @@ wheels = [
name = "markdown"
version = "3.8.2"
source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
-]
sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" },
@@ -2152,109 +1614,23 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
- { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344, upload-time = "2024-10-18T15:21:43.721Z" },
- { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389, upload-time = "2024-10-18T15:21:44.666Z" },
- { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607, upload-time = "2024-10-18T15:21:45.452Z" },
- { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728, upload-time = "2024-10-18T15:21:46.295Z" },
- { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826, upload-time = "2024-10-18T15:21:47.134Z" },
- { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843, upload-time = "2024-10-18T15:21:48.334Z" },
- { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219, upload-time = "2024-10-18T15:21:49.587Z" },
- { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946, upload-time = "2024-10-18T15:21:50.441Z" },
- { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063, upload-time = "2024-10-18T15:21:51.385Z" },
- { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506, upload-time = "2024-10-18T15:21:52.974Z" },
-]
-
-[[package]]
-name = "matplotlib"
-version = "3.9.4"
-source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
-dependencies = [
- { name = "contourpy", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "cycler", marker = "python_full_version < '3.10'" },
- { name = "fonttools", marker = "python_full_version < '3.10'" },
- { name = "importlib-resources", marker = "python_full_version < '3.10'" },
- { name = "kiwisolver", version = "1.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "packaging", marker = "python_full_version < '3.10'" },
- { name = "pillow", marker = "python_full_version < '3.10'" },
- { name = "pyparsing", marker = "python_full_version < '3.10'" },
- { name = "python-dateutil", marker = "python_full_version < '3.10'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/df/17/1747b4154034befd0ed33b52538f5eb7752d05bb51c5e2a31470c3bc7d52/matplotlib-3.9.4.tar.gz", hash = "sha256:1e00e8be7393cbdc6fedfa8a6fba02cf3e83814b285db1c60b906a023ba41bc3", size = 36106529, upload-time = "2024-12-13T05:56:34.184Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/7e/94/27d2e2c30d54b56c7b764acc1874a909e34d1965a427fc7092bb6a588b63/matplotlib-3.9.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fdd7abfb706dfa8d307af64a87f1a862879ec3cd8d0ec8637458f0885b9c50", size = 7885089, upload-time = "2024-12-13T05:54:24.224Z" },
- { url = "https://files.pythonhosted.org/packages/c6/25/828273307e40a68eb8e9df832b6b2aaad075864fdc1de4b1b81e40b09e48/matplotlib-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d89bc4e85e40a71d1477780366c27fb7c6494d293e1617788986f74e2a03d7ff", size = 7770600, upload-time = "2024-12-13T05:54:27.214Z" },
- { url = "https://files.pythonhosted.org/packages/f2/65/f841a422ec994da5123368d76b126acf4fc02ea7459b6e37c4891b555b83/matplotlib-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddf9f3c26aae695c5daafbf6b94e4c1a30d6cd617ba594bbbded3b33a1fcfa26", size = 8200138, upload-time = "2024-12-13T05:54:29.497Z" },
- { url = "https://files.pythonhosted.org/packages/07/06/272aca07a38804d93b6050813de41ca7ab0e29ba7a9dd098e12037c919a9/matplotlib-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18ebcf248030173b59a868fda1fe42397253f6698995b55e81e1f57431d85e50", size = 8312711, upload-time = "2024-12-13T05:54:34.396Z" },
- { url = "https://files.pythonhosted.org/packages/98/37/f13e23b233c526b7e27ad61be0a771894a079e0f7494a10d8d81557e0e9a/matplotlib-3.9.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974896ec43c672ec23f3f8c648981e8bc880ee163146e0312a9b8def2fac66f5", size = 9090622, upload-time = "2024-12-13T05:54:36.808Z" },
- { url = "https://files.pythonhosted.org/packages/4f/8c/b1f5bd2bd70e60f93b1b54c4d5ba7a992312021d0ddddf572f9a1a6d9348/matplotlib-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:4598c394ae9711cec135639374e70871fa36b56afae17bdf032a345be552a88d", size = 7828211, upload-time = "2024-12-13T05:54:40.596Z" },
- { url = "https://files.pythonhosted.org/packages/74/4b/65be7959a8fa118a3929b49a842de5b78bb55475236fcf64f3e308ff74a0/matplotlib-3.9.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4dd29641d9fb8bc4492420c5480398dd40a09afd73aebe4eb9d0071a05fbe0c", size = 7894430, upload-time = "2024-12-13T05:54:44.049Z" },
- { url = "https://files.pythonhosted.org/packages/e9/18/80f70d91896e0a517b4a051c3fd540daa131630fd75e02e250365353b253/matplotlib-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30e5b22e8bcfb95442bf7d48b0d7f3bdf4a450cbf68986ea45fca3d11ae9d099", size = 7780045, upload-time = "2024-12-13T05:54:46.414Z" },
- { url = "https://files.pythonhosted.org/packages/a2/73/ccb381026e3238c5c25c3609ba4157b2d1a617ec98d65a8b4ee4e1e74d02/matplotlib-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bb0030d1d447fd56dcc23b4c64a26e44e898f0416276cac1ebc25522e0ac249", size = 8209906, upload-time = "2024-12-13T05:54:49.459Z" },
- { url = "https://files.pythonhosted.org/packages/ab/33/1648da77b74741c89f5ea95cbf42a291b4b364f2660b316318811404ed97/matplotlib-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aca90ed222ac3565d2752b83dbb27627480d27662671e4d39da72e97f657a423", size = 8322873, upload-time = "2024-12-13T05:54:53.066Z" },
- { url = "https://files.pythonhosted.org/packages/57/d3/8447ba78bc6593c9044c372d1609f8ea10fb1e071e7a9e0747bea74fc16c/matplotlib-3.9.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a181b2aa2906c608fcae72f977a4a2d76e385578939891b91c2550c39ecf361e", size = 9099566, upload-time = "2024-12-13T05:54:55.522Z" },
- { url = "https://files.pythonhosted.org/packages/23/e1/4f0e237bf349c02ff9d1b6e7109f1a17f745263809b9714a8576dc17752b/matplotlib-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:1f6882828231eca17f501c4dcd98a05abb3f03d157fbc0769c6911fe08b6cfd3", size = 7838065, upload-time = "2024-12-13T05:54:58.337Z" },
- { url = "https://files.pythonhosted.org/packages/1a/2b/c918bf6c19d6445d1cefe3d2e42cb740fb997e14ab19d4daeb6a7ab8a157/matplotlib-3.9.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dfc48d67e6661378a21c2983200a654b72b5c5cdbd5d2cf6e5e1ece860f0cc70", size = 7891131, upload-time = "2024-12-13T05:55:02.837Z" },
- { url = "https://files.pythonhosted.org/packages/c1/e5/b4e8fc601ca302afeeabf45f30e706a445c7979a180e3a978b78b2b681a4/matplotlib-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47aef0fab8332d02d68e786eba8113ffd6f862182ea2999379dec9e237b7e483", size = 7776365, upload-time = "2024-12-13T05:55:05.158Z" },
- { url = "https://files.pythonhosted.org/packages/99/06/b991886c506506476e5d83625c5970c656a491b9f80161458fed94597808/matplotlib-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fba1f52c6b7dc764097f52fd9ab627b90db452c9feb653a59945de16752e965f", size = 8200707, upload-time = "2024-12-13T05:55:09.48Z" },
- { url = "https://files.pythonhosted.org/packages/c3/e2/556b627498cb27e61026f2d1ba86a78ad1b836fef0996bef5440e8bc9559/matplotlib-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173ac3748acaac21afcc3fa1633924609ba1b87749006bc25051c52c422a5d00", size = 8313761, upload-time = "2024-12-13T05:55:12.95Z" },
- { url = "https://files.pythonhosted.org/packages/58/ff/165af33ec766ff818306ea88e91f9f60d2a6ed543be1eb122a98acbf3b0d/matplotlib-3.9.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320edea0cadc07007765e33f878b13b3738ffa9745c5f707705692df70ffe0e0", size = 9095284, upload-time = "2024-12-13T05:55:16.199Z" },
- { url = "https://files.pythonhosted.org/packages/9f/8b/3d0c7a002db3b1ed702731c2a9a06d78d035f1f2fb0fb936a8e43cc1e9f4/matplotlib-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a4a4cfc82330b27042a7169533da7991e8789d180dd5b3daeaee57d75cd5a03b", size = 7841160, upload-time = "2024-12-13T05:55:19.991Z" },
- { url = "https://files.pythonhosted.org/packages/49/b1/999f89a7556d101b23a2f0b54f1b6e140d73f56804da1398f2f0bc0924bc/matplotlib-3.9.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37eeffeeca3c940985b80f5b9a7b95ea35671e0e7405001f249848d2b62351b6", size = 7891499, upload-time = "2024-12-13T05:55:22.142Z" },
- { url = "https://files.pythonhosted.org/packages/87/7b/06a32b13a684977653396a1bfcd34d4e7539c5d55c8cbfaa8ae04d47e4a9/matplotlib-3.9.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3e7465ac859ee4abcb0d836137cd8414e7bb7ad330d905abced457217d4f0f45", size = 7776802, upload-time = "2024-12-13T05:55:25.947Z" },
- { url = "https://files.pythonhosted.org/packages/65/87/ac498451aff739e515891bbb92e566f3c7ef31891aaa878402a71f9b0910/matplotlib-3.9.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c12302c34afa0cf061bea23b331e747e5e554b0fa595c96e01c7b75bc3b858", size = 8200802, upload-time = "2024-12-13T05:55:28.461Z" },
- { url = "https://files.pythonhosted.org/packages/f8/6b/9eb761c00e1cb838f6c92e5f25dcda3f56a87a52f6cb8fdfa561e6cf6a13/matplotlib-3.9.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b8c97917f21b75e72108b97707ba3d48f171541a74aa2a56df7a40626bafc64", size = 8313880, upload-time = "2024-12-13T05:55:30.965Z" },
- { url = "https://files.pythonhosted.org/packages/d7/a2/c8eaa600e2085eec7e38cbbcc58a30fc78f8224939d31d3152bdafc01fd1/matplotlib-3.9.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0229803bd7e19271b03cb09f27db76c918c467aa4ce2ae168171bc67c3f508df", size = 9094637, upload-time = "2024-12-13T05:55:33.701Z" },
- { url = "https://files.pythonhosted.org/packages/71/1f/c6e1daea55b7bfeb3d84c6cb1abc449f6a02b181e7e2a5e4db34c3afb793/matplotlib-3.9.4-cp313-cp313-win_amd64.whl", hash = "sha256:7c0d8ef442ebf56ff5e206f8083d08252ee738e04f3dc88ea882853a05488799", size = 7841311, upload-time = "2024-12-13T05:55:36.737Z" },
- { url = "https://files.pythonhosted.org/packages/c0/3a/2757d3f7d388b14dd48f5a83bea65b6d69f000e86b8f28f74d86e0d375bd/matplotlib-3.9.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a04c3b00066a688834356d196136349cb32f5e1003c55ac419e91585168b88fb", size = 7919989, upload-time = "2024-12-13T05:55:39.024Z" },
- { url = "https://files.pythonhosted.org/packages/24/28/f5077c79a4f521589a37fe1062d6a6ea3534e068213f7357e7cfffc2e17a/matplotlib-3.9.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04c519587f6c210626741a1e9a68eefc05966ede24205db8982841826af5871a", size = 7809417, upload-time = "2024-12-13T05:55:42.412Z" },
- { url = "https://files.pythonhosted.org/packages/36/c8/c523fd2963156692916a8eb7d4069084cf729359f7955cf09075deddfeaf/matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308afbf1a228b8b525fcd5cec17f246bbbb63b175a3ef6eb7b4d33287ca0cf0c", size = 8226258, upload-time = "2024-12-13T05:55:47.259Z" },
- { url = "https://files.pythonhosted.org/packages/f6/88/499bf4b8fa9349b6f5c0cf4cead0ebe5da9d67769129f1b5651e5ac51fbc/matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb3b02246ddcffd3ce98e88fed5b238bc5faff10dbbaa42090ea13241d15764", size = 8335849, upload-time = "2024-12-13T05:55:49.763Z" },
- { url = "https://files.pythonhosted.org/packages/b8/9f/20a4156b9726188646a030774ee337d5ff695a965be45ce4dbcb9312c170/matplotlib-3.9.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8a75287e9cb9eee48cb79ec1d806f75b29c0fde978cb7223a1f4c5848d696041", size = 9102152, upload-time = "2024-12-13T05:55:51.997Z" },
- { url = "https://files.pythonhosted.org/packages/10/11/237f9c3a4e8d810b1759b67ff2da7c32c04f9c80aa475e7beb36ed43a8fb/matplotlib-3.9.4-cp313-cp313t-win_amd64.whl", hash = "sha256:488deb7af140f0ba86da003e66e10d55ff915e152c78b4b66d231638400b1965", size = 7896987, upload-time = "2024-12-13T05:55:55.941Z" },
- { url = "https://files.pythonhosted.org/packages/56/eb/501b465c9fef28f158e414ea3a417913dc2ac748564c7ed41535f23445b4/matplotlib-3.9.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3c3724d89a387ddf78ff88d2a30ca78ac2b4c89cf37f2db4bd453c34799e933c", size = 7885919, upload-time = "2024-12-13T05:55:59.66Z" },
- { url = "https://files.pythonhosted.org/packages/da/36/236fbd868b6c91309a5206bd90c3f881f4f44b2d997cd1d6239ef652f878/matplotlib-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d5f0a8430ffe23d7e32cfd86445864ccad141797f7d25b7c41759a5b5d17cfd7", size = 7771486, upload-time = "2024-12-13T05:56:04.264Z" },
- { url = "https://files.pythonhosted.org/packages/e0/4b/105caf2d54d5ed11d9f4335398f5103001a03515f2126c936a752ccf1461/matplotlib-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bb0141a21aef3b64b633dc4d16cbd5fc538b727e4958be82a0e1c92a234160e", size = 8201838, upload-time = "2024-12-13T05:56:06.792Z" },
- { url = "https://files.pythonhosted.org/packages/5d/a7/bb01188fb4013d34d274caf44a2f8091255b0497438e8b6c0a7c1710c692/matplotlib-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57aa235109e9eed52e2c2949db17da185383fa71083c00c6c143a60e07e0888c", size = 8314492, upload-time = "2024-12-13T05:56:09.964Z" },
- { url = "https://files.pythonhosted.org/packages/33/19/02e1a37f7141fc605b193e927d0a9cdf9dc124a20b9e68793f4ffea19695/matplotlib-3.9.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b18c600061477ccfdd1e6fd050c33d8be82431700f3452b297a56d9ed7037abb", size = 9092500, upload-time = "2024-12-13T05:56:13.55Z" },
- { url = "https://files.pythonhosted.org/packages/57/68/c2feb4667adbf882ffa4b3e0ac9967f848980d9f8b5bebd86644aa67ce6a/matplotlib-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:ef5f2d1b67d2d2145ff75e10f8c008bfbf71d45137c4b648c87193e7dd053eac", size = 7822962, upload-time = "2024-12-13T05:56:16.358Z" },
- { url = "https://files.pythonhosted.org/packages/0c/22/2ef6a364cd3f565442b0b055e0599744f1e4314ec7326cdaaa48a4d864d7/matplotlib-3.9.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:44e0ed786d769d85bc787b0606a53f2d8d2d1d3c8a2608237365e9121c1a338c", size = 7877995, upload-time = "2024-12-13T05:56:18.805Z" },
- { url = "https://files.pythonhosted.org/packages/87/b8/2737456e566e9f4d94ae76b8aa0d953d9acb847714f9a7ad80184474f5be/matplotlib-3.9.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:09debb9ce941eb23ecdbe7eab972b1c3e0276dcf01688073faff7b0f61d6c6ca", size = 7769300, upload-time = "2024-12-13T05:56:21.315Z" },
- { url = "https://files.pythonhosted.org/packages/b2/1f/e709c6ec7b5321e6568769baa288c7178e60a93a9da9e682b39450da0e29/matplotlib-3.9.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc53cf157a657bfd03afab14774d54ba73aa84d42cfe2480c91bd94873952db", size = 8313423, upload-time = "2024-12-13T05:56:26.719Z" },
- { url = "https://files.pythonhosted.org/packages/5e/b6/5a1f868782cd13f053a679984e222007ecff654a9bfbac6b27a65f4eeb05/matplotlib-3.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ad45da51be7ad02387801fd154ef74d942f49fe3fcd26a64c94842ba7ec0d865", size = 7854624, upload-time = "2024-12-13T05:56:29.359Z" },
]
[[package]]
name = "matplotlib"
version = "3.10.3"
source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version >= '3.12' and sys_platform == 'darwin'",
- "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.11.*' and sys_platform == 'darwin'",
- "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
dependencies = [
- { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
- { name = "cycler", marker = "python_full_version >= '3.10'" },
- { name = "fonttools", marker = "python_full_version >= '3.10'" },
- { name = "kiwisolver", version = "1.4.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
- { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "contourpy" },
+ { name = "cycler" },
+ { name = "fonttools" },
+ { name = "kiwisolver" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
- { name = "packaging", marker = "python_full_version >= '3.10'" },
- { name = "pillow", marker = "python_full_version >= '3.10'" },
- { name = "pyparsing", marker = "python_full_version >= '3.10'" },
- { name = "python-dateutil", marker = "python_full_version >= '3.10'" },
+ { name = "packaging" },
+ { name = "pillow" },
+ { name = "pyparsing" },
+ { name = "python-dateutil" },
]
sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" }
wheels = [
@@ -2371,11 +1747,9 @@ name = "mkdocs"
version = "1.6.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "click" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "ghp-import" },
- { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "jinja2" },
{ name = "markdown" },
{ name = "markupsafe" },
@@ -2411,7 +1785,6 @@ name = "mkdocs-get-deps"
version = "0.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "mergedeep" },
{ name = "platformdirs" },
{ name = "pyyaml" },
@@ -2509,7 +1882,6 @@ name = "mkdocstrings"
version = "0.29.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "jinja2" },
{ name = "markdown" },
{ name = "markupsafe" },
@@ -2569,7 +1941,6 @@ dependencies = [
{ name = "beautifulsoup4" },
{ name = "bleach", extra = ["css"] },
{ name = "defusedxml" },
- { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "jinja2" },
{ name = "jupyter-core" },
{ name = "jupyterlab-pygments" },
@@ -2679,71 +2050,14 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" },
]
-[[package]]
-name = "numpy"
-version = "2.0.2"
-source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
-sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" },
- { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" },
- { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" },
- { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" },
- { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" },
- { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" },
- { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" },
- { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" },
- { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" },
- { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" },
- { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" },
- { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" },
- { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" },
- { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" },
- { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" },
- { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" },
- { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" },
- { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" },
- { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" },
- { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" },
- { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" },
- { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" },
- { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" },
- { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" },
- { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" },
- { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" },
- { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" },
- { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" },
- { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" },
- { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" },
- { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" },
- { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" },
- { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" },
- { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" },
- { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" },
- { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" },
- { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" },
- { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" },
- { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" },
- { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" },
- { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" },
- { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" },
- { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" },
- { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" },
-]
-
[[package]]
name = "numpy"
version = "2.2.6"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
+ "python_full_version < '3.11' and sys_platform == 'darwin'",
+ "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",
+ "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
wheels = [
@@ -2874,8 +2188,7 @@ name = "opencv-python"
version = "4.11.0.86"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956, upload-time = "2025-01-16T13:52:24.737Z" }
@@ -2920,8 +2233,7 @@ name = "pandas"
version = "2.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "python-dateutil" },
{ name = "pytz" },
@@ -2963,13 +2275,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/85/86/1fa345fc17caf5d7780d2699985c03dbe186c68fee00b526813939062bb0/pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", size = 11883998, upload-time = "2025-07-07T19:19:34.267Z" },
{ url = "https://files.pythonhosted.org/packages/81/aa/e58541a49b5e6310d89474333e994ee57fea97c8aaa8fc7f00b873059bbf/pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", size = 12704705, upload-time = "2025-07-07T19:19:36.856Z" },
{ url = "https://files.pythonhosted.org/packages/d5/f9/07086f5b0f2a19872554abeea7658200824f5835c58a106fa8f2ae96a46c/pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", size = 13189044, upload-time = "2025-07-07T19:19:39.999Z" },
- { url = "https://files.pythonhosted.org/packages/6e/21/ecf2df680982616459409b09962a8c2065330c7151dc6538069f3b634acf/pandas-2.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4645f770f98d656f11c69e81aeb21c6fca076a44bed3dcbb9396a4311bc7f6d8", size = 11567275, upload-time = "2025-07-07T19:19:45.152Z" },
- { url = "https://files.pythonhosted.org/packages/1e/1a/dcb50e44b75419e96b276c9fb023b0f147b3c411be1cd517492aa2a184d4/pandas-2.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:342e59589cc454aaff7484d75b816a433350b3d7964d7847327edda4d532a2e3", size = 10811488, upload-time = "2025-07-07T19:19:47.797Z" },
- { url = "https://files.pythonhosted.org/packages/2d/55/66cd2b679f6a27398380eac7574bc24746128f74626a3c02b978ea00e5ce/pandas-2.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d12f618d80379fde6af007f65f0c25bd3e40251dbd1636480dfffce2cf1e6da", size = 11763000, upload-time = "2025-07-07T19:19:50.83Z" },
- { url = "https://files.pythonhosted.org/packages/ae/1c/5b9b263c80fd5e231b77df6f78cd7426d1d4ad3a4e858e85b7b3d93d0e9c/pandas-2.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd71c47a911da120d72ef173aeac0bf5241423f9bfea57320110a978457e069e", size = 12361395, upload-time = "2025-07-07T19:19:53.714Z" },
- { url = "https://files.pythonhosted.org/packages/f7/74/7e817b31413fbb96366ea327d43d1926a9c48c58074e27e094e2839a0e36/pandas-2.3.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:09e3b1587f0f3b0913e21e8b32c3119174551deb4a4eba4a89bc7377947977e7", size = 13225086, upload-time = "2025-07-07T19:19:56.378Z" },
- { url = "https://files.pythonhosted.org/packages/1f/0f/bc0a44b47eba2f22ae4235719a573d552ef7ad76ed3ea39ae62d554e040b/pandas-2.3.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2323294c73ed50f612f67e2bf3ae45aea04dce5690778e08a09391897f35ff88", size = 13871698, upload-time = "2025-07-07T19:19:58.854Z" },
- { url = "https://files.pythonhosted.org/packages/fa/cb/6c32f8fadefa4314b740fbe8f74f6a02423bd1549e7c930826df35ac3c1b/pandas-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4b0de34dc8499c2db34000ef8baad684cfa4cbd836ecee05f323ebfba348c7d", size = 11357186, upload-time = "2025-07-07T19:20:01.475Z" },
]
[[package]]
@@ -3097,17 +2402,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" },
{ url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" },
{ url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" },
- { url = "https://files.pythonhosted.org/packages/9e/8e/9c089f01677d1264ab8648352dcb7773f37da6ad002542760c80107da816/pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f", size = 5316478, upload-time = "2025-07-01T09:15:52.209Z" },
- { url = "https://files.pythonhosted.org/packages/b5/a9/5749930caf674695867eb56a581e78eb5f524b7583ff10b01b6e5048acb3/pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081", size = 4686522, upload-time = "2025-07-01T09:15:54.162Z" },
- { url = "https://files.pythonhosted.org/packages/43/46/0b85b763eb292b691030795f9f6bb6fcaf8948c39413c81696a01c3577f7/pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4", size = 5853376, upload-time = "2025-07-03T13:11:01.066Z" },
- { url = "https://files.pythonhosted.org/packages/5e/c6/1a230ec0067243cbd60bc2dad5dc3ab46a8a41e21c15f5c9b52b26873069/pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc", size = 7626020, upload-time = "2025-07-03T13:11:06.479Z" },
- { url = "https://files.pythonhosted.org/packages/63/dd/f296c27ffba447bfad76c6a0c44c1ea97a90cb9472b9304c94a732e8dbfb/pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06", size = 5956732, upload-time = "2025-07-01T09:15:56.111Z" },
- { url = "https://files.pythonhosted.org/packages/a5/a0/98a3630f0b57f77bae67716562513d3032ae70414fcaf02750279c389a9e/pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a", size = 6624404, upload-time = "2025-07-01T09:15:58.245Z" },
- { url = "https://files.pythonhosted.org/packages/de/e6/83dfba5646a290edd9a21964da07674409e410579c341fc5b8f7abd81620/pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978", size = 6067760, upload-time = "2025-07-01T09:16:00.003Z" },
- { url = "https://files.pythonhosted.org/packages/bc/41/15ab268fe6ee9a2bc7391e2bbb20a98d3974304ab1a406a992dcb297a370/pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d", size = 6700534, upload-time = "2025-07-01T09:16:02.29Z" },
- { url = "https://files.pythonhosted.org/packages/64/79/6d4f638b288300bed727ff29f2a3cb63db054b33518a95f27724915e3fbc/pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71", size = 6277091, upload-time = "2025-07-01T09:16:04.4Z" },
- { url = "https://files.pythonhosted.org/packages/46/05/4106422f45a05716fd34ed21763f8ec182e8ea00af6e9cb05b93a247361a/pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada", size = 6986091, upload-time = "2025-07-01T09:16:06.342Z" },
- { url = "https://files.pythonhosted.org/packages/63/c6/287fd55c2c12761d0591549d48885187579b7c257bef0c6660755b0b59ae/pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb", size = 2422632, upload-time = "2025-07-01T09:16:08.142Z" },
{ url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" },
{ url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" },
{ url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" },
@@ -3142,48 +2436,16 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
-[[package]]
-name = "pre-commit"
-version = "4.3.0"
-source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
-dependencies = [
- { name = "cfgv", version = "3.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "identify", version = "2.6.15", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "nodeenv", marker = "python_full_version < '3.10'" },
- { name = "pyyaml", marker = "python_full_version < '3.10'" },
- { name = "virtualenv", marker = "python_full_version < '3.10'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" },
-]
-
[[package]]
name = "pre-commit"
version = "4.5.1"
source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version >= '3.12' and sys_platform == 'darwin'",
- "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.11.*' and sys_platform == 'darwin'",
- "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
dependencies = [
- { name = "cfgv", version = "3.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
- { name = "identify", version = "2.6.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
- { name = "nodeenv", marker = "python_full_version >= '3.10'" },
- { name = "pyyaml", marker = "python_full_version >= '3.10'" },
- { name = "virtualenv", marker = "python_full_version >= '3.10'" },
+ { name = "cfgv" },
+ { name = "identify" },
+ { name = "nodeenv" },
+ { name = "pyyaml" },
+ { name = "virtualenv" },
]
sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" }
wheels = [
@@ -3338,8 +2600,7 @@ name = "pytest-cov"
version = "7.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.10'" },
- { name = "coverage", version = "7.13.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" },
+ { name = "coverage", extra = ["toml"] },
{ name = "pluggy" },
{ name = "pytest" },
]
@@ -3364,9 +2625,6 @@ wheels = [
name = "python-json-logger"
version = "3.3.0"
source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "typing-extensions", marker = "python_full_version < '3.10'" },
-]
sdist = { url = "https://files.pythonhosted.org/packages/9e/de/d3144a0bceede957f961e975f3752760fbe390d57fbe194baf709d8f1f7b/python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84", size = 16642, upload-time = "2025-03-07T07:08:27.301Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/08/20/0f2523b9e50a8052bc6a8b732dfc8568abbdc42010aef03a2d750bdab3b2/python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7", size = 15163, upload-time = "2025-03-07T07:08:25.627Z" },
@@ -3398,8 +2656,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1c/09/9c1b978ffc4ae53999e89c19c77ba882d9fce476729f23ef55211ea1c034/pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab", size = 8794384, upload-time = "2025-03-17T00:56:04.383Z" },
{ url = "https://files.pythonhosted.org/packages/45/3c/b4640f740ffebadd5d34df35fecba0e1cfef8fde9f3e594df91c28ad9b50/pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e", size = 9503039, upload-time = "2025-03-17T00:56:06.207Z" },
{ url = "https://files.pythonhosted.org/packages/b4/f4/f785020090fb050e7fb6d34b780f2231f302609dc964672f72bfaeb59a28/pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33", size = 8458152, upload-time = "2025-03-17T00:56:07.819Z" },
- { url = "https://files.pythonhosted.org/packages/a2/cd/d09d434630edb6a0c44ad5079611279a67530296cfe0451e003de7f449ff/pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a", size = 8848099, upload-time = "2025-03-17T00:55:42.415Z" },
- { url = "https://files.pythonhosted.org/packages/93/ff/2a8c10315ffbdee7b3883ac0d1667e267ca8b3f6f640d81d43b87a82c0c7/pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475", size = 9602031, upload-time = "2025-03-17T00:55:44.512Z" },
]
[[package]]
@@ -3422,7 +2678,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/e5/9714def18c3a411809771a3fbcec70bffa764b9675afb00048a620fca604/pywinpty-2.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:83a8f20b430bbc5d8957249f875341a60219a4e971580f2ba694fbfb54a45ebc", size = 1405243, upload-time = "2025-02-03T21:56:52.476Z" },
{ url = "https://files.pythonhosted.org/packages/fb/16/2ab7b3b7f55f3c6929e5f629e1a68362981e4e5fed592a2ed1cb4b4914a5/pywinpty-2.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:ab5920877dd632c124b4ed17bc6dd6ef3b9f86cd492b963ffdb1a67b85b0f408", size = 1405020, upload-time = "2025-02-03T21:56:04.753Z" },
{ url = "https://files.pythonhosted.org/packages/7c/16/edef3515dd2030db2795dbfbe392232c7a0f3dc41b98e92b38b42ba497c7/pywinpty-2.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:a4560ad8c01e537708d2790dbe7da7d986791de805d89dd0d3697ca59e9e4901", size = 1404151, upload-time = "2025-02-03T21:55:53.628Z" },
- { url = "https://files.pythonhosted.org/packages/47/96/90fa02f19b1eff7469ad7bf0ef8efca248025de9f1d0a0b25682d2aacf68/pywinpty-2.0.15-cp39-cp39-win_amd64.whl", hash = "sha256:d261cd88fcd358cfb48a7ca0700db3e1c088c9c10403c9ebc0d8a8b57aa6a117", size = 1405302, upload-time = "2025-02-03T21:55:40.394Z" },
]
[[package]]
@@ -3467,15 +2722,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
- { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" },
- { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" },
- { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" },
- { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" },
- { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" },
- { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" },
- { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" },
- { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" },
- { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" },
]
[[package]]
@@ -3495,8 +2741,8 @@ name = "pyzmq"
version = "27.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "cffi", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.10' and implementation_name == 'pypy' and sys_platform == 'darwin') or (implementation_name == 'pypy' and platform_machine == 'arm64' and sys_platform == 'darwin')" },
- { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.10' and implementation_name == 'pypy' and platform_machine != 'arm64') or (implementation_name == 'pypy' and sys_platform != 'darwin')" },
+ { name = "cffi", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "implementation_name == 'pypy' and sys_platform == 'darwin'" },
+ { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "implementation_name == 'pypy' and sys_platform != 'darwin'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f1/06/50a4e9648b3e8b992bef8eb632e457307553a89d294103213cfd47b3da69/pyzmq-27.0.0.tar.gz", hash = "sha256:b1f08eeb9ce1510e6939b6e5dcd46a17765e2333daae78ecf4606808442e52cf", size = 280478, upload-time = "2025-06-13T14:09:07.087Z" }
wheels = [
@@ -3539,16 +2785,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/64/39/dc2db178c26a42228c5ac94a9cc595030458aa64c8d796a7727947afbf55/pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20d5cb29e8c5f76a127c75b6e7a77e846bc4b655c373baa098c26a61b7ecd0ef", size = 1885199, upload-time = "2025-06-13T14:07:57.166Z" },
{ url = "https://files.pythonhosted.org/packages/c7/21/dae7b06a1f8cdee5d8e7a63d99c5d129c401acc40410bef2cbf42025e26f/pyzmq-27.0.0-cp313-cp313t-win32.whl", hash = "sha256:a20528da85c7ac7a19b7384e8c3f8fa707841fd85afc4ed56eda59d93e3d98ad", size = 575439, upload-time = "2025-06-13T14:07:58.959Z" },
{ url = "https://files.pythonhosted.org/packages/eb/bc/1709dc55f0970cf4cb8259e435e6773f9946f41a045c2cb90e870b7072da/pyzmq-27.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d8229f2efece6a660ee211d74d91dbc2a76b95544d46c74c615e491900dc107f", size = 639933, upload-time = "2025-06-13T14:08:00.777Z" },
- { url = "https://files.pythonhosted.org/packages/19/dc/95210fe17e5d7dba89bd663e1d88f50a8003f296284731b09f1d95309a42/pyzmq-27.0.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:100f6e5052ba42b2533011d34a018a5ace34f8cac67cb03cfa37c8bdae0ca617", size = 1330656, upload-time = "2025-06-13T14:08:17.414Z" },
- { url = "https://files.pythonhosted.org/packages/d3/7e/63f742b578316258e03ecb393d35c0964348d80834bdec8a100ed7bb9c91/pyzmq-27.0.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:bf6c6b061efd00404b9750e2cfbd9507492c8d4b3721ded76cb03786131be2ed", size = 906522, upload-time = "2025-06-13T14:08:18.945Z" },
- { url = "https://files.pythonhosted.org/packages/1f/bf/f0b2b67f5a9bfe0fbd0e978a2becd901f802306aa8e29161cb0963094352/pyzmq-27.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee05728c0b0b2484a9fc20466fa776fffb65d95f7317a3419985b8c908563861", size = 863545, upload-time = "2025-06-13T14:08:20.386Z" },
- { url = "https://files.pythonhosted.org/packages/87/0e/7d90ccd2ef577c8bae7f926acd2011a6d960eea8a068c5fd52b419206960/pyzmq-27.0.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7cdf07fe0a557b131366f80727ec8ccc4b70d89f1e3f920d94a594d598d754f0", size = 666796, upload-time = "2025-06-13T14:08:21.836Z" },
- { url = "https://files.pythonhosted.org/packages/4f/6d/ca8007a313baa73361778773aef210f4902e68f468d1f93b6c8b908fabbd/pyzmq-27.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:90252fa2ff3a104219db1f5ced7032a7b5fc82d7c8d2fec2b9a3e6fd4e25576b", size = 1655599, upload-time = "2025-06-13T14:08:23.343Z" },
- { url = "https://files.pythonhosted.org/packages/46/de/5cb4f99d6c0dd8f33d729c9ebd49af279586e5ab127e93aa6ef0ecd08c4c/pyzmq-27.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ea6d441c513bf18c578c73c323acf7b4184507fc244762193aa3a871333c9045", size = 2034119, upload-time = "2025-06-13T14:08:26.369Z" },
- { url = "https://files.pythonhosted.org/packages/d0/8d/57cc90c8b5f30a97a7e86ec91a3b9822ec7859d477e9c30f531fb78f4a97/pyzmq-27.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ae2b34bcfaae20c064948a4113bf8709eee89fd08317eb293ae4ebd69b4d9740", size = 1891955, upload-time = "2025-06-13T14:08:28.39Z" },
- { url = "https://files.pythonhosted.org/packages/24/f5/a7012022573188903802ab75b5314b00e5c629228f3a36fadb421a42ebff/pyzmq-27.0.0-cp39-cp39-win32.whl", hash = "sha256:5b10bd6f008937705cf6e7bf8b6ece5ca055991e3eb130bca8023e20b86aa9a3", size = 568497, upload-time = "2025-06-13T14:08:30.089Z" },
- { url = "https://files.pythonhosted.org/packages/9b/f3/2a4b2798275a574801221d94d599ed3e26d19f6378a7364cdfa3bee53944/pyzmq-27.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:00387d12a8af4b24883895f7e6b9495dc20a66027b696536edac35cb988c38f3", size = 629315, upload-time = "2025-06-13T14:08:31.877Z" },
- { url = "https://files.pythonhosted.org/packages/da/eb/386a70314f305816142d6e8537f5557e5fd9614c03698d6c88cbd6c41190/pyzmq-27.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:4c19d39c04c29a6619adfeb19e3735c421b3bfee082f320662f52e59c47202ba", size = 559596, upload-time = "2025-06-13T14:08:33.357Z" },
{ url = "https://files.pythonhosted.org/packages/09/6f/be6523a7f3821c0b5370912ef02822c028611360e0d206dd945bdbf9eaef/pyzmq-27.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:656c1866505a5735d0660b7da6d7147174bbf59d4975fc2b7f09f43c9bc25745", size = 835950, upload-time = "2025-06-13T14:08:35Z" },
{ url = "https://files.pythonhosted.org/packages/c6/1e/a50fdd5c15018de07ab82a61bc460841be967ee7bbe7abee3b714d66f7ac/pyzmq-27.0.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74175b9e12779382432dd1d1f5960ebe7465d36649b98a06c6b26be24d173fab", size = 799876, upload-time = "2025-06-13T14:08:36.849Z" },
{ url = "https://files.pythonhosted.org/packages/88/a1/89eb5b71f5a504f8f887aceb8e1eb3626e00c00aa8085381cdff475440dc/pyzmq-27.0.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c6de908465697a8708e4d6843a1e884f567962fc61eb1706856545141d0cbb", size = 567400, upload-time = "2025-06-13T14:08:38.95Z" },
@@ -3559,11 +2795,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/ba/64af397e0f421453dc68e31d5e0784d554bf39013a2de0872056e96e58af/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14fe7aaac86e4e93ea779a821967360c781d7ac5115b3f1a171ced77065a0174", size = 567400, upload-time = "2025-06-13T14:08:46.855Z" },
{ url = "https://files.pythonhosted.org/packages/63/87/ec956cbe98809270b59a22891d5758edae147a258e658bf3024a8254c855/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ad0562d4e6abb785be3e4dd68599c41be821b521da38c402bc9ab2a8e7ebc7e", size = 747031, upload-time = "2025-06-13T14:08:48.419Z" },
{ url = "https://files.pythonhosted.org/packages/be/8a/4a3764a68abc02e2fbb0668d225b6fda5cd39586dd099cee8b2ed6ab0452/pyzmq-27.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9df43a2459cd3a3563404c1456b2c4c69564daa7dbaf15724c09821a3329ce46", size = 544726, upload-time = "2025-06-13T14:08:49.903Z" },
- { url = "https://files.pythonhosted.org/packages/03/f6/11b2a6c8cd13275c31cddc3f89981a1b799a3c41dec55289fa18dede96b5/pyzmq-27.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:39ddd3ba0a641f01d8f13a3cfd4c4924eb58e660d8afe87e9061d6e8ca6f7ac3", size = 835944, upload-time = "2025-06-13T14:08:59.189Z" },
- { url = "https://files.pythonhosted.org/packages/73/34/aa39076f4e07ae1912fa4b966fe24e831e01d736d4c1c7e8a3aa28a555b5/pyzmq-27.0.0-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8ca7e6a0388dd9e1180b14728051068f4efe83e0d2de058b5ff92c63f399a73f", size = 799869, upload-time = "2025-06-13T14:09:00.758Z" },
- { url = "https://files.pythonhosted.org/packages/65/f3/81ed6b3dd242408ee79c0d8a88734644acf208baee8666ecd7e52664cf55/pyzmq-27.0.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2524c40891be6a3106885a3935d58452dd83eb7a5742a33cc780a1ad4c49dec0", size = 758371, upload-time = "2025-06-13T14:09:02.461Z" },
- { url = "https://files.pythonhosted.org/packages/e1/04/dac4ca674764281caf744e8adefd88f7e325e1605aba0f9a322094b903fa/pyzmq-27.0.0-pp39-pypy39_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a56e3e5bd2d62a01744fd2f1ce21d760c7c65f030e9522738d75932a14ab62a", size = 567393, upload-time = "2025-06-13T14:09:04.037Z" },
- { url = "https://files.pythonhosted.org/packages/51/8b/619a9ee2fa4d3c724fbadde946427735ade64da03894b071bbdc3b789d83/pyzmq-27.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:096af9e133fec3a72108ddefba1e42985cb3639e9de52cfd336b6fc23aa083e9", size = 544715, upload-time = "2025-06-13T14:09:05.579Z" },
]
[[package]]
@@ -3766,19 +2997,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/52/d9/3f0f105420fecd18551b678c9a6ce60bd23986098b252a56d35781b3e7e9/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e", size = 554886, upload-time = "2025-07-01T15:55:52.541Z" },
{ url = "https://files.pythonhosted.org/packages/6b/c5/347c056a90dc8dd9bc240a08c527315008e1b5042e7a4cf4ac027be9d38a/rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f", size = 219027, upload-time = "2025-07-01T15:55:53.874Z" },
{ url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" },
- { url = "https://files.pythonhosted.org/packages/fb/74/846ab687119c9d31fc21ab1346ef9233c31035ce53c0e2d43a130a0c5a5e/rpds_py-0.26.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:7a48af25d9b3c15684059d0d1fc0bc30e8eee5ca521030e2bffddcab5be40226", size = 372786, upload-time = "2025-07-01T15:55:56.512Z" },
- { url = "https://files.pythonhosted.org/packages/33/02/1f9e465cb1a6032d02b17cd117c7bd9fb6156bc5b40ffeb8053d8a2aa89c/rpds_py-0.26.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0c71c2f6bf36e61ee5c47b2b9b5d47e4d1baad6426bfed9eea3e858fc6ee8806", size = 358062, upload-time = "2025-07-01T15:55:58.084Z" },
- { url = "https://files.pythonhosted.org/packages/2a/49/81a38e3c67ac943907a9711882da3d87758c82cf26b2120b8128e45d80df/rpds_py-0.26.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d815d48b1804ed7867b539236b6dd62997850ca1c91cad187f2ddb1b7bbef19", size = 381576, upload-time = "2025-07-01T15:55:59.422Z" },
- { url = "https://files.pythonhosted.org/packages/14/37/418f030a76ef59f41e55f9dc916af8afafa3c9e3be38df744b2014851474/rpds_py-0.26.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84cfbd4d4d2cdeb2be61a057a258d26b22877266dd905809e94172dff01a42ae", size = 397062, upload-time = "2025-07-01T15:56:00.868Z" },
- { url = "https://files.pythonhosted.org/packages/47/e3/9090817a8f4388bfe58e28136e9682fa7872a06daff2b8a2f8c78786a6e1/rpds_py-0.26.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbaa70553ca116c77717f513e08815aec458e6b69a028d4028d403b3bc84ff37", size = 516277, upload-time = "2025-07-01T15:56:02.672Z" },
- { url = "https://files.pythonhosted.org/packages/3f/3a/1ec3dd93250fb8023f27d49b3f92e13f679141f2e59a61563f88922c2821/rpds_py-0.26.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39bfea47c375f379d8e87ab4bb9eb2c836e4f2069f0f65731d85e55d74666387", size = 402604, upload-time = "2025-07-01T15:56:04.453Z" },
- { url = "https://files.pythonhosted.org/packages/f2/98/9133c06e42ec3ce637936263c50ac647f879b40a35cfad2f5d4ad418a439/rpds_py-0.26.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1533b7eb683fb5f38c1d68a3c78f5fdd8f1412fa6b9bf03b40f450785a0ab915", size = 383664, upload-time = "2025-07-01T15:56:05.823Z" },
- { url = "https://files.pythonhosted.org/packages/a9/10/a59ce64099cc77c81adb51f06909ac0159c19a3e2c9d9613bab171f4730f/rpds_py-0.26.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c5ab0ee51f560d179b057555b4f601b7df909ed31312d301b99f8b9fc6028284", size = 415944, upload-time = "2025-07-01T15:56:07.132Z" },
- { url = "https://files.pythonhosted.org/packages/c3/f1/ae0c60b3be9df9d5bef3527d83b8eb4b939e3619f6dd8382840e220a27df/rpds_py-0.26.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e5162afc9e0d1f9cae3b577d9c29ddbab3505ab39012cb794d94a005825bde21", size = 558311, upload-time = "2025-07-01T15:56:08.484Z" },
- { url = "https://files.pythonhosted.org/packages/fb/2b/bf1498ebb3ddc5eff2fe3439da88963d1fc6e73d1277fa7ca0c72620d167/rpds_py-0.26.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:43f10b007033f359bc3fa9cd5e6c1e76723f056ffa9a6b5c117cc35720a80292", size = 587928, upload-time = "2025-07-01T15:56:09.946Z" },
- { url = "https://files.pythonhosted.org/packages/b6/eb/e6b949edf7af5629848c06d6e544a36c9f2781e2d8d03b906de61ada04d0/rpds_py-0.26.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e3730a48e5622e598293eee0762b09cff34dd3f271530f47b0894891281f051d", size = 554554, upload-time = "2025-07-01T15:56:11.775Z" },
- { url = "https://files.pythonhosted.org/packages/0a/1c/aa0298372ea898620d4706ad26b5b9e975550a4dd30bd042b0fe9ae72cce/rpds_py-0.26.0-cp39-cp39-win32.whl", hash = "sha256:4b1f66eb81eab2e0ff5775a3a312e5e2e16bf758f7b06be82fb0d04078c7ac51", size = 220273, upload-time = "2025-07-01T15:56:13.273Z" },
- { url = "https://files.pythonhosted.org/packages/b8/b0/8b3bef6ad0b35c172d1c87e2e5c2bb027d99e2a7bc7a16f744e66cf318f3/rpds_py-0.26.0-cp39-cp39-win_amd64.whl", hash = "sha256:519067e29f67b5c90e64fb1a6b6e9d2ec0ba28705c51956637bac23a2f4ddae1", size = 231627, upload-time = "2025-07-01T15:56:14.853Z" },
{ url = "https://files.pythonhosted.org/packages/ef/9a/1f033b0b31253d03d785b0cd905bc127e555ab496ea6b4c7c2e1f951f2fd/rpds_py-0.26.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3c0909c5234543ada2515c05dc08595b08d621ba919629e94427e8e03539c958", size = 373226, upload-time = "2025-07-01T15:56:16.578Z" },
{ url = "https://files.pythonhosted.org/packages/58/29/5f88023fd6aaaa8ca3c4a6357ebb23f6f07da6079093ccf27c99efce87db/rpds_py-0.26.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c1fb0cda2abcc0ac62f64e2ea4b4e64c57dfd6b885e693095460c61bde7bb18e", size = 359230, upload-time = "2025-07-01T15:56:17.978Z" },
{ url = "https://files.pythonhosted.org/packages/6c/6c/13eaebd28b439da6964dde22712b52e53fe2824af0223b8e403249d10405/rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d142d2d6cf9b31c12aa4878d82ed3b2324226270b89b676ac62ccd7df52d08", size = 382363, upload-time = "2025-07-01T15:56:19.977Z" },
@@ -3802,58 +3020,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/a6/33b1fc0c9f7dcfcfc4a4353daa6308b3ece22496ceece348b3e7a7559a09/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb", size = 559439, upload-time = "2025-07-01T15:56:48.549Z" },
{ url = "https://files.pythonhosted.org/packages/71/2d/ceb3f9c12f8cfa56d34995097f6cd99da1325642c60d1b6680dd9df03ed8/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0", size = 588380, upload-time = "2025-07-01T15:56:50.086Z" },
{ url = "https://files.pythonhosted.org/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334, upload-time = "2025-07-01T15:56:51.703Z" },
- { url = "https://files.pythonhosted.org/packages/7e/78/a08e2f28e91c7e45db1150813c6d760a0fb114d5652b1373897073369e0d/rpds_py-0.26.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a90a13408a7a856b87be8a9f008fff53c5080eea4e4180f6c2e546e4a972fb5d", size = 373157, upload-time = "2025-07-01T15:56:53.291Z" },
- { url = "https://files.pythonhosted.org/packages/52/01/ddf51517497c8224fb0287e9842b820ed93748bc28ea74cab56a71e3dba4/rpds_py-0.26.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ac51b65e8dc76cf4949419c54c5528adb24fc721df722fd452e5fbc236f5c40", size = 358827, upload-time = "2025-07-01T15:56:54.963Z" },
- { url = "https://files.pythonhosted.org/packages/4d/f4/acaefa44b83705a4fcadd68054280127c07cdb236a44a1c08b7c5adad40b/rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b2093224a18c6508d95cfdeba8db9cbfd6f3494e94793b58972933fcee4c6d", size = 382182, upload-time = "2025-07-01T15:56:56.474Z" },
- { url = "https://files.pythonhosted.org/packages/e9/a2/d72ac03d37d33f6ff4713ca4c704da0c3b1b3a959f0bf5eb738c0ad94ea2/rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f01a5d6444a3258b00dc07b6ea4733e26f8072b788bef750baa37b370266137", size = 397123, upload-time = "2025-07-01T15:56:58.272Z" },
- { url = "https://files.pythonhosted.org/packages/74/58/c053e9d1da1d3724434dd7a5f506623913e6404d396ff3cf636a910c0789/rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6e2c12160c72aeda9d1283e612f68804621f448145a210f1bf1d79151c47090", size = 516285, upload-time = "2025-07-01T15:57:00.283Z" },
- { url = "https://files.pythonhosted.org/packages/94/41/c81e97ee88b38b6d1847c75f2274dee8d67cb8d5ed7ca8c6b80442dead75/rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb28c1f569f8d33b2b5dcd05d0e6ef7005d8639c54c2f0be824f05aedf715255", size = 402182, upload-time = "2025-07-01T15:57:02.587Z" },
- { url = "https://files.pythonhosted.org/packages/74/74/38a176b34ce5197b4223e295f36350dd90713db13cf3c3b533e8e8f7484e/rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1766b5724c3f779317d5321664a343c07773c8c5fd1532e4039e6cc7d1a815be", size = 384436, upload-time = "2025-07-01T15:57:04.125Z" },
- { url = "https://files.pythonhosted.org/packages/e4/21/f40b9a5709d7078372c87fd11335469dc4405245528b60007cd4078ed57a/rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b6d9e5a2ed9c4988c8f9b28b3bc0e3e5b1aaa10c28d210a594ff3a8c02742daf", size = 417039, upload-time = "2025-07-01T15:57:05.608Z" },
- { url = "https://files.pythonhosted.org/packages/02/ee/ed835925731c7e87306faa80a3a5e17b4d0f532083155e7e00fe1cd4e242/rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:b5f7a446ddaf6ca0fad9a5535b56fbfc29998bf0e0b450d174bbec0d600e1d72", size = 559111, upload-time = "2025-07-01T15:57:07.371Z" },
- { url = "https://files.pythonhosted.org/packages/ce/88/d6e9e686b8ffb6139b82eb1c319ef32ae99aeb21f7e4bf45bba44a760d09/rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:eed5ac260dd545fbc20da5f4f15e7efe36a55e0e7cf706e4ec005b491a9546a0", size = 588609, upload-time = "2025-07-01T15:57:09.319Z" },
- { url = "https://files.pythonhosted.org/packages/e5/96/09bcab08fa12a69672716b7f86c672ee7f79c5319f1890c5a79dcb8e0df2/rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:582462833ba7cee52e968b0341b85e392ae53d44c0f9af6a5927c80e539a8b67", size = 555212, upload-time = "2025-07-01T15:57:10.905Z" },
- { url = "https://files.pythonhosted.org/packages/2c/07/c554b6ed0064b6e0350a622714298e930b3cf5a3d445a2e25c412268abcf/rpds_py-0.26.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69a607203441e07e9a8a529cff1d5b73f6a160f22db1097211e6212a68567d11", size = 232048, upload-time = "2025-07-01T15:57:12.473Z" },
-]
-
-[[package]]
-name = "scipy"
-version = "1.13.1"
-source = { registry = "https://pypi.org/simple" }
-resolution-markers = [
- "python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
- "python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
-]
-dependencies = [
- { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/ae/00/48c2f661e2816ccf2ecd77982f6605b2950afe60f60a52b4cbbc2504aa8f/scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", size = 57210720, upload-time = "2024-05-23T03:29:26.079Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/33/59/41b2529908c002ade869623b87eecff3e11e3ce62e996d0bdcb536984187/scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca", size = 39328076, upload-time = "2024-05-23T03:19:01.687Z" },
- { url = "https://files.pythonhosted.org/packages/d5/33/f1307601f492f764062ce7dd471a14750f3360e33cd0f8c614dae208492c/scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f", size = 30306232, upload-time = "2024-05-23T03:19:09.089Z" },
- { url = "https://files.pythonhosted.org/packages/c0/66/9cd4f501dd5ea03e4a4572ecd874936d0da296bd04d1c45ae1a4a75d9c3a/scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989", size = 33743202, upload-time = "2024-05-23T03:19:15.138Z" },
- { url = "https://files.pythonhosted.org/packages/a3/ba/7255e5dc82a65adbe83771c72f384d99c43063648456796436c9a5585ec3/scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f", size = 38577335, upload-time = "2024-05-23T03:19:21.984Z" },
- { url = "https://files.pythonhosted.org/packages/49/a5/bb9ded8326e9f0cdfdc412eeda1054b914dfea952bda2097d174f8832cc0/scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94", size = 38820728, upload-time = "2024-05-23T03:19:28.225Z" },
- { url = "https://files.pythonhosted.org/packages/12/30/df7a8fcc08f9b4a83f5f27cfaaa7d43f9a2d2ad0b6562cced433e5b04e31/scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54", size = 46210588, upload-time = "2024-05-23T03:19:35.661Z" },
- { url = "https://files.pythonhosted.org/packages/b4/15/4a4bb1b15bbd2cd2786c4f46e76b871b28799b67891f23f455323a0cdcfb/scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9", size = 39333805, upload-time = "2024-05-23T03:19:43.081Z" },
- { url = "https://files.pythonhosted.org/packages/ba/92/42476de1af309c27710004f5cdebc27bec62c204db42e05b23a302cb0c9a/scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326", size = 30317687, upload-time = "2024-05-23T03:19:48.799Z" },
- { url = "https://files.pythonhosted.org/packages/80/ba/8be64fe225360a4beb6840f3cbee494c107c0887f33350d0a47d55400b01/scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299", size = 33694638, upload-time = "2024-05-23T03:19:55.104Z" },
- { url = "https://files.pythonhosted.org/packages/36/07/035d22ff9795129c5a847c64cb43c1fa9188826b59344fee28a3ab02e283/scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa", size = 38569931, upload-time = "2024-05-23T03:20:01.82Z" },
- { url = "https://files.pythonhosted.org/packages/d9/10/f9b43de37e5ed91facc0cfff31d45ed0104f359e4f9a68416cbf4e790241/scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59", size = 38838145, upload-time = "2024-05-23T03:20:09.173Z" },
- { url = "https://files.pythonhosted.org/packages/4a/48/4513a1a5623a23e95f94abd675ed91cfb19989c58e9f6f7d03990f6caf3d/scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", size = 46196227, upload-time = "2024-05-23T03:20:16.433Z" },
- { url = "https://files.pythonhosted.org/packages/f2/7b/fb6b46fbee30fc7051913068758414f2721003a89dd9a707ad49174e3843/scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1", size = 39357301, upload-time = "2024-05-23T03:20:23.538Z" },
- { url = "https://files.pythonhosted.org/packages/dc/5a/2043a3bde1443d94014aaa41e0b50c39d046dda8360abd3b2a1d3f79907d/scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d", size = 30363348, upload-time = "2024-05-23T03:20:29.885Z" },
- { url = "https://files.pythonhosted.org/packages/e7/cb/26e4a47364bbfdb3b7fb3363be6d8a1c543bcd70a7753ab397350f5f189a/scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627", size = 33406062, upload-time = "2024-05-23T03:20:36.012Z" },
- { url = "https://files.pythonhosted.org/packages/88/ab/6ecdc526d509d33814835447bbbeedbebdec7cca46ef495a61b00a35b4bf/scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884", size = 38218311, upload-time = "2024-05-23T03:20:42.086Z" },
- { url = "https://files.pythonhosted.org/packages/0b/00/9f54554f0f8318100a71515122d8f4f503b1a2c4b4cfab3b4b68c0eb08fa/scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16", size = 38442493, upload-time = "2024-05-23T03:20:48.292Z" },
- { url = "https://files.pythonhosted.org/packages/3e/df/963384e90733e08eac978cd103c34df181d1fec424de383cdc443f418dd4/scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", size = 45910955, upload-time = "2024-05-23T03:20:55.091Z" },
- { url = "https://files.pythonhosted.org/packages/7f/29/c2ea58c9731b9ecb30b6738113a95d147e83922986b34c685b8f6eefde21/scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5", size = 39352927, upload-time = "2024-05-23T03:21:01.95Z" },
- { url = "https://files.pythonhosted.org/packages/5c/c0/e71b94b20ccf9effb38d7147c0064c08c622309fd487b1b677771a97d18c/scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24", size = 30324538, upload-time = "2024-05-23T03:21:07.634Z" },
- { url = "https://files.pythonhosted.org/packages/6d/0f/aaa55b06d474817cea311e7b10aab2ea1fd5d43bc6a2861ccc9caec9f418/scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004", size = 33732190, upload-time = "2024-05-23T03:21:14.41Z" },
- { url = "https://files.pythonhosted.org/packages/35/f5/d0ad1a96f80962ba65e2ce1de6a1e59edecd1f0a7b55990ed208848012e0/scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d", size = 38612244, upload-time = "2024-05-23T03:21:21.827Z" },
- { url = "https://files.pythonhosted.org/packages/8d/02/1165905f14962174e6569076bcc3315809ae1291ed14de6448cc151eedfd/scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c", size = 38845637, upload-time = "2024-05-23T03:21:28.729Z" },
- { url = "https://files.pythonhosted.org/packages/3e/77/dab54fe647a08ee4253963bcd8f9cf17509c8ca64d6335141422fe2e2114/scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2", size = 46227440, upload-time = "2024-05-23T03:21:35.888Z" },
]
[[package]]
@@ -3861,12 +3027,12 @@ name = "scipy"
version = "1.15.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
- "python_full_version == '3.10.*' and sys_platform == 'darwin'",
- "python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
- "(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
+ "python_full_version < '3.11' and sys_platform == 'darwin'",
+ "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",
+ "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
dependencies = [
- { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" }
wheels = [
@@ -3977,8 +3143,8 @@ name = "secretstorage"
version = "3.3.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "cryptography", marker = "(python_full_version < '3.10' and platform_machine != 'arm64') or sys_platform != 'darwin'" },
- { name = "jeepney", marker = "(python_full_version < '3.10' and platform_machine != 'arm64') or sys_platform != 'darwin'" },
+ { name = "cryptography", marker = "sys_platform != 'darwin'" },
+ { name = "jeepney", marker = "sys_platform != 'darwin'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739, upload-time = "2022-08-13T16:22:46.976Z" }
wheels = [
@@ -4059,18 +3225,15 @@ version = "0.28.0"
source = { editable = "." }
dependencies = [
{ name = "defusedxml" },
- { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "matplotlib", version = "3.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
- { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "matplotlib" },
+ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "opencv-python" },
{ name = "pillow" },
{ name = "pydeprecate" },
{ name = "pyyaml" },
{ name = "requests" },
- { name = "scipy", version = "1.13.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
+ { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "scipy", version = "1.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "tqdm" },
]
@@ -4092,8 +3255,7 @@ dev = [
{ name = "jupytext" },
{ name = "nbconvert" },
{ name = "notebook" },
- { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "pre-commit", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "pre-commit" },
{ name = "pytest" },
{ name = "pytest-cov" },
{ name = "tox" },
@@ -4143,7 +3305,7 @@ dev = [
]
docs = [
{ name = "mike", specifier = ">=2" },
- { name = "mkdocs-git-committers-plugin-2", marker = "python_full_version >= '3.9' and python_full_version < '4'", specifier = ">=2.4.1" },
+ { name = "mkdocs-git-committers-plugin-2", marker = "python_full_version >= '3.10' and python_full_version < '4'", specifier = ">=2.4.1" },
{ name = "mkdocs-git-revision-date-localized-plugin", specifier = ">=1.2.4" },
{ name = "mkdocs-jupyter", specifier = ">=0.24.3" },
{ name = "mkdocs-material", extras = ["imaging"], specifier = ">=9.7" },
@@ -4157,7 +3319,7 @@ version = "0.18.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ptyprocess", marker = "os_name != 'nt'" },
- { name = "pywinpty", marker = "(python_full_version < '3.10' and os_name == 'nt' and platform_machine != 'arm64' and sys_platform == 'darwin') or (os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" },
+ { name = "pywinpty", marker = "(os_name == 'nt' and platform_machine != 'aarch64' and sys_platform == 'linux') or (os_name == 'nt' and sys_platform != 'darwin' and sys_platform != 'linux')" },
{ name = "tornado" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" }
@@ -4241,8 +3403,7 @@ dependencies = [
{ name = "cachetools" },
{ name = "chardet" },
{ name = "colorama" },
- { name = "filelock", version = "3.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "filelock", version = "3.20.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "filelock" },
{ name = "packaging" },
{ name = "platformdirs" },
{ name = "pluggy" },
@@ -4283,7 +3444,6 @@ version = "6.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "id" },
- { name = "importlib-metadata", marker = "python_full_version < '3.10'" },
{ name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" },
{ name = "packaging" },
{ name = "readme-renderer" },
@@ -4358,8 +3518,7 @@ version = "20.36.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "distlib" },
- { name = "filelock", version = "3.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
- { name = "filelock", version = "3.20.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "filelock" },
{ name = "platformdirs" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
@@ -4386,13 +3545,8 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" },
{ url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" },
{ url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" },
- { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390, upload-time = "2024-11-01T14:06:49.325Z" },
- { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386, upload-time = "2024-11-01T14:06:50.536Z" },
- { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017, upload-time = "2024-11-01T14:06:51.717Z" },
{ url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" },
{ url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" },
- { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903, upload-time = "2024-11-01T14:06:57.052Z" },
- { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381, upload-time = "2024-11-01T14:06:58.193Z" },
{ url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
{ url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
{ url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },