Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5302ecc
Refactor README for better clarity and organization
vikassaini77 Jun 12, 2026
c5ad5a5
Revise README formatting and example paths
vikassaini77 Jun 12, 2026
b498db5
Update README.md
vikassaini77 Jun 12, 2026
c3923be
Update README.md
vikassaini77 Jun 12, 2026
1c2351e
Update README.md
vikassaini77 Jun 12, 2026
f7344db
Update README.md
vikassaini77 Jun 12, 2026
ab6b0d8
fix(examples): validate command input to prevent execution of untrust…
vikassaini77 Jun 12, 2026
8c95d1c
refactor(utils): replace asserts with proper error handling in image …
vikassaini77 Jun 12, 2026
4f49aab
refactor(detection): replace asserts with proper error handling in io…
vikassaini77 Jun 12, 2026
15b5b2b
refactor(detection): replace asserts with proper error handling in co…
vikassaini77 Jun 12, 2026
026448a
refactor(detection): replace asserts with proper error handling in li…
vikassaini77 Jun 12, 2026
1d376ad
refactor(detection): replace asserts with proper error handling in vlm
vikassaini77 Jun 12, 2026
2073e8d
refactor(detection): replace asserts with proper error handling in core
vikassaini77 Jun 12, 2026
57199e2
refactor(key_points): replace asserts with proper error handling in a…
vikassaini77 Jun 12, 2026
f599273
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jun 12, 2026
15004d2
refactor: replace bare asserts with ValueErrors in annotators and tra…
vikassaini77 Jun 13, 2026
cfddc39
refactor(metrics): improve NotImplementedError messages
vikassaini77 Jun 13, 2026
94b6829
refactor(video): replace generic Exception with IOError and ValueError
vikassaini77 Jun 13, 2026
62e02e9
refactor(tools): replace generic Exception with IOError in csv_sink
vikassaini77 Jun 13, 2026
0ccfc3b
refactor(validators): replace Optional with Union operator for modern…
vikassaini77 Jun 13, 2026
affd24c
refactor(key_points): replace Union with pipe operator
vikassaini77 Jun 13, 2026
77cbe20
refactor(detection): replace Union with pipe operator in internal tools
vikassaini77 Jun 13, 2026
beba05a
refactor(dataset): replace Union with pipe operator in coco format
vikassaini77 Jun 13, 2026
ce54a03
fix(pre_commit): 🎨 auto format pre-commit hooks
pre-commit-ci[bot] Jun 13, 2026
1c11ed0
fix: revert Union to | refactoring due to Python 3.8 runtime evaluati…
vikassaini77 Jun 13, 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
49 changes: 30 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div align="center">
<p>
<a align="center" href="" target="https://supervision.roboflow.com">
<a align="center" href="https://supervision.roboflow.com" target="_blank">
<img
width="100%"
src="https://media.roboflow.com/open-source/supervision/rf-supervision-banner.png?updatedAt=1678995927529"
Expand All @@ -24,11 +24,24 @@

</div>

## 👋 hello
## 📑 Table of Contents

