diff --git a/README.md b/README.md
index 7a9d6a3248..0f19ee65dd 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
+
-## 👋 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.
@@ -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.
@@ -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)
@@ -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)
@@ -82,7 +95,7 @@ len(detections)
-### 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.
@@ -90,7 +103,8 @@ Supervision offers a wide range of highly customizable [annotators](https://supe
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()
@@ -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.
@@ -123,7 +137,7 @@ for path, image, annotation in ds:
pass
```
-
+
👉 more dataset utils
- load
@@ -213,7 +227,7 @@ for path, image, annotation in ds:
-## 🎬 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/)!
@@ -233,7 +247,7 @@ Want to learn how to use Supervision? Explore our [how-to guides](https://superv
Created: 11 Jan 2024
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.
-## 💜 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)
@@ -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!
@@ -259,8 +273,6 @@ We love your input! Please see our [contributing guide](.github/CONTRIBUTING.md)
-
diff --git a/examples/time_in_zone/scripts/stream_from_file.py b/examples/time_in_zone/scripts/stream_from_file.py
index 4208f4d639..cc8742e7e9 100644
--- a/examples/time_in_zone/scripts/stream_from_file.py
+++ b/examples/time_in_zone/scripts/stream_from_file.py
@@ -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
return process.returncode
diff --git a/src/supervision/annotators/core.py b/src/supervision/annotators/core.py
index d63e4501ba..9a869a6f66 100644
--- a/src/supervision/annotators/core.py
+++ b/src/supervision/annotators/core.py
@@ -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(
@@ -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 (
@@ -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 (
@@ -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_)
diff --git a/src/supervision/detection/core.py b/src/supervision/detection/core.py
index aac4d7df40..3ff79bab10 100644
--- a/src/supervision/detection/core.py
+++ b/src/supervision/detection/core.py
@@ -1883,7 +1883,8 @@ 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,
@@ -1891,7 +1892,8 @@ def from_vlm(
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(
@@ -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)
@@ -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()
@@ -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(
@@ -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."
+ )
predictions = np.hstack(
(
self.xyxy,
@@ -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."
+ )
predictions = np.hstack(
(
self.xyxy,
@@ -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 = (
diff --git a/src/supervision/detection/line_zone.py b/src/supervision/detection/line_zone.py
index 3178fca24a..436e367692 100644
--- a/src/supervision/detection/line_zone.py
+++ b/src/supervision/detection/line_zone.py
@@ -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(
[
@@ -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
@@ -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
diff --git a/src/supervision/detection/tools/csv_sink.py b/src/supervision/detection/tools/csv_sink.py
index 510e06da30..f7c1c18e8c 100644
--- a/src/supervision/detection/tools/csv_sink.py
+++ b/src/supervision/detection/tools/csv_sink.py
@@ -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)
diff --git a/src/supervision/detection/utils/converters.py b/src/supervision/detection/utils/converters.py
index 9626daf6dd..dcaf1ca55a 100644
--- a/src/supervision/detection/utils/converters.py
+++ b/src/supervision/detection/utils/converters.py
@@ -691,8 +691,10 @@ def mask_to_rle(
{ align=center width="800" }
"""
- assert mask.ndim == 2, "Input mask must be 2D"
- assert mask.size != 0, "Input mask cannot be empty"
+ if mask.ndim != 2:
+ raise ValueError("Input mask must be 2D")
+ if mask.size == 0:
+ raise ValueError("Input mask cannot be empty")
counts: list[int] = cast(list[int], _mask_to_rle_counts(mask).tolist())
if compressed:
diff --git a/src/supervision/detection/utils/iou_and_nms.py b/src/supervision/detection/utils/iou_and_nms.py
index df1f6ea71c..39a4bcb43c 100644
--- a/src/supervision/detection/utils/iou_and_nms.py
+++ b/src/supervision/detection/utils/iou_and_nms.py
@@ -352,9 +352,8 @@ def box_iou_batch_with_jaccard(
```
"""
- assert len(is_crowd) == len(boxes_true), (
- "`is_crowd` must have the same length as `boxes_true`"
- )
+ if len(is_crowd) != len(boxes_true):
+ raise ValueError("`is_crowd` must have the same length as `boxes_true`")
if len(boxes_detection) == 0 or len(boxes_true) == 0:
return cast(npt.NDArray[np.float64], np.array([]))
ious: npt.NDArray[np.float64] = np.zeros(
@@ -728,10 +727,11 @@ def mask_non_max_suppression(
AssertionError: If `iou_threshold` is not within the closed
range from `0` to `1`.
"""
- assert 0 <= iou_threshold <= 1, (
- "Value of `iou_threshold` must be in the closed range from 0 to 1, "
- f"{iou_threshold} given."
- )
+ if not 0 <= iou_threshold <= 1:
+ raise ValueError(
+ "Value of `iou_threshold` must be in the closed range from 0 to 1, "
+ f"{iou_threshold} given."
+ )
rows, columns = predictions.shape
if columns == 5:
@@ -821,10 +821,11 @@ def box_non_max_suppression(
AssertionError: If `iou_threshold` is not within the
closed range from `0` to `1`.
"""
- assert 0 <= iou_threshold <= 1, (
- "Value of `iou_threshold` must be in the closed range from 0 to 1, "
- f"{iou_threshold} given."
- )
+ if not 0 <= iou_threshold <= 1:
+ raise ValueError(
+ "Value of `iou_threshold` must be in the closed range from 0 to 1, "
+ f"{iou_threshold} given."
+ )
sort_index, predictions, categories = _prepare_predictions_for_nms(predictions)
ious = box_iou_batch(predictions[:, :4], predictions[:, :4], overlap_metric)
keep = _nms_loop_from_iou_matrix(ious, categories, iou_threshold)
@@ -1153,10 +1154,11 @@ def oriented_box_non_max_suppression(
>>> keep
array([ True, False])
"""
- assert 0 <= iou_threshold <= 1, (
- "Value of `iou_threshold` must be in the closed range from 0 to 1, "
- f"{iou_threshold} given."
- )
+ if not 0 <= iou_threshold <= 1:
+ raise ValueError(
+ "Value of `iou_threshold` must be in the closed range from 0 to 1, "
+ f"{iou_threshold} given."
+ )
for name, arr in (("predictions", predictions), ("oriented_boxes", oriented_boxes)):
if name == "predictions":
if arr.ndim != 2 or arr.shape[1] not in (5, 6):
@@ -1297,10 +1299,11 @@ class are used by the grouping logic; overlap is computed on
f"`predictions` and `oriented_boxes` must have the same length, "
f"got {len(predictions)} and {len(oriented_boxes)}."
)
- assert 0 <= iou_threshold <= 1, (
- "Value of `iou_threshold` must be in the closed range from 0 to 1, "
- f"{iou_threshold} given."
- )
+ if not 0 <= iou_threshold <= 1:
+ raise ValueError(
+ "Value of `iou_threshold` must be in the closed range from 0 to 1, "
+ f"{iou_threshold} given."
+ )
def group_within(global_indices: npt.NDArray[np.int_]) -> list[list[int]]:
return _group_overlapping_oriented_boxes(
diff --git a/src/supervision/detection/vlm.py b/src/supervision/detection/vlm.py
index 2b6b7bb273..f3cc87ebbf 100644
--- a/src/supervision/detection/vlm.py
+++ b/src/supervision/detection/vlm.py
@@ -515,7 +515,8 @@ def from_florence_2(
`obb_boxes` is an optional array of shape `(n, 4, 2)` with oriented
bounding boxes.
"""
- assert len(result) == 1, f"Expected result with a single element. Got: {result}"
+ if len(result) != 1:
+ raise ValueError(f"Expected result with a single element. Got: {result}")
task = next(iter(result.keys()))
if task not in SUPPORTED_TASKS_FLORENCE_2:
raise ValueError(
@@ -564,18 +565,20 @@ def from_florence_2(
return xyxy, labels, None, None
if task in ["
", ""]:
- assert isinstance(result, str), (
- f"Expected string as result, got {type(result)}"
- )
+ if not isinstance(result, str):
+ raise TypeError(
+ f"Expected string as result, got {type(result)}"
+ )
if result == "No object detected.":
return np.empty((0, 4), dtype=np.float32), np.array([]), None, None
pattern = re.compile(r"")
match = pattern.search(result)
- assert match is not None, (
- f"Expected string to end in location tags, but got {result}"
- )
+ if match is None:
+ raise ValueError(
+ f"Expected string to end in location tags, but got {result}"
+ )
w, h = _validate_resolution(resolution_wh)
xyxy = np.array([match.groups()], dtype=np.float32)
diff --git a/src/supervision/key_points/annotators.py b/src/supervision/key_points/annotators.py
index f1f2a873d1..96a0f2ec78 100644
--- a/src/supervision/key_points/annotators.py
+++ b/src/supervision/key_points/annotators.py
@@ -84,7 +84,8 @@ def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType:
```
"""
- assert isinstance(scene, np.ndarray)
+ if not isinstance(scene, np.ndarray):
+ raise TypeError("scene must be a numpy.ndarray")
if len(key_points) == 0:
return scene
@@ -205,7 +206,8 @@ def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType:
```
"""
- assert isinstance(scene, np.ndarray)
+ if not isinstance(scene, np.ndarray):
+ raise TypeError("scene must be a numpy.ndarray")
if len(key_points) == 0:
return scene
@@ -441,7 +443,8 @@ def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType:
```
"""
- assert isinstance(scene, np.ndarray)
+ if not isinstance(scene, np.ndarray):
+ raise TypeError("scene must be a numpy.ndarray")
if len(key_points) == 0:
return scene
@@ -541,7 +544,8 @@ def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType:
```
"""
- assert isinstance(scene, np.ndarray)
+ if not isinstance(scene, np.ndarray):
+ raise TypeError("scene must be a numpy.ndarray")
if len(key_points) == 0:
return scene
@@ -643,7 +647,8 @@ def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType:
```
"""
- assert isinstance(scene, np.ndarray)
+ if not isinstance(scene, np.ndarray):
+ raise TypeError("scene must be a numpy.ndarray")
if len(key_points) == 0:
return scene
@@ -823,7 +828,8 @@ def annotate(
```
"""
- assert isinstance(scene, np.ndarray)
+ if not isinstance(scene, np.ndarray):
+ raise TypeError("scene must be a numpy.ndarray")
font = cv2.FONT_HERSHEY_SIMPLEX
skeletons_count, points_count, _ = key_points.xy.shape
diff --git a/src/supervision/metrics/core.py b/src/supervision/metrics/core.py
index ad6a17818e..eb7179b982 100644
--- a/src/supervision/metrics/core.py
+++ b/src/supervision/metrics/core.py
@@ -16,21 +16,27 @@ def update(self, *args: Any, **kwargs: Any) -> Metric:
Add data to the metric, without computing the result.
Return the metric itself to allow method chaining.
"""
- raise NotImplementedError
+ raise NotImplementedError(
+ f"Method 'update' must be implemented by {self.__class__.__name__}."
+ )
@abstractmethod
def reset(self) -> None:
"""
Reset internal metric state.
"""
- raise NotImplementedError
+ raise NotImplementedError(
+ f"Method 'reset' must be implemented by {self.__class__.__name__}."
+ )
@abstractmethod
def compute(self, *args: Any, **kwargs: Any) -> Any:
"""
Compute the metric from the internal state and return the result.
"""
- raise NotImplementedError
+ raise NotImplementedError(
+ f"Method 'compute' must be implemented by {self.__class__.__name__}."
+ )
class MetricTarget(Enum):
diff --git a/src/supervision/tracker/byte_tracker/single_object_track.py b/src/supervision/tracker/byte_tracker/single_object_track.py
index 26f6bb6831..17ba3a93e9 100644
--- a/src/supervision/tracker/byte_tracker/single_object_track.py
+++ b/src/supervision/tracker/byte_tracker/single_object_track.py
@@ -49,9 +49,12 @@ def __init__(
self.external_track_id = self.external_id_counter.NO_ID
def predict(self) -> None:
- assert self.mean is not None
- assert self.covariance is not None
- assert self.kalman_filter is not None
+ if self.mean is None:
+ raise ValueError("mean cannot be None")
+ if self.covariance is None:
+ raise ValueError("covariance cannot be None")
+ if self.kalman_filter is None:
+ raise ValueError("kalman_filter cannot be None")
mean_state = self.mean.copy()
if self.state != TrackState.Tracked:
mean_state[7] = 0
@@ -65,8 +68,10 @@ def multi_predict(stracks: list[STrack], shared_kalman: KalmanFilter) -> None:
multi_mean = []
multi_covariance = []
for i, st in enumerate(stracks):
- assert st.mean is not None
- assert st.covariance is not None
+ if st.mean is None:
+ raise ValueError("st.mean cannot be None")
+ if st.covariance is None:
+ raise ValueError("st.covariance cannot be None")
multi_mean.append(st.mean.copy())
multi_covariance.append(st.covariance)
if st.state != TrackState.Tracked:
@@ -98,9 +103,12 @@ def activate(self, kalman_filter: KalmanFilter, frame_id: int) -> None:
self.start_frame = frame_id
def re_activate(self, new_track: STrack, frame_id: int) -> None:
- assert self.kalman_filter is not None
- assert self.mean is not None
- assert self.covariance is not None
+ if self.kalman_filter is None:
+ raise ValueError("kalman_filter cannot be None")
+ if self.mean is None:
+ raise ValueError("mean cannot be None")
+ if self.covariance is None:
+ raise ValueError("covariance cannot be None")
self.mean, self.covariance = self.kalman_filter.update(
self.mean, self.covariance, self.tlwh_to_xyah(new_track.tlwh)
)
@@ -118,9 +126,12 @@ def update(self, new_track: STrack, frame_id: int) -> None:
new_track: The new track data.
frame_id: The current frame ID.
"""
- assert self.kalman_filter is not None
- assert self.mean is not None
- assert self.covariance is not None
+ if self.kalman_filter is None:
+ raise ValueError("kalman_filter cannot be None")
+ if self.mean is None:
+ raise ValueError("mean cannot be None")
+ if self.covariance is None:
+ raise ValueError("covariance cannot be None")
self.frame_id = frame_id
self.tracklet_len += 1
diff --git a/src/supervision/utils/image.py b/src/supervision/utils/image.py
index a4768e948a..7187aaa9e5 100644
--- a/src/supervision/utils/image.py
+++ b/src/supervision/utils/image.py
@@ -132,7 +132,8 @@ def scale_image(image: ImageType, scale_factor: float) -> ImageType:
{ align=center width="1000" }
""" # noqa E501 // docs
- assert isinstance(image, np.ndarray)
+ if not isinstance(image, np.ndarray):
+ raise TypeError("image must be a numpy.ndarray")
if scale_factor <= 0:
raise ValueError("Scale factor must be positive.")
@@ -190,7 +191,8 @@ def resize_image(
{ align=center width="1000" }
""" # noqa E501 // docs
- assert isinstance(image, np.ndarray)
+ if not isinstance(image, np.ndarray):
+ raise TypeError("image must be a numpy.ndarray")
if keep_aspect_ratio:
image_ratio = image.shape[1] / image.shape[0]
target_ratio = resolution_wh[0] / resolution_wh[1]
@@ -252,7 +254,8 @@ def letterbox_image(
{ align=center width="1000" }
""" # noqa E501 // docs
- assert isinstance(image, np.ndarray)
+ if not isinstance(image, np.ndarray):
+ raise TypeError("image must be a numpy.ndarray")
color = unify_to_bgr(color=color)
resized_image = resize_image(
image=image, resolution_wh=resolution_wh, keep_aspect_ratio=True
@@ -389,7 +392,8 @@ def tint_image(
{ align=center width="1000" }
""" # noqa E501 // docs
- assert isinstance(image, np.ndarray)
+ if not isinstance(image, np.ndarray):
+ raise TypeError("image must be a numpy.ndarray")
if not 0.0 <= opacity <= 1.0:
raise ValueError("opacity must be between 0.0 and 1.0")
@@ -744,11 +748,13 @@ def _establish_grid_size(
return _negotiate_grid_size(images=images)
if grid_size[0] is None:
columns = grid_size[1]
- assert columns is not None
+ if columns is None:
+ raise ValueError("columns cannot be None")
return math.ceil(len(images) / columns), columns
if grid_size[1] is None:
rows = grid_size[0]
- assert rows is not None
+ if rows is None:
+ raise ValueError("rows cannot be None")
return rows, math.ceil(len(images) / rows)
return cast(tuple[int, int], grid_size)
diff --git a/src/supervision/utils/video.py b/src/supervision/utils/video.py
index 1d3fe2a464..43c0f977c6 100644
--- a/src/supervision/utils/video.py
+++ b/src/supervision/utils/video.py
@@ -59,7 +59,7 @@ class VideoInfo:
def from_video_path(cls, video_path: str) -> VideoInfo:
video = cv2.VideoCapture(video_path)
if not video.isOpened():
- raise Exception(f"Could not open video at {video_path}")
+ raise OSError(f"Could not open video at {video_path}")
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
@@ -209,10 +209,10 @@ def _validate_and_setup_video(
) -> tuple[cv2.VideoCapture, int, int]:
video = cv2.VideoCapture(source_path)
if not video.isOpened():
- raise Exception(f"Could not open video at {source_path}")
+ raise OSError(f"Could not open video at {source_path}")
total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
if end is not None and end > total_frames:
- raise Exception("Requested frames are outbound")
+ raise ValueError("Requested frames are outbound")
start = max(start, 0)
end = min(end, total_frames) if end is not None else total_frames
diff --git a/src/supervision/validators/__init__.py b/src/supervision/validators/__init__.py
index be8c5870b4..5d52d179a4 100644
--- a/src/supervision/validators/__init__.py
+++ b/src/supervision/validators/__init__.py
@@ -1,4 +1,6 @@
-from typing import Any, Optional
+from __future__ import annotations
+
+from typing import Any
import numpy as np
from deprecate import deprecated, void
@@ -295,7 +297,7 @@ def _validate_keypoints_fields(
confidence: Any,
detection_confidence: Any = None,
visible: Any = None,
- data: Optional[dict[str, Any]] = None,
+ data: dict[str, Any] | None = None,
) -> None:
n = len(xy)
m = len(xy[0]) if len(xy) > 0 else 0