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( ![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: 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: ![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.ndarray") 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.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( ![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.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( ![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.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