- [👋 Hello](#-hello)
- [💻 Install](#-install)
- [🔥 Quickstart](#-quickstart)
- [Models](#models)
- [Annotators](#annotators)
- [Datasets](#datasets)
- [🎬 Tutorials](#-tutorials)
- [💜 Built with Supervision](#-built-with-supervision)
- [📚 Documentation](#-documentation)
- [🏆 Contribution](#-contribution)

## 👋 Hello

**We are your essential toolkit for computer vision.** From data loading to real-time zone counting, we provide the building blocks so you can focus on building applications around your models. 🤝

## 💻 install
## 💻 Install

Pip install the supervision package in a [**Python>=3.9**](https://www.python.org/) environment.

Expand All @@ -38,9 +51,9 @@ pip install supervision

Read more about conda, mamba, and installing from source in our [guide](https://roboflow.github.io/supervision/).

## 🔥 quickstart
## 🔥 Quickstart

### models
### Models

Supervision was designed to be model agnostic. Just plug in any classification, detection, or segmentation model. For your convenience, we have created [connectors](https://supervision.roboflow.com/latest/detection/core/#detections) for the most popular libraries like Ultralytics, Transformers, MMDetection, or Inference. Other integrations, like `rfdetr`, already return `sv.Detections` directly.

Expand All @@ -51,7 +64,7 @@ import supervision as sv
from PIL import Image
from rfdetr import RFDETRSmall

image = Image.open(...)
image = Image.open("path/to/image.jpg")
model = RFDETRSmall()
detections = model.predict(image, threshold=0.5)

Expand All @@ -71,7 +84,7 @@ len(detections)
from PIL import Image
from inference import get_model

image = Image.open(...)
image = Image.open("path/to/image.jpg")
model = get_model(model_id="rfdetr-small", api_key="ROBOFLOW_API_KEY")
result = model.infer(image)[0]
detections = sv.Detections.from_inference(result)
Expand All @@ -82,15 +95,16 @@ len(detections)

</details>

### annotators
### Annotators

Supervision offers a wide range of highly customizable [annotators](https://supervision.roboflow.com/latest/detection/annotators/), allowing you to compose the perfect visualization for your use case.

```python
import cv2
import supervision as sv

image = cv2.imread(...)
image = cv2.imread("path/to/image.jpg")
# Assuming detections are obtained from a model
detections = sv.Detections(...)

box_annotator = sv.BoxAnnotator()
Expand All @@ -99,7 +113,7 @@ annotated_frame = box_annotator.annotate(scene=image.copy(), detections=detectio

https://github.com/roboflow/supervision/assets/26109316/691e219c-0565-4403-9218-ab5644f39bce

### datasets
### Datasets

Supervision provides a set of [utils](https://supervision.roboflow.com/latest/datasets/core/) that allow you to load, split, merge, and save datasets in one of the supported formats.

Expand All @@ -123,7 +137,7 @@ for path, image, annotation in ds:
pass
```

<details close>
<details>
<summary>👉 more dataset utils</summary>

- load
Expand Down Expand Up @@ -213,7 +227,7 @@ for path, image, annotation in ds:

</details>

## 🎬 tutorials
## 🎬 Tutorials

Want to learn how to use Supervision? Explore our [how-to guides](https://supervision.roboflow.com/develop/how_to/detect_and_annotate/), [end-to-end examples](./examples), [cheatsheet](https://roboflow.github.io/cheatsheet-supervision/), and [cookbooks](https://supervision.roboflow.com/develop/cookbooks/)!

Expand All @@ -233,7 +247,7 @@ Want to learn how to use Supervision? Explore our [how-to guides](https://superv
<div><strong>Created: 11 Jan 2024</strong></div>
<br/>Learn how to track and estimate the speed of vehicles using YOLO, ByteTrack, and Roboflow Inference. This comprehensive tutorial covers object detection, multi-object tracking, filtering detections, perspective transformation, speed estimation, visualization improvements, and more.</p>

## 💜 built with supervision
## 💜 Built with Supervision

Did you build something cool using supervision? [Let us know!](https://github.com/roboflow/supervision/discussions/categories/built-with-supervision)

Expand All @@ -243,11 +257,11 @@ https://github.com/roboflow/supervision/assets/26109316/c9436828-9fbf-4c25-ae8c-

https://github.com/roboflow/supervision/assets/26109316/3ac6982f-4943-4108-9b7f-51787ef1a69f

## 📚 documentation
## 📚 Documentation

Visit our [documentation](https://roboflow.github.io/supervision) page to learn how supervision can help you build computer vision applications faster and more reliably.

## 🏆 contribution
## 🏆 Contribution

We love your input! Please see our [contributing guide](.github/CONTRIBUTING.md) to get started. Thank you 🙏 to all our contributors!

Expand All @@ -259,8 +273,6 @@ We love your input! Please see our [contributing guide](.github/CONTRIBUTING.md)

<br>

<div align="center">

<div align="center">
<a href="https://youtube.com/roboflow">
<img
Expand Down Expand Up @@ -295,13 +307,12 @@ We love your input! Please see our [contributing guide](.github/CONTRIBUTING.md)
src="https://media.roboflow.com/notebooks/template/icons/purple/forum.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672949633584"
width="3%"
/>
</a>
<img src="https://raw.githubusercontent.com/ultralytics/assets/main/social/logo-transparent.png" width="3%"/>
<a href="https://blog.roboflow.com">
<img
src="https://media.roboflow.com/notebooks/template/icons/purple/blog.png?ik-sdk-version=javascript-1.4.3&updatedAt=1672949633605"
width="3%"
/>
</a>
</a>
</div>
</div>
4 changes: 3 additions & 1 deletion examples/time_in_zone/scripts/stream_from_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ def run_command_in_thread(command: list) -> Thread:


def run_command(command: list) -> int:
process = subprocess.run(command) # noqa: S603 # TODO: Validate command input to prevent execution of untrusted input
if command[0] not in ["docker", "ffmpeg"]:
raise ValueError(f"Command {command[0]} not allowed")
process = subprocess.run(command) # noqa: S603
Comment on lines +87 to +89
return process.returncode


Expand Down
12 changes: 8 additions & 4 deletions src/supervision/annotators/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2656,7 +2656,8 @@ def annotate(
if custom_values is not None:
value = custom_values[detection_idx]
else:
assert detections.confidence is not None # MyPy type hint
if detections.confidence is None:
raise ValueError("detections.confidence cannot be None")
value = detections.confidence[detection_idx]

color = resolve_color(
Expand Down Expand Up @@ -3127,7 +3128,8 @@ def annotate(

@staticmethod
def _use_obb(detections_1: Detections, detections_2: Detections) -> bool:
assert not detections_1.is_empty() or not detections_2.is_empty()
if detections_1.is_empty() and detections_2.is_empty():
raise ValueError("Both detections_1 and detections_2 cannot be empty.")
is_obb_1 = ORIENTED_BOX_COORDINATES in detections_1.data
is_obb_2 = ORIENTED_BOX_COORDINATES in detections_2.data
return (
Expand All @@ -3138,7 +3140,8 @@ def _use_obb(detections_1: Detections, detections_2: Detections) -> bool:

@staticmethod
def _use_mask(detections_1: Detections, detections_2: Detections) -> bool:
assert not detections_1.is_empty() or not detections_2.is_empty()
if detections_1.is_empty() and detections_2.is_empty():
raise ValueError("Both detections_1 and detections_2 cannot be empty.")
is_mask_1 = detections_1.mask is not None
is_mask_2 = detections_2.mask is not None
return (
Expand Down Expand Up @@ -3185,7 +3188,8 @@ def _mask_from_mask(
mask = np.zeros(scene.shape[:2], dtype=np.bool_)
if detections.is_empty():
return mask
assert detections.mask is not None
if detections.mask is None:
raise ValueError("detections.mask cannot be None")

for detections_mask in detections.mask:
mask |= detections_mask.astype(np.bool_)
Expand Down
60 changes: 36 additions & 24 deletions src/supervision/detection/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1883,15 +1883,17 @@ def from_vlm(
vlm = _validate_vlm_parameters(vlm, result, kwargs)

if vlm == VLM.PALIGEMMA:
assert isinstance(result, str)
if not isinstance(result, str):
raise TypeError(f"Expected string result for {vlm}, got {type(result)}")
xyxy, class_id, class_name = from_paligemma(result, **kwargs)
data: dict[str, npt.NDArray[np.generic] | list[Any]] = {
CLASS_NAME_DATA_FIELD: class_name,
}
return cls(xyxy=xyxy, class_id=class_id, data=data)

if vlm == VLM.QWEN_2_5_VL:
assert isinstance(result, str)
if not isinstance(result, str):
raise TypeError(f"Expected string result for {vlm}, got {type(result)}")
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(
Expand All @@ -1902,7 +1904,8 @@ def from_vlm(
)

if vlm == VLM.QWEN_3_VL:
assert isinstance(result, str)
if not isinstance(result, str):
raise TypeError(f"Expected string result for {vlm}, got {type(result)}")
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)
Expand All @@ -1911,13 +1914,15 @@ def from_vlm(
)

if vlm == VLM.DEEPSEEK_VL_2:
assert isinstance(result, str)
if not isinstance(result, str):
raise TypeError(f"Expected string result for {vlm}, got {type(result)}")
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)

if vlm == VLM.FLORENCE_2:
assert isinstance(result, dict)
if not isinstance(result, dict):
raise TypeError(f"Expected dict result for {vlm}, got {type(result)}")
xyxy, labels, mask, xyxyxyxy = from_florence_2(result, **kwargs)
if len(xyxy) == 0:
empty = cls.empty()
Expand All @@ -1933,18 +1938,21 @@ def from_vlm(
return cls(xyxy=xyxy, mask=mask, data=data)

if vlm == VLM.GOOGLE_GEMINI_2_0:
assert isinstance(result, str)
if not isinstance(result, str):
raise TypeError(f"Expected string result for {vlm}, got {type(result)}")
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)

if vlm == VLM.MOONDREAM:
assert isinstance(result, dict)
if not isinstance(result, dict):
raise TypeError(f"Expected dict result for {vlm}, got {type(result)}")
xyxy = from_moondream(result, **kwargs)
return cls(xyxy=xyxy)

if vlm == VLM.GOOGLE_GEMINI_2_5:
assert isinstance(result, str)
if not isinstance(result, str):
raise TypeError(f"Expected string result for {vlm}, got {type(result)}")
gemini_result = from_google_gemini_2_5(result, **kwargs)
data = {CLASS_NAME_DATA_FIELD: gemini_result[2]}
return cls(
Expand Down Expand Up @@ -2493,17 +2501,19 @@ def with_nms(
if len(self) == 0:
return self

assert self.confidence is not None, (
"Detections confidence must be given for NMS to be executed."
)
if self.confidence is None:
raise ValueError(
"Detections confidence must be given for NMS to be executed."
)

if class_agnostic:
predictions = np.hstack((self.xyxy, self.confidence.reshape(-1, 1)))
else:
assert self.class_id is not None, (
"Detections class_id must be given for NMS to be executed. If you"
" intended to perform class agnostic NMS set class_agnostic=True."
)
if self.class_id is None:
raise ValueError(
"Detections class_id must be given for NMS to be executed. If you"
" intended to perform class agnostic NMS set class_agnostic=True."
)
Comment on lines +2504 to +2516
predictions = np.hstack(
(
self.xyxy,
Expand Down Expand Up @@ -2571,17 +2581,19 @@ def with_nmm(
if len(self) == 0:
return self

assert self.confidence is not None, (
"Detections confidence must be given for NMM to be executed."
)
if self.confidence is None:
raise ValueError(
"Detections confidence must be given for NMM to be executed."
)

if class_agnostic:
predictions = np.hstack((self.xyxy, self.confidence.reshape(-1, 1)))
else:
assert self.class_id is not None, (
"Detections class_id must be given for NMM to be executed. If you"
" intended to perform class agnostic NMM set class_agnostic=True."
)
if self.class_id is None:
raise ValueError(
"Detections class_id must be given for NMM to be executed. If you"
" intended to perform class agnostic NMM set class_agnostic=True."
)
Comment on lines +2584 to +2596
predictions = np.hstack(
(
self.xyxy,
Expand Down Expand Up @@ -2692,8 +2704,8 @@ def merge_inner_detection_object_pair(
if detections_1.confidence is None and detections_2.confidence is None:
merged_confidence = None
else:
assert detections_1.confidence is not None
assert detections_2.confidence is not None
if detections_1.confidence is None or detections_2.confidence is None:
raise ValueError("Both Detections objects must have confidence scores")
detection_1_area = (xyxy_1[2] - xyxy_1[0]) * (xyxy_1[3] - xyxy_1[1])
detections_2_area = (xyxy_2[2] - xyxy_2[0]) * (xyxy_2[3] - xyxy_2[1])
merged_confidence = (
Expand Down
12 changes: 8 additions & 4 deletions src/supervision/detection/line_zone.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,10 @@ def _compute_anchor_sides(
The third array, `has_any_right_trigger`, indicates if the
detection's anchor is on the right side of the line zone.
"""
assert len(detections) > 0
assert detections.tracker_id is not None
if len(detections) == 0:
raise ValueError("Detections cannot be empty")
if detections.tracker_id is None:
raise ValueError("detections.tracker_id cannot be None")

all_anchors = np.array(
[
Expand Down Expand Up @@ -312,7 +314,8 @@ def _update_class_id_to_name(self, detections: Detections) -> None:
Assumes that class_names are only provided when class_ids are.
"""
class_names = detections.data.get(CLASS_NAME_DATA_FIELD)
assert class_names is None or detections.class_id is not None
if class_names is not None and detections.class_id is None:
raise ValueError("class_names is provided but detections.class_id is None")

if detections.class_id is None:
return
Expand Down Expand Up @@ -618,7 +621,8 @@ def _draw_oriented_label(
text_box_color=self.color,
line_angle_degrees=line_angle_degrees,
)
assert label_image.shape[0] == label_image.shape[1]
if label_image.shape[0] != label_image.shape[1]:
raise ValueError("label_image must be square")

text_width, text_height = cv2.getTextSize(
text, cv2.FONT_HERSHEY_SIMPLEX, self.text_scale, self.text_thickness
Expand Down
2 changes: 1 addition & 1 deletion src/supervision/detection/tools/csv_sink.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def append(
and tuples are broadcast unchanged.
"""
if not self.writer:
raise Exception(
raise OSError(
f"Cannot append to CSV: The file '{self.file_name}' is not open."
)
field_names = CSVSink.parse_field_names(detections, custom_data)
Expand Down
Loading