Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
d80f505
chore: bump minimum Python to 3.10
Borda May 20, 2026
4c90f98
Apply suggestions from code review
Borda May 20, 2026
97a277e
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 20, 2026
63041da
chore: enable flake8-annotations (ANN) rule in Ruff configuration
Borda May 20, 2026
57fef28
chore: annotate all functions with explicit return types for flake8-a…
Borda May 20, 2026
e6ca7e2
fix: resolve mypy typing issues
Borda May 20, 2026
0de829e
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 20, 2026
6155b1c
chore: add per-file-ignore for missing type annotations in test files
Borda May 20, 2026
dca8a7b
chore: reformat code for improved readability and consistency
Borda May 20, 2026
9618cef
Merge branch 'bump/py3.10' of https://github.com/roboflow/supervision…
Borda May 20, 2026
4672a38
chore: enforce consistent typing and formatting across codebase
Borda May 20, 2026
280fac0
chore: remove Python 3.14 from CI test matrix
Borda May 20, 2026
121e83e
chore: add explicit type annotations to improve type safety and reada…
Borda May 20, 2026
3381808
fix: revert public-API breaking changes from py3.10 bump
Borda May 20, 2026
376846b
docs: update user-facing minimum Python version to 3.10
Copilot May 20, 2026
1f32431
fix: address copilot review thread feedback
Copilot May 20, 2026
af76087
fix: enforce explicit type annotations and restore dtype consistency
Borda May 20, 2026
fb36258
fix: update confusion matrix and box spreading to ensure dtype consis…
Borda May 20, 2026
5d1919f
fix: restore Detections compat annotations and fix pre-commit failures
Borda May 20, 2026
cf8bcb1
Potential fix for pull request finding
Borda May 20, 2026
7f3fc54
fix: cast confusion matrix copy to float before NaN masking
Copilot May 20, 2026
da87029
Merge branch 'develop' into bump/py3.10
Borda May 20, 2026
b3be9e1
fix(pre-commit): pin mypy hook dependencies for reproducible installs
Borda May 20, 2026
e9d07c5
fix(pre-commit): set Python version to 3.10 for consistency with proj…
Borda May 20, 2026
78c0ddd
refactor: reduce casts and tighten typing across supervision
Borda May 21, 2026
b517aae
refactor: tighten coco typing boundary
Borda May 21, 2026
404d27a
refactor: remove unused overloads and tighten typing in mask_to_rle
Borda May 21, 2026
4878276
refactor: introduce stricter type aliases and rename internal type de…
Borda May 21, 2026
7af3cfd
refactor: extract typing definitions into dedicated `_typing.py` modu…
Borda May 21, 2026
39b949e
fix: restore Metric.compute API compatibility
Borda May 21, 2026
583fbff
chore: revert non-typing drift from py3.10 typing branch
Borda May 21, 2026
36cc2f4
refactor: narrow converters changes to typing-only adjustments
Borda May 21, 2026
1430819
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] May 21, 2026
47dcdb1
refactor: rename type aliases for consistency and update references a…
Borda May 21, 2026
f223753
Merge branch 'bump/py3.10' of https://github.com/roboflow/supervision…
Borda May 21, 2026
76321b1
fix(dataset): restore isinstance guard in _extract_class_names
Borda May 21, 2026
657ce7b
refactor(metrics): remove redundant CompactMask branch in get_detecti…
Borda May 21, 2026
9f7ff21
chore: drop Python 3.9 from package metadata and CI matrix
Borda May 22, 2026
68f6344
docs: update Python version requirement to 3.10+ in user-facing docs
Borda May 22, 2026
d9bea9d
fix(annotators): revert IconAnnotator _load_icon to cast — avoid sile…
Borda May 22, 2026
ec73564
fix(typing): resolve 24 mypy strict errors across detection and metri…
Borda May 22, 2026
4d30e84
test(metrics): add ValueError tests for missing class_id and confidence
Borda May 22, 2026
51135c4
fix(dataset): add safety guard for RLE segmentation missing 'counts' key
Borda May 22, 2026
381b462
test(detection): add DetectionsSmoother coverage — confidence averagi…
Borda May 22, 2026
2b9a00b
test(draw): add OSError test for load_image on non-image file
Borda May 22, 2026
a4d7e78
docs(changelog): add Python 3.9 end-of-support notice for 0.29.0
Borda May 22, 2026
ba79b43
refine(metrics): align MeanAveragePrecision dtype to float32 throughout
Borda May 22, 2026
09ee44d
fix(boxes): preserve caller dtype in denormalize_boxes
Borda May 22, 2026
5401ce9
test: fix ruff PT011 — add match= to pytest.raises calls
Borda May 22, 2026
e30da42
Merge branch 'develop' into bump/py3.10
Borda May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ date_modified: 2026-04-30

# Changelog

### UnReleased
### UnReleased <small>upcoming</small>

!!! 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.

Expand Down
2 changes: 1 addition & 1 deletion examples/compact_mask/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion examples/count_people_in_zone/inference_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion examples/count_people_in_zone/ultralytics_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
244 changes: 121 additions & 123 deletions examples/heatmap_and_track/script.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion examples/speed_estimation/inference_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion examples/speed_estimation/ultralytics_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion examples/speed_estimation/yolo_nas_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion examples/time_in_zone/inference_stream_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 4 additions & 2 deletions examples/time_in_zone/rfdetr_file_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
6 changes: 4 additions & 2 deletions examples/time_in_zone/rfdetr_naive_stream_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
Loading
Loading