diff --git a/src/supervision/annotators/core.py b/src/supervision/annotators/core.py index 8336e11a..ae43fa1c 100644 --- a/src/supervision/annotators/core.py +++ b/src/supervision/annotators/core.py @@ -1616,7 +1616,6 @@ class RichLabelAnnotator(_BaseLabelAnnotator): ``` """ - assert isinstance(scene, Image.Image) _validate_labels(labels, detections) draw = ImageDraw.Draw(scene) diff --git a/src/supervision/key_points/annotators.py b/src/supervision/key_points/annotators.py index 63a2b7d1..02db1c9a 100644 --- a/src/supervision/key_points/annotators.py +++ b/src/supervision/key_points/annotators.py @@ -64,6 +64,9 @@ class VertexAnnotator(BaseKeyPointAnnotator): The annotated image, matching the type of `scene` (`numpy.ndarray` or `PIL.Image.Image`) + Raises: + TypeError: If `scene` is not a `numpy.ndarray` or `PIL.Image.Image`. + Example: ```pycon >>> import numpy as np @@ -84,7 +87,6 @@ class VertexAnnotator(BaseKeyPointAnnotator): ``` """ - assert isinstance(scene, np.ndarray) if len(key_points) == 0: return scene @@ -154,6 +156,9 @@ class EdgeAnnotator(BaseKeyPointAnnotator): The annotated image, matching the type of `scene` (`numpy.ndarray` or `PIL.Image.Image`) + Raises: + TypeError: If `scene` is not a `numpy.ndarray` or `PIL.Image.Image`. + Example: Single-skeleton example: @@ -205,7 +210,6 @@ class EdgeAnnotator(BaseKeyPointAnnotator): ``` """ - assert isinstance(scene, np.ndarray) if len(key_points) == 0: return scene @@ -416,6 +420,9 @@ class VertexEllipseAreaAnnotator(_BaseVertexEllipseAnnotator): Returns: The annotated image, matching the type of ``scene``. + Raises: + TypeError: If `scene` is not a `numpy.ndarray` or `PIL.Image.Image`. + Example: ```pycon >>> import numpy as np @@ -445,7 +452,6 @@ class VertexEllipseAreaAnnotator(_BaseVertexEllipseAnnotator): ``` """ - assert isinstance(scene, np.ndarray) if len(key_points) == 0: return scene @@ -514,6 +520,9 @@ class VertexEllipseOutlineAnnotator(_BaseVertexEllipseAnnotator): Returns: The annotated image, matching the type of ``scene``. + Raises: + TypeError: If `scene` is not a `numpy.ndarray` or `PIL.Image.Image`. + Example: ```pycon >>> import numpy as np @@ -544,7 +553,6 @@ class VertexEllipseOutlineAnnotator(_BaseVertexEllipseAnnotator): ``` """ - assert isinstance(scene, np.ndarray) if len(key_points) == 0: return scene @@ -616,6 +624,9 @@ class VertexEllipseHaloAnnotator(_BaseVertexEllipseAnnotator): Returns: The annotated image, matching the type of ``scene``. + Raises: + TypeError: If `scene` is not a `numpy.ndarray` or `PIL.Image.Image`. + Example: ```pycon >>> import numpy as np @@ -645,7 +656,6 @@ class VertexEllipseHaloAnnotator(_BaseVertexEllipseAnnotator): ``` """ - assert isinstance(scene, np.ndarray) if len(key_points) == 0: return scene @@ -737,6 +747,7 @@ class VertexLabelAnnotator: self.text_padding: int = text_padding self.smart_position = smart_position + @ensure_cv2_image_for_class_method def annotate( self, scene: ImageType, @@ -762,6 +773,9 @@ class VertexLabelAnnotator: The annotated image, matching the type of `scene` (`numpy.ndarray` or `PIL.Image.Image`) + Raises: + TypeError: If `scene` is not a `numpy.ndarray` or `PIL.Image.Image`. + Example: Single-skeleton example: @@ -824,7 +838,6 @@ class VertexLabelAnnotator: ``` """ - assert isinstance(scene, np.ndarray) font = cv2.FONT_HERSHEY_SIMPLEX skeletons_count, points_count, _ = key_points.xy.shape diff --git a/src/supervision/utils/conversion.py b/src/supervision/utils/conversion.py index 1ef2acba..741dc5e7 100644 --- a/src/supervision/utils/conversion.py +++ b/src/supervision/utils/conversion.py @@ -22,6 +22,9 @@ def ensure_cv2_image_for_class_method( is complete. Assumes the annotators modify the scene in-place. + + Raises: + TypeError: If `scene` is not a `numpy.ndarray` or `PIL.Image.Image`. """ @functools.wraps(annotate_func) @@ -35,7 +38,7 @@ def ensure_cv2_image_for_class_method( scene.paste(cv2_to_pillow(annotated_np)) return scene - raise ValueError(f"Unsupported image type: {type(scene)}") + raise TypeError(f"Unsupported image type: {type(scene)}") return cast(F, wrapper) @@ -59,6 +62,9 @@ def ensure_cv2_image_for_standalone_function( np.ndarray, converts back when processing is complete. Assumes the annotators do NOT modify the scene in-place. + + Raises: + TypeError: If `image` is not a `numpy.ndarray` or `PIL.Image.Image`. """ @functools.wraps(image_processing_fun) @@ -71,7 +77,7 @@ def ensure_cv2_image_for_standalone_function( annotated = image_processing_fun(scene, *args, **kwargs) return cv2_to_pillow(annotated) - raise ValueError(f"Unsupported image type: {type(image)}") + raise TypeError(f"Unsupported image type: {type(image)}") return cast(F, wrapper) @@ -84,6 +90,9 @@ def ensure_pil_image_for_class_method( PIL image, converts back when processing is complete. Assumes the annotators modify the scene in-place. + + Raises: + TypeError: If `scene` is not a `numpy.ndarray` or `PIL.Image.Image`. """ @functools.wraps(annotate_func) @@ -97,7 +106,7 @@ def ensure_pil_image_for_class_method( if isinstance(scene, Image.Image): return cast(ImageType, annotate_func(self, scene, *args, **kwargs)) - raise ValueError(f"Unsupported image type: {type(scene)}") + raise TypeError(f"Unsupported image type: {type(scene)}") return cast(F, wrapper) diff --git a/src/supervision/utils/image.py b/src/supervision/utils/image.py index a4768e94..e5cc5cfd 100644 --- a/src/supervision/utils/image.py +++ b/src/supervision/utils/image.py @@ -105,6 +105,7 @@ def scale_image(image: ImageType, scale_factor: float) -> ImageType: type. Raises: + TypeError: If `image` is not a `numpy.ndarray` or `PIL.Image.Image`. ValueError: If scale factor is non-positive. Examples: @@ -132,7 +133,6 @@ 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 scale_factor <= 0: raise ValueError("Scale factor must be positive.") @@ -161,6 +161,9 @@ def resize_image( Resized image matching input type. + Raises: + TypeError: If `image` is not a `numpy.ndarray` or `PIL.Image.Image`. + Examples: ```pycon >>> import numpy as np @@ -190,7 +193,6 @@ 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 keep_aspect_ratio: image_ratio = image.shape[1] / image.shape[0] target_ratio = resolution_wh[0] / resolution_wh[1] @@ -227,6 +229,9 @@ def letterbox_image( Returns: Letterboxed image matching input type. + Raises: + TypeError: If `image` is not a `numpy.ndarray` or `PIL.Image.Image`. + Note: For BGRA inputs, the alpha channel in the padding region is set to 0 (fully transparent). Grayscale inputs receive scalar padding @@ -252,7 +257,6 @@ 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) color = unify_to_bgr(color=color) resized_image = resize_image( image=image, resolution_wh=resolution_wh, keep_aspect_ratio=True @@ -372,6 +376,7 @@ def tint_image( type. Raises: + TypeError: If `image` is not a `numpy.ndarray` or `PIL.Image.Image`. ValueError: If opacity is outside range [0.0, 1.0]. Examples: @@ -389,7 +394,6 @@ 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 0.0 <= opacity <= 1.0: raise ValueError("opacity must be between 0.0 and 1.0") diff --git a/tests/key_points/test_annotators.py b/tests/key_points/test_annotators.py index 8121c027..d2eae10b 100644 --- a/tests/key_points/test_annotators.py +++ b/tests/key_points/test_annotators.py @@ -502,3 +502,30 @@ class TestVertexLabelAnnotator: def test_resolve_color_list_wrong_length_raises(self, colors, points_count): with pytest.raises(ValueError, match="Number of colors"): sv.VertexLabelAnnotator._resolve_color_list(colors, points_count) + + +class TestAnnotatorInputValidation: + """Verify that all keypoint annotators reject invalid scene types.""" + + @pytest.mark.parametrize( + ("annotator_class", "kwargs"), + [ + pytest.param(sv.VertexAnnotator, {}, id="VertexAnnotator"), + pytest.param(sv.EdgeAnnotator, {}, id="EdgeAnnotator"), + pytest.param(sv.VertexEllipseAnnotator, {}, id="VertexEllipseAnnotator"), + pytest.param( + sv.VertexEllipseOutlineAnnotator, {}, id="VertexEllipseOutlineAnnotator" + ), + pytest.param( + sv.VertexEllipseHaloAnnotator, {}, id="VertexEllipseHaloAnnotator" + ), + pytest.param(sv.VertexLabelAnnotator, {}, id="VertexLabelAnnotator"), + ], + ) + def test_annotate_wrong_scene_type_raises( + self, annotator_class, kwargs, sample_key_points + ): + """Wrong scene type raises TypeError.""" + annotator = annotator_class(**kwargs) + with pytest.raises(TypeError, match="Unsupported image type"): + annotator.annotate(scene="not_an_image", key_points=sample_key_points) diff --git a/tests/utils/test_image.py b/tests/utils/test_image.py index 3ae413f8..1326081e 100644 --- a/tests/utils/test_image.py +++ b/tests/utils/test_image.py @@ -7,6 +7,8 @@ from supervision.utils.image import ( get_image_resolution_wh, letterbox_image, resize_image, + scale_image, + tint_image, ) @@ -193,3 +195,20 @@ def test_crop_image(image, xyxy, expected_size): def test_get_image_resolution_wh(image, expected): resolution = get_image_resolution_wh(image) assert resolution == expected + + +@pytest.mark.parametrize( + ("func", "kwargs"), + [ + pytest.param(scale_image, {"scale_factor": 1.0}, id="scale_image"), + pytest.param(resize_image, {"resolution_wh": (10, 10)}, id="resize_image"), + pytest.param( + letterbox_image, {"resolution_wh": (10, 10)}, id="letterbox_image" + ), + pytest.param(tint_image, {}, id="tint_image"), + ], +) +def test_image_utils_wrong_type_raises(func, kwargs): + """Wrong image type raises TypeError via decorator.""" + with pytest.raises(TypeError, match="Unsupported image type"): + func(image="not_an_image", **kwargs)