From 5302ecc5a1682a656e9c74198abbcf57d56d76ee Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:28:42 +0530 Subject: [PATCH 01/25] Refactor README for better clarity and organization Updated README to improve formatting and structure. --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7a9d6a3248..bd45b80600 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ for path, image, annotation in ds: pass ``` -
+
👉 more dataset utils - load @@ -259,8 +259,6 @@ We love your input! Please see our [contributing guide](.github/CONTRIBUTING.md)
-
-
+ -
-
From c5ad5a5f8f66d34193a82cf0e040b2a820dd458e Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:31:13 +0530 Subject: [PATCH 02/25] Revise README formatting and example paths Updated links, section headers, and example code paths in README. --- README.md | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index bd45b80600..0e07eefea0 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. @@ -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! From b498db57173160c95dd283081471dac2665702eb Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:40:49 +0530 Subject: [PATCH 03/25] Update README.md From c3923be56737d48de316a90ebb4ca9c73999d0c3 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:41:05 +0530 Subject: [PATCH 04/25] Update README.md From 1c2351e2631a4effd66332ff0fbd5bc07ddc70de Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:41:18 +0530 Subject: [PATCH 05/25] Update README.md From f7344dbd4b0e78fce91dbc241c2af86293d3bea9 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:41:30 +0530 Subject: [PATCH 06/25] Update README.md From 396632efec834bde4b2e5e8f41be252fb1230da3 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:17:51 +0530 Subject: [PATCH 07/25] refactor(detection): replace assert with ValueError in converters.py --- src/supervision/detection/utils/converters.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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( ![mask_to_rle](https://media.roboflow.com/supervision-docs/ mask-to-rle.png){ 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: From 26068c975e3b9ec7f6e50ef43f3daa73b791cf4d Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:18:59 +0530 Subject: [PATCH 08/25] refactor(detection): replace assert with ValueError in iou_and_nms.py --- .../detection/utils/iou_and_nms.py | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/supervision/detection/utils/iou_and_nms.py b/src/supervision/detection/utils/iou_and_nms.py index df1f6ea71c..53fdca496e 100644 --- a/src/supervision/detection/utils/iou_and_nms.py +++ b/src/supervision/detection/utils/iou_and_nms.py @@ -352,9 +352,11 @@ 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( + f"Length of is_crowd ({len(is_crowd)}) must be equal to " + f"length of boxes_true ({len(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 +730,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 +824,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 +1157,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 +1302,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( From bd37b27032fe7b0682b6257a50b98e4b5c1d02d9 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:19:21 +0530 Subject: [PATCH 09/25] refactor(metrics): replace assert with ValueError in mean_average_precision.py --- src/supervision/metrics/mean_average_precision.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/supervision/metrics/mean_average_precision.py b/src/supervision/metrics/mean_average_precision.py index 8d598da9c2..be09942c2b 100644 --- a/src/supervision/metrics/mean_average_precision.py +++ b/src/supervision/metrics/mean_average_precision.py @@ -481,9 +481,8 @@ def load_predictions(self, predictions: list[dict[str, Any]]) -> EvaluationDatas ids = [pred["image_id"] for pred in predictions] # Make sure the image ids from predictions exist in the current dataset - assert set(ids) == (set(ids) & set(self.get_image_ids())), ( - "Results do not correspond to current coco set" - ) + if set(ids) != (set(ids) & set(self.get_image_ids())): + raise ValueError("Results do not correspond to current coco set") # Check if the predictions contain any unsupported keys if "caption" in predictions[0]: From 314e63c3179961f2eeb3657dd115711561e612e4 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:20:03 +0530 Subject: [PATCH 10/25] refactor(detection): replace assert with ValueError in vlm.py --- src/supervision/detection/vlm.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) 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) From 96a11b6de6bf952ab49b6bf1a712732b2546ba24 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:20:31 +0530 Subject: [PATCH 11/25] refactor(detection): replace assert with ValueError in line_zone.py --- src/supervision/detection/line_zone.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/supervision/detection/line_zone.py b/src/supervision/detection/line_zone.py index 3178fca24a..0fe6603ada 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 must have a tracker_id.") 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 are provided but 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 a square image.") text_width, text_height = cv2.getTextSize( text, cv2.FONT_HERSHEY_SIMPLEX, self.text_scale, self.text_thickness From 2caf9a1556b066bcc7908482c78df2a4acd9c1aa Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:20:51 +0530 Subject: [PATCH 12/25] refactor(key_points): replace assert with TypeError in annotators.py --- src/supervision/key_points/annotators.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/supervision/key_points/annotators.py b/src/supervision/key_points/annotators.py index f1f2a873d1..2e35509dbb 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 array") 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 array") 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 array") 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 array") 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 array") 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 array") font = cv2.FONT_HERSHEY_SIMPLEX skeletons_count, points_count, _ = key_points.xy.shape From 64faba63c2a0c54667118c4be75ebaeb14d871e1 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:22:14 +0530 Subject: [PATCH 13/25] refactor(detection): replace assert with TypeError and ValueError in core.py --- src/supervision/detection/core.py | 56 ++++++++++++++++++------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/supervision/detection/core.py b/src/supervision/detection/core.py index aac4d7df40..68f2204e00 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 str result for {vlm}, got {type(result).__name__}") 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 str result for {vlm}, got {type(result).__name__}") 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 str result for {vlm}, got {type(result).__name__}") 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 str result for {vlm}, got {type(result).__name__}") 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).__name__}") 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 str result for {vlm}, got {type(result).__name__}") 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).__name__}") 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 str result for {vlm}, got {type(result).__name__}") gemini_result = from_google_gemini_2_5(result, **kwargs) data = {CLASS_NAME_DATA_FIELD: gemini_result[2]} return cls( @@ -2493,17 +2501,17 @@ 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("Non-Maximum Suppression requires `confidence` to be not `None`") 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( + "Non-Maximum Suppression requires `class_id` to be not `None` if " + "`class_agnostic` is False" + ) predictions = np.hstack( ( self.xyxy, @@ -2571,17 +2579,17 @@ 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 +2700,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 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 = ( From fad0a180fa0d621d37c377c83270c0abbf7a1e02 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:22:39 +0530 Subject: [PATCH 14/25] refactor(utils): replace assert with TypeError and ValueError in image.py --- src/supervision/utils/image.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/supervision/utils/image.py b/src/supervision/utils/image.py index a4768e948a..24f3df2c38 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: ![scale-image](https://media.roboflow.com/supervision-docs/supervision-docs-scale-image-2.png){ 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 array") if scale_factor <= 0: raise ValueError("Scale factor must be positive.") @@ -190,7 +191,8 @@ def resize_image( ![resize-image](https://media.roboflow.com/supervision-docs/supervision-docs-resize-image-2.png){ 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 array") 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( ![letterbox-image](https://media.roboflow.com/supervision-docs/supervision-docs-letterbox-image-2.png){ 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 array") 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( ![tint-image](https://media.roboflow.com/supervision-docs/supervision-docs-tint-image-2.png){ 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 array") 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 if size is None and grid is True") 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 if size is None and grid is True") return rows, math.ceil(len(images) / rows) return cast(tuple[int, int], grid_size) From 757a2f586e37ad5093d79d4ebc72ff199bf041ee Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 14 Jun 2026 10:56:17 +0000 Subject: [PATCH 15/25] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++--- src/supervision/detection/core.py | 40 +++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0e07eefea0..0f19ee65dd 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ - [👋 Hello](#-hello) - [💻 Install](#-install) - [🔥 Quickstart](#-quickstart) - - [Models](#models) - - [Annotators](#annotators) - - [Datasets](#datasets) + - [Models](#models) + - [Annotators](#annotators) + - [Datasets](#datasets) - [🎬 Tutorials](#-tutorials) - [💜 Built with Supervision](#-built-with-supervision) - [📚 Documentation](#-documentation) diff --git a/src/supervision/detection/core.py b/src/supervision/detection/core.py index 68f2204e00..053c08f32b 100644 --- a/src/supervision/detection/core.py +++ b/src/supervision/detection/core.py @@ -1884,7 +1884,9 @@ def from_vlm( if vlm == VLM.PALIGEMMA: if not isinstance(result, str): - raise TypeError(f"Expected str result for {vlm}, got {type(result).__name__}") + raise TypeError( + f"Expected str result for {vlm}, got {type(result).__name__}" + ) xyxy, class_id, class_name = from_paligemma(result, **kwargs) data: dict[str, npt.NDArray[np.generic] | list[Any]] = { CLASS_NAME_DATA_FIELD: class_name, @@ -1893,7 +1895,9 @@ def from_vlm( if vlm == VLM.QWEN_2_5_VL: if not isinstance(result, str): - raise TypeError(f"Expected str result for {vlm}, got {type(result).__name__}") + raise TypeError( + f"Expected str result for {vlm}, got {type(result).__name__}" + ) 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( @@ -1905,7 +1909,9 @@ def from_vlm( if vlm == VLM.QWEN_3_VL: if not isinstance(result, str): - raise TypeError(f"Expected str result for {vlm}, got {type(result).__name__}") + raise TypeError( + f"Expected str result for {vlm}, got {type(result).__name__}" + ) 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) @@ -1915,14 +1921,18 @@ def from_vlm( if vlm == VLM.DEEPSEEK_VL_2: if not isinstance(result, str): - raise TypeError(f"Expected str result for {vlm}, got {type(result).__name__}") + raise TypeError( + f"Expected str result for {vlm}, got {type(result).__name__}" + ) 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: if not isinstance(result, dict): - raise TypeError(f"Expected dict result for {vlm}, got {type(result).__name__}") + raise TypeError( + f"Expected dict result for {vlm}, got {type(result).__name__}" + ) xyxy, labels, mask, xyxyxyxy = from_florence_2(result, **kwargs) if len(xyxy) == 0: empty = cls.empty() @@ -1939,20 +1949,26 @@ def from_vlm( if vlm == VLM.GOOGLE_GEMINI_2_0: if not isinstance(result, str): - raise TypeError(f"Expected str result for {vlm}, got {type(result).__name__}") + raise TypeError( + f"Expected str result for {vlm}, got {type(result).__name__}" + ) 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: if not isinstance(result, dict): - raise TypeError(f"Expected dict result for {vlm}, got {type(result).__name__}") + raise TypeError( + f"Expected dict result for {vlm}, got {type(result).__name__}" + ) xyxy = from_moondream(result, **kwargs) return cls(xyxy=xyxy) if vlm == VLM.GOOGLE_GEMINI_2_5: if not isinstance(result, str): - raise TypeError(f"Expected str result for {vlm}, got {type(result).__name__}") + raise TypeError( + f"Expected str result for {vlm}, got {type(result).__name__}" + ) gemini_result = from_google_gemini_2_5(result, **kwargs) data = {CLASS_NAME_DATA_FIELD: gemini_result[2]} return cls( @@ -2502,7 +2518,9 @@ def with_nms( return self if self.confidence is None: - raise ValueError("Non-Maximum Suppression requires `confidence` to be not `None`") + raise ValueError( + "Non-Maximum Suppression requires `confidence` to be not `None`" + ) if class_agnostic: predictions = np.hstack((self.xyxy, self.confidence.reshape(-1, 1))) @@ -2580,7 +2598,9 @@ def with_nmm( return self if self.confidence is None: - raise ValueError("Detections confidence must be given for NMM to be executed.") + 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))) From 099db6f00506ab8c7a766da1754f79134bc5d28d Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:37:07 +0530 Subject: [PATCH 16/25] refactor(annotators): replace assert with ValueError and TypeError in annotators/core.py --- src/supervision/annotators/core.py | 37 ++++++++++++++++++------------ 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/supervision/annotators/core.py b/src/supervision/annotators/core.py index d63e4501ba..e2799aab62 100644 --- a/src/supervision/annotators/core.py +++ b/src/supervision/annotators/core.py @@ -1360,11 +1360,12 @@ def _draw_labels( detections: Detections, custom_color_lookup: npt.NDArray[np.int_] | None, ) -> None: - assert len(labels) == len(label_properties) == len(detections), ( - f"Number of label properties ({len(label_properties)}), " - f"labels ({len(labels)}) and detections ({len(detections)}) " - "do not match." - ) + if not (len(labels) == len(label_properties) == len(detections)): + raise ValueError( + f"Number of label properties ({len(label_properties)}), " + f"labels ({len(labels)}) and detections ({len(detections)}) " + "do not match." + ) color_lookup = ( custom_color_lookup @@ -1584,7 +1585,8 @@ def annotate( ``` """ - assert isinstance(scene, Image.Image) + if not isinstance(scene, Image.Image): + raise TypeError("scene must be a PIL Image") _validate_labels(labels, detections) draw = ImageDraw.Draw(scene) @@ -1674,11 +1676,12 @@ def _draw_labels( detections: Detections, custom_color_lookup: npt.NDArray[np.int_] | None, ) -> None: - assert len(labels) == len(label_properties) == len(detections), ( - f"Number of label properties ({len(label_properties)}), " - f"labels ({len(labels)}) and detections ({len(detections)}) " - "do not match." - ) + if not (len(labels) == len(label_properties) == len(detections)): + raise ValueError( + f"Number of label properties ({len(label_properties)}), " + f"labels ({len(labels)}) and detections ({len(detections)}) " + "do not match." + ) color_lookup = ( custom_color_lookup if custom_color_lookup is not None @@ -2656,7 +2659,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 +3131,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("At least one detections object must not 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 +3143,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("At least one detections object must not be empty.") is_mask_1 = detections_1.mask is not None is_mask_2 = detections_2.mask is not None return ( @@ -3185,7 +3191,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_) From 01cbaca9973402cbb7f8f9ce4bf72e03460156d9 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:04:23 +0530 Subject: [PATCH 17/25] refactor(tracker): replace asserts with ValueError in single_object_track.py --- .../byte_tracker/single_object_track.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/supervision/tracker/byte_tracker/single_object_track.py b/src/supervision/tracker/byte_tracker/single_object_track.py index 26f6bb6831..0c2d4a88fd 100644 --- a/src/supervision/tracker/byte_tracker/single_object_track.py +++ b/src/supervision/tracker/byte_tracker/single_object_track.py @@ -49,9 +49,8 @@ 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 or self.covariance is None or self.kalman_filter is None: + raise ValueError("mean, covariance and kalman_filter must not be None") mean_state = self.mean.copy() if self.state != TrackState.Tracked: mean_state[7] = 0 @@ -65,8 +64,8 @@ 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 or st.covariance is None: + raise ValueError("strack mean and covariance must not be None") multi_mean.append(st.mean.copy()) multi_covariance.append(st.covariance) if st.state != TrackState.Tracked: @@ -98,9 +97,8 @@ 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 or self.mean is None or self.covariance is None: + raise ValueError("mean, covariance and kalman_filter must not be None") self.mean, self.covariance = self.kalman_filter.update( self.mean, self.covariance, self.tlwh_to_xyah(new_track.tlwh) ) @@ -118,9 +116,8 @@ 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 or self.mean is None or self.covariance is None: + raise ValueError("mean, covariance and kalman_filter must not be None") self.frame_id = frame_id self.tracklet_len += 1 From f88bb35a0ebe60c52c02e79fecc6d8e15c5d4542 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:55:38 +0530 Subject: [PATCH 18/25] docs(image): fix dangling docstring word in crop_image --- src/supervision/utils/image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/supervision/utils/image.py b/src/supervision/utils/image.py index 24f3df2c38..537c8e09ec 100644 --- a/src/supervision/utils/image.py +++ b/src/supervision/utils/image.py @@ -44,8 +44,7 @@ def crop_image( Bounding box coordinates in `(x_min, y_min, x_max, y_max)` format. Returns: - Cropped image matching input - type. + Cropped image matching input type. Examples: ```pycon From ec0419085ef949f0d6663fba01b043a7b1b83f4c Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:55:38 +0530 Subject: [PATCH 19/25] docs(image): fix dangling docstring word in scale_image --- src/supervision/utils/image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/supervision/utils/image.py b/src/supervision/utils/image.py index 537c8e09ec..05e6d13698 100644 --- a/src/supervision/utils/image.py +++ b/src/supervision/utils/image.py @@ -100,8 +100,7 @@ def scale_image(image: ImageType, scale_factor: float) -> ImageType: scale_factor: Factor by which to scale the image. Returns: - Scaled image matching input - type. + Scaled image matching input type. Raises: ValueError: If scale factor is non-positive. From f4b02b15f24235c31ec491ddf998429582d0551c Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:55:38 +0530 Subject: [PATCH 20/25] docs(image): fix dangling docstring word in resize_image --- src/supervision/utils/image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/supervision/utils/image.py b/src/supervision/utils/image.py index 05e6d13698..cc0b27ab22 100644 --- a/src/supervision/utils/image.py +++ b/src/supervision/utils/image.py @@ -157,8 +157,7 @@ def resize_image( Defaults to `False`. Returns: - Resized image matching input - type. + Resized image matching input type. Examples: ```pycon From cad410bef1f1ffcdad13b97ba18e9ecd6e642451 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:55:38 +0530 Subject: [PATCH 21/25] docs(image): fix dangling docstring word in tint_image --- src/supervision/utils/image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/supervision/utils/image.py b/src/supervision/utils/image.py index cc0b27ab22..79da318bc1 100644 --- a/src/supervision/utils/image.py +++ b/src/supervision/utils/image.py @@ -368,8 +368,7 @@ def tint_image( Defaults to `0.5`. Returns: - Tinted image matching input - type. + Tinted image matching input type. Raises: ValueError: If opacity is outside range [0.0, 1.0]. From c95125bbab6af0c69dc2208e85aaf69c5a40220c Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:55:38 +0530 Subject: [PATCH 22/25] docs(image): fix dangling docstring word in grayscale_image --- src/supervision/utils/image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/supervision/utils/image.py b/src/supervision/utils/image.py index 79da318bc1..f4db5b513e 100644 --- a/src/supervision/utils/image.py +++ b/src/supervision/utils/image.py @@ -407,8 +407,7 @@ def grayscale_image(image: ImageType) -> ImageType: all three channels for compatibility with color-based drawing helpers. Args: - image: The image to convert to - grayscale. + image: The image to convert to grayscale. Returns: 3-channel grayscale image From dd6a6c02f029b272b55b6de213278218f80c81f7 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:55:39 +0530 Subject: [PATCH 23/25] docs(image): fix dangling docstring word in ImageSink --- src/supervision/utils/image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/supervision/utils/image.py b/src/supervision/utils/image.py index f4db5b513e..695ba8aea2 100644 --- a/src/supervision/utils/image.py +++ b/src/supervision/utils/image.py @@ -488,8 +488,7 @@ def __init__( Initialize context manager for saving images to directory. Args: - target_dir_path: Target directory path where images will be - saved. + target_dir_path: Target directory path where images will be saved. overwrite: Whether to overwrite existing directory. Defaults to `False`. image_name_pattern: File name pattern for saved images. From 687491eb34ac089645de4bca6b99d0683f954f12 Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:55:39 +0530 Subject: [PATCH 24/25] docs(image): fix dangling docstring word in create_tiles --- src/supervision/utils/image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/supervision/utils/image.py b/src/supervision/utils/image.py index 695ba8aea2..0f200b2e1c 100644 --- a/src/supervision/utils/image.py +++ b/src/supervision/utils/image.py @@ -593,8 +593,7 @@ def create_tiles( Args: images: Images to create tiles. Elements can be either np.ndarray or - PIL.Image, and a common representation will be agreed by the - function. + PIL.Image, and a common representation will be agreed by the function. grid_size: Expected grid size in format (n_rows, n_cols). If not given, automated grid placement will be applied. One may also provide only one out of two elements of the tuple - then grid From 2ce4ddf25baa0ba3fc218b49efbd4d17f7c6a95c Mon Sep 17 00:00:00 2001 From: Vikas saini <119332490+vikassaini77@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:55:39 +0530 Subject: [PATCH 25/25] docs(conversion): fix dangling docstring word in pillow_to_cv2 --- src/supervision/utils/conversion.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/supervision/utils/conversion.py b/src/supervision/utils/conversion.py index 00a57818bf..41fc33b651 100644 --- a/src/supervision/utils/conversion.py +++ b/src/supervision/utils/conversion.py @@ -147,8 +147,7 @@ def images_to_cv2(images: list[ImageType]) -> list[npt.NDArray[np.uint8]]: def pillow_to_cv2(image: Image.Image) -> npt.NDArray[np.uint8]: """ - Converts Pillow image into OpenCV image, handling RGB -> BGR - conversion. + Converts Pillow image into OpenCV image, handling RGB -> BGR conversion. Args: image: Pillow image (in RGB format).