diff --git a/docs/how_to/process_datasets.md b/docs/how_to/process_datasets.md
index 36c122df..acfd941c 100644
--- a/docs/how_to/process_datasets.md
+++ b/docs/how_to/process_datasets.md
@@ -331,12 +331,9 @@ for i in range(16):
annotated_image = label_annotator.annotate(annotated_image, annotations, labels)
annotated_images.append(annotated_image)
-grid = sv.create_tiles(
+sv.plot_images_grid(
annotated_images,
grid_size=(4, 4),
- single_tile_size=(400, 400),
- tile_padding_color=sv.Color.WHITE,
- tile_margin_color=sv.Color.WHITE
)
```
diff --git a/docs/stylesheets/cookbooks-card.css b/docs/stylesheets/cookbooks_card.css
similarity index 100%
rename from docs/stylesheets/cookbooks-card.css
rename to docs/stylesheets/cookbooks_card.css
diff --git a/docs/utils/image.md b/docs/utils/image.md
index 8e39136a..a24d19dd 100644
--- a/docs/utils/image.md
+++ b/docs/utils/image.md
@@ -29,10 +29,16 @@ comments: true
:::supervision.utils.image.letterbox_image
diff --git a/mkdocs.yml b/mkdocs.yml
index 394d5ddd..daf3d098 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -26,7 +26,7 @@ extra:
extra_css:
- stylesheets/extra.css
- - stylesheets/cookbooks-card.css
+ - stylesheets/cookbooks_card.css
nav:
- Home: index.md
diff --git a/supervision/__init__.py b/supervision/__init__.py
index ab45651a..04d3fb25 100644
--- a/supervision/__init__.py
+++ b/supervision/__init__.py
@@ -120,12 +120,13 @@ from supervision.utils.conversion import cv2_to_pillow, pillow_to_cv2
from supervision.utils.file import list_files_with_extensions
from supervision.utils.image import (
ImageSink,
- create_tiles,
crop_image,
+ grayscale_image,
letterbox_image,
overlay_image,
resize_image,
scale_image,
+ tint_image,
)
from supervision.utils.notebook import plot_image, plot_images_grid
from supervision.utils.video import (
@@ -206,7 +207,6 @@ __all__ = [
"clip_boxes",
"contains_holes",
"contains_multiple_segments",
- "create_tiles",
"crop_image",
"cv2_to_pillow",
"draw_filled_polygon",
@@ -222,6 +222,7 @@ __all__ = [
"get_coco_class_index_mapping",
"get_polygon_center",
"get_video_frames_generator",
+ "grayscale_image",
"letterbox_image",
"list_files_with_extensions",
"mask_iou_batch",
@@ -245,6 +246,7 @@ __all__ = [
"rle_to_mask",
"scale_boxes",
"scale_image",
+ "tint_image",
"xcycwh_to_xyxy",
"xywh_to_xyxy",
"xyxy_to_polygons",
diff --git a/supervision/annotators/base.py b/supervision/annotators/base.py
index 159ad556..9b4bbcbe 100644
--- a/supervision/annotators/base.py
+++ b/supervision/annotators/base.py
@@ -1,19 +1,7 @@
from abc import ABC, abstractmethod
-from typing import TypeVar
-
-import numpy as np
-from PIL import Image
from supervision.detection.core import Detections
-
-ImageType = TypeVar("ImageType", np.ndarray, Image.Image)
-"""
-An image of type `np.ndarray` or `PIL.Image.Image`.
-
-Unlike a `Union`, ensures the type remains consistent. If a function
-takes an `ImageType` argument and returns an `ImageType`, when you
-pass an `np.ndarray`, you will get an `np.ndarray` back.
-"""
+from supervision.draw.base import ImageType
class BaseAnnotator(ABC):
diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py
index f951e681..900d823b 100644
--- a/supervision/annotators/core.py
+++ b/supervision/annotators/core.py
@@ -9,7 +9,7 @@ import numpy.typing as npt
from PIL import Image, ImageDraw, ImageFont
from scipy.interpolate import splev, splprep
-from supervision.annotators.base import BaseAnnotator, ImageType
+from supervision.annotators.base import BaseAnnotator
from supervision.annotators.utils import (
PENDING_TRACK_ID,
ColorLookup,
@@ -29,12 +29,13 @@ from supervision.detection.utils.converters import (
polygon_to_mask,
xyxy_to_polygons,
)
+from supervision.draw.base import ImageType
from supervision.draw.color import Color, ColorPalette
from supervision.draw.utils import draw_polygon, draw_rounded_rectangle, draw_text
from supervision.geometry.core import Point, Position, Rect
from supervision.utils.conversion import (
- ensure_cv2_image_for_annotation,
- ensure_pil_image_for_annotation,
+ ensure_cv2_image_for_class_method,
+ ensure_pil_image_for_class_method,
)
from supervision.utils.image import (
crop_image,
@@ -177,7 +178,7 @@ class BoxAnnotator(BaseAnnotator):
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -260,7 +261,7 @@ class OrientedBoxAnnotator(BaseAnnotator):
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -349,7 +350,7 @@ class MaskAnnotator(BaseAnnotator):
self.opacity = opacity
self.color_lookup: ColorLookup = color_lookup
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -439,7 +440,7 @@ class PolygonAnnotator(BaseAnnotator):
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -526,7 +527,7 @@ class ColorAnnotator(BaseAnnotator):
self.color_lookup: ColorLookup = color_lookup
self.opacity = opacity
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -622,7 +623,7 @@ class HaloAnnotator(BaseAnnotator):
self.color_lookup: ColorLookup = color_lookup
self.kernel_size: int = kernel_size
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -722,7 +723,7 @@ class EllipseAnnotator(BaseAnnotator):
self.end_angle: int = end_angle
self.color_lookup: ColorLookup = color_lookup
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -814,7 +815,7 @@ class BoxCornerAnnotator(BaseAnnotator):
self.corner_length: int = corner_length
self.color_lookup: ColorLookup = color_lookup
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -903,7 +904,7 @@ class CircleAnnotator(BaseAnnotator):
self.thickness: int = thickness
self.color_lookup: ColorLookup = color_lookup
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -1002,7 +1003,7 @@ class DotAnnotator(BaseAnnotator):
self.outline_thickness = outline_thickness
self.outline_color: Color | ColorPalette = outline_color
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -1128,7 +1129,7 @@ class LabelAnnotator(_BaseLabelAnnotator):
max_line_length=max_line_length,
)
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -1438,7 +1439,7 @@ class RichLabelAnnotator(_BaseLabelAnnotator):
max_line_length=max_line_length,
)
- @ensure_pil_image_for_annotation
+ @ensure_pil_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -1665,7 +1666,7 @@ class IconAnnotator(BaseAnnotator):
self.position = icon_position
self.offset_xy = offset_xy
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self, scene: ImageType, detections: Detections, icon_path: str | list[str]
) -> ImageType:
@@ -1754,7 +1755,7 @@ class BlurAnnotator(BaseAnnotator):
"""
self.kernel_size: int = kernel_size
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -1843,7 +1844,7 @@ class TraceAnnotator(BaseAnnotator):
self.smooth = smooth
self.color_lookup: ColorLookup = color_lookup
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -1965,7 +1966,7 @@ class HeatMapAnnotator(BaseAnnotator):
self.low_hue = low_hue
self.heat_mask: npt.NDArray[np.float32] | None = None
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(self, scene: ImageType, detections: Detections) -> ImageType:
"""
Annotates the scene with a heatmap based on the provided detections.
@@ -2047,7 +2048,7 @@ class PixelateAnnotator(BaseAnnotator):
"""
self.pixel_size: int = pixel_size
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -2144,7 +2145,7 @@ class TriangleAnnotator(BaseAnnotator):
self.outline_thickness: int = outline_thickness
self.outline_color: Color | ColorPalette = outline_color
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -2256,7 +2257,7 @@ class RoundBoxAnnotator(BaseAnnotator):
raise ValueError("roundness attribute must be float between (0, 1.0]")
self.roundness: float = roundness
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -2396,7 +2397,7 @@ class PercentageBarAnnotator(BaseAnnotator):
else int(0.15 * self.height)
)
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -2577,7 +2578,7 @@ class CropAnnotator(BaseAnnotator):
self.border_thickness: int = border_thickness
self.border_color_lookup: ColorLookup = border_color_lookup
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self,
scene: ImageType,
@@ -2726,7 +2727,7 @@ class BackgroundOverlayAnnotator(BaseAnnotator):
self.opacity = opacity
self.force_box = force_box
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(self, scene: ImageType, detections: Detections) -> ImageType:
"""
Applies a colored overlay to the scene outside of the detected regions.
@@ -2824,7 +2825,7 @@ class ComparisonAnnotator:
self.label_scale = label_scale
self.text_thickness = int(self.label_scale + 1.2)
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(
self, scene: ImageType, detections_1: Detections, detections_2: Detections
) -> ImageType:
diff --git a/supervision/draw/base.py b/supervision/draw/base.py
new file mode 100644
index 00000000..e27c1d3c
--- /dev/null
+++ b/supervision/draw/base.py
@@ -0,0 +1,13 @@
+from typing import TypeVar
+
+import numpy as np
+from PIL import Image
+
+ImageType = TypeVar("ImageType", np.ndarray, Image.Image)
+"""
+An image of type `np.ndarray` or `PIL.Image.Image`.
+
+Unlike a `Union`, ensures the type remains consistent. If a function
+takes an `ImageType` argument and returns an `ImageType`, when you
+pass an `np.ndarray`, you will get an `np.ndarray` back.
+"""
diff --git a/supervision/draw/utils.py b/supervision/draw/utils.py
index ed4a9037..0d9ffe12 100644
--- a/supervision/draw/utils.py
+++ b/supervision/draw/utils.py
@@ -346,28 +346,50 @@ def draw_image(
def calculate_optimal_text_scale(resolution_wh: tuple[int, int]) -> float:
"""
- Calculate font scale based on the resolution of an image.
+ Calculate optimal font scale based on image resolution. Adjusts font scale
+ proportionally to the smallest dimension of the given image resolution for
+ consistent readability.
- Parameters:
- resolution_wh (Tuple[int, int]): A tuple representing the width and height
- of the image.
+ Args:
+ resolution_wh (tuple[int, int]): (width, height) of the image in pixels
Returns:
- float: The calculated font scale factor.
+ float: recommended font scale factor
+
+ Examples:
+ ```python
+ import supervision as sv
+
+ sv.calculate_optimal_text_scale((1920, 1080))
+ # 1.08
+ sv.calculate_optimal_text_scale((640, 480))
+ # 0.48
+ ```
"""
return min(resolution_wh) * 1e-3
def calculate_optimal_line_thickness(resolution_wh: tuple[int, int]) -> int:
"""
- Calculate line thickness based on the resolution of an image.
+ Calculate optimal line thickness based on image resolution. Adjusts the line
+ thickness for readability depending on the smallest dimension of the provided
+ image resolution.
- Parameters:
- resolution_wh (Tuple[int, int]): A tuple representing the width and height
- of the image.
+ Args:
+ resolution_wh (tuple[int, int]): (width, height) of the image in pixels
Returns:
- int: The calculated line thickness in pixels.
+ int: recommended line thickness in pixels
+
+ Examples:
+ ```python
+ import supervision as sv
+
+ sv.calculate_optimal_line_thickness((1920, 1080))
+ # 4
+ sv.calculate_optimal_line_thickness((640, 480))
+ # 2
+ ```
"""
if min(resolution_wh) < 1080:
return 2
diff --git a/supervision/key_points/annotators.py b/supervision/key_points/annotators.py
index ab9f04d1..c3f9e984 100644
--- a/supervision/key_points/annotators.py
+++ b/supervision/key_points/annotators.py
@@ -6,14 +6,14 @@ from logging import warn
import cv2
import numpy as np
-from supervision.annotators.base import ImageType
from supervision.detection.utils.boxes import pad_boxes, spread_out_boxes
+from supervision.draw.base import ImageType
from supervision.draw.color import Color
from supervision.draw.utils import draw_rounded_rectangle
from supervision.geometry.core import Rect
from supervision.key_points.core import KeyPoints
from supervision.key_points.skeletons import SKELETONS_BY_VERTEX_COUNT
-from supervision.utils.conversion import ensure_cv2_image_for_annotation
+from supervision.utils.conversion import ensure_cv2_image_for_class_method
class BaseKeyPointAnnotator(ABC):
@@ -43,7 +43,7 @@ class VertexAnnotator(BaseKeyPointAnnotator):
self.color = color
self.radius = radius
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType:
"""
Annotates the given scene with skeleton vertices based on the provided key
@@ -120,7 +120,7 @@ class EdgeAnnotator(BaseKeyPointAnnotator):
self.thickness = thickness
self.edges = edges
- @ensure_cv2_image_for_annotation
+ @ensure_cv2_image_for_class_method
def annotate(self, scene: ImageType, key_points: KeyPoints) -> ImageType:
"""
Annotates the given scene by drawing lines between specified key points to form
diff --git a/supervision/utils/conversion.py b/supervision/utils/conversion.py
index 79ec5003..b1c8f16b 100644
--- a/supervision/utils/conversion.py
+++ b/supervision/utils/conversion.py
@@ -4,10 +4,10 @@ import cv2
import numpy as np
from PIL import Image
-from supervision.annotators.base import ImageType
+from supervision.draw.base import ImageType
-def ensure_cv2_image_for_annotation(annotate_func):
+def ensure_cv2_image_for_class_method(annotate_func):
"""
Decorates `BaseAnnotator.annotate` implementations, converts scene to
an image type used internally by the annotators, converts back when annotation
@@ -32,7 +32,7 @@ def ensure_cv2_image_for_annotation(annotate_func):
return wrapper
-def ensure_cv2_image_for_processing(image_processing_fun):
+def ensure_cv2_image_for_standalone_function(image_processing_fun):
"""
Decorates image processing functions that accept np.ndarray, converting `image` to
np.ndarray, converts back when processing is complete.
@@ -55,7 +55,7 @@ def ensure_cv2_image_for_processing(image_processing_fun):
return wrapper
-def ensure_pil_image_for_annotation(annotate_func):
+def ensure_pil_image_for_class_method(annotate_func):
"""
Decorates image processing functions that accept np.ndarray, converting `image` to
PIL image, converts back when processing is complete.
diff --git a/supervision/utils/image.py b/supervision/utils/image.py
index 69609867..23981c90 100644
--- a/supervision/utils/image.py
+++ b/supervision/utils/image.py
@@ -1,12 +1,7 @@
from __future__ import annotations
-import itertools
-import math
import os
import shutil
-from collections.abc import Callable
-from functools import partial
-from typing import Literal
import cv2
import numpy as np
@@ -14,74 +9,58 @@ import numpy.typing as npt
from supervision.annotators.base import ImageType
from supervision.draw.color import Color, unify_to_bgr
-from supervision.draw.utils import calculate_optimal_text_scale, draw_text
-from supervision.geometry.core import Point
from supervision.utils.conversion import (
- cv2_to_pillow,
- ensure_cv2_image_for_processing,
- images_to_cv2,
+ ensure_cv2_image_for_standalone_function,
)
-from supervision.utils.iterables import create_batches, fill
-
-RelativePosition = Literal["top", "bottom"]
-
-MAX_COLUMNS_FOR_SINGLE_ROW_GRID = 3
+from supervision.utils.internal import deprecated
-@ensure_cv2_image_for_processing
+@ensure_cv2_image_for_standalone_function
def crop_image(
image: ImageType,
xyxy: npt.NDArray[int] | list[int] | tuple[int, int, int, int],
) -> ImageType:
"""
- Crops the given image based on the given bounding box.
+ Crop image based on bounding box coordinates.
Args:
- image (ImageType): The image to be cropped. `ImageType` is a flexible type,
- accepting either `numpy.ndarray` or `PIL.Image.Image`.
- xyxy (Union[np.ndarray, List[int], Tuple[int, int, int, int]]): A bounding box
- coordinates in the format `(x_min, y_min, x_max, y_max)`, accepted as either
- a `numpy.ndarray`, a `list`, or a `tuple`.
+ image (`numpy.ndarray` or `PIL.Image.Image`): The image to crop.
+ xyxy (`numpy.array`, `list[int]`, or `tuple[int, int, int, int]`):
+ Bounding box coordinates in `(x_min, y_min, x_max, y_max)` format.
Returns:
- (ImageType): The cropped image. The type is determined by the input type and
- may be either a `numpy.ndarray` or `PIL.Image.Image`.
-
- === "OpenCV"
+ (`numpy.ndarray` or `PIL.Image.Image`): Cropped image matching input
+ type.
+ Examples:
```python
import cv2
import supervision as sv
- image = cv2.imread(
)
+ image = cv2.imread("source.png")
image.shape
# (1080, 1920, 3)
- xyxy = [200, 400, 600, 800]
+ xyxy = (200, 400, 600, 800)
cropped_image = sv.crop_image(image=image, xyxy=xyxy)
cropped_image.shape
# (400, 400, 3)
```
- === "Pillow"
-
```python
from PIL import Image
import supervision as sv
- image = Image.open()
+ image = Image.open("source.png")
image.size
# (1920, 1080)
- xyxy = [200, 400, 600, 800]
+ xyxy = (200, 400, 600, 800)
cropped_image = sv.crop_image(image=image, xyxy=xyxy)
cropped_image.size
# (400, 400)
```
-
- { align=center width="800" }
- """ # noqa E501 // docs
-
+ """
if isinstance(xyxy, (list, tuple)):
xyxy = np.array(xyxy)
xyxy = np.round(xyxy).astype(int)
@@ -89,31 +68,28 @@ def crop_image(
return image[y_min:y_max, x_min:x_max]
-@ensure_cv2_image_for_processing
+@ensure_cv2_image_for_standalone_function
def scale_image(image: ImageType, scale_factor: float) -> ImageType:
"""
- Scales the given image based on the given scale factor.
+ Scale image by given factor. Scale factor > 1.0 zooms in, < 1.0 zooms out.
Args:
- image (ImageType): The image to be scaled. `ImageType` is a flexible type,
- accepting either `numpy.ndarray` or `PIL.Image.Image`.
- scale_factor (float): The factor by which the image will be scaled. Scale
- factor > `1.0` zooms in, < `1.0` zooms out.
+ image (`numpy.ndarray` or `PIL.Image.Image`): The image to scale.
+ scale_factor (`float`): Factor by which to scale the image.
Returns:
- (ImageType): The scaled image. The type is determined by the input type and
- may be either a `numpy.ndarray` or `PIL.Image.Image`.
+ (`numpy.ndarray` or `PIL.Image.Image`): Scaled image matching input
+ type.
Raises:
- ValueError: If the scale factor is non-positive.
-
- === "OpenCV"
+ ValueError: If scale factor is non-positive.
+ Examples:
```python
import cv2
import supervision as sv
- image = cv2.imread()
+ image = cv2.imread("source.png")
image.shape
# (1080, 1920, 3)
@@ -122,13 +98,11 @@ def scale_image(image: ImageType, scale_factor: float) -> ImageType:
# (540, 960, 3)
```
- === "Pillow"
-
```python
from PIL import Image
import supervision as sv
- image = Image.open()
+ image = Image.open("source.png")
image.size
# (1920, 1080)
@@ -146,35 +120,31 @@ def scale_image(image: ImageType, scale_factor: float) -> ImageType:
return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR)
-@ensure_cv2_image_for_processing
+@ensure_cv2_image_for_standalone_function
def resize_image(
image: ImageType,
resolution_wh: tuple[int, int],
keep_aspect_ratio: bool = False,
) -> ImageType:
"""
- Resizes the given image to a specified resolution. Can maintain the original aspect
- ratio or resize directly to the desired dimensions.
+ Resize image to specified resolution. Can optionally maintain aspect ratio.
Args:
- image (ImageType): The image to be resized. `ImageType` is a flexible type,
- accepting either `numpy.ndarray` or `PIL.Image.Image`.
- resolution_wh (Tuple[int, int]): The target resolution as
- `(width, height)`.
- keep_aspect_ratio (bool): Flag to maintain the image's original
- aspect ratio. Defaults to `False`.
+ image (`numpy.ndarray` or `PIL.Image.Image`): The image to resize.
+ resolution_wh (`tuple[int, int]`): Target resolution as `(width, height)`.
+ keep_aspect_ratio (`bool`): Flag to maintain original aspect ratio.
+ Defaults to `False`.
Returns:
- (ImageType): The resized image. The type is determined by the input type and
- may be either a `numpy.ndarray` or `PIL.Image.Image`.
-
- === "OpenCV"
+ (`numpy.ndarray` or `PIL.Image.Image`): Resized image matching input
+ type.
+ Examples:
```python
import cv2
import supervision as sv
- image = cv2.imread()
+ image = cv2.imread("source.png")
image.shape
# (1080, 1920, 3)
@@ -185,13 +155,11 @@ def resize_image(
# (562, 1000, 3)
```
- === "Pillow"
-
```python
from PIL import Image
import supervision as sv
- image = Image.open()
+ image = Image.open("source.png")
image.size
# (1920, 1080)
@@ -219,54 +187,53 @@ def resize_image(
return cv2.resize(image, (width_new, height_new), interpolation=cv2.INTER_LINEAR)
-@ensure_cv2_image_for_processing
+@ensure_cv2_image_for_standalone_function
def letterbox_image(
image: ImageType,
resolution_wh: tuple[int, int],
color: tuple[int, int, int] | Color = Color.BLACK,
) -> ImageType:
"""
- Resizes and pads an image to a specified resolution with a given color, maintaining
- the original aspect ratio.
+ Resize image and pad with color to achieve desired resolution while
+ maintaining aspect ratio.
Args:
- image (ImageType): The image to be resized. `ImageType` is a flexible type,
- accepting either `numpy.ndarray` or `PIL.Image.Image`.
- resolution_wh (Tuple[int, int]): The target resolution as
- `(width, height)`.
- color (Union[Tuple[int, int, int], Color]): The color to pad with. If tuple
- provided it should be in BGR format.
+ image (`numpy.ndarray` or `PIL.Image.Image`): The image to resize and pad.
+ resolution_wh (`tuple[int, int]`): Target resolution as `(width, height)`.
+ color (`tuple[int, int, int]` or `Color`): Padding color. If tuple, should
+ be in BGR format. Defaults to `Color.BLACK`.
Returns:
- (ImageType): The resized image. The type is determined by the input type and
- may be either a `numpy.ndarray` or `PIL.Image.Image`.
-
- === "OpenCV"
+ (`numpy.ndarray` or `PIL.Image.Image`): Letterboxed image matching input
+ type.
+ Examples:
```python
import cv2
import supervision as sv
- image = cv2.imread()
+ image = cv2.imread("source.png")
image.shape
# (1080, 1920, 3)
- letterboxed_image = sv.letterbox_image(image=image, resolution_wh=(1000, 1000))
+ letterboxed_image = sv.letterbox_image(
+ image=image, resolution_wh=(1000, 1000)
+ )
letterboxed_image.shape
# (1000, 1000, 3)
```
- === "Pillow"
-
```python
from PIL import Image
import supervision as sv
- image = Image.open()
+ image = Image.open("source.png")
image.size
# (1920, 1080)
- letterboxed_image = sv.letterbox_image(image=image, resolution_wh=(1000, 1000))
+ letterboxed_image = sv.letterbox_image(
+ image=image, resolution_wh=(1000, 1000)
+ )
letterboxed_image.size
# (1000, 1000)
```
@@ -302,37 +269,59 @@ def letterbox_image(
return image_with_borders
+@deprecated(
+ "`overlay_image` function is deprecated and will be removed in "
+ "`supervision-0.32.0`. Use `draw_image` instead."
+)
def overlay_image(
image: npt.NDArray[np.uint8],
overlay: npt.NDArray[np.uint8],
anchor: tuple[int, int],
) -> npt.NDArray[np.uint8]:
"""
- Places an image onto a scene at a given anchor point, handling cases where
- the image's position is partially or completely outside the scene's bounds.
+ Overlay image onto scene at specified anchor point. Handles cases where
+ overlay position is partially or completely outside scene bounds.
Args:
- image (np.ndarray): The background scene onto which the image is placed.
- overlay (np.ndarray): The image to be placed onto the scene.
- anchor (Tuple[int, int]): The `(x, y)` coordinates in the scene where the
- top-left corner of the image will be placed.
+ image (`numpy.array`): Background scene with shape `(height, width, 3)`.
+ overlay (`numpy.array`): Image to overlay with shape
+ `(height, width, 3)` or `(height, width, 4)`.
+ anchor (`tuple[int, int]`): Coordinates `(x, y)` where top-left corner
+ of overlay will be placed.
Returns:
- (np.ndarray): The result image with overlay.
+ (`numpy.array`): Scene with overlay applied, shape `(height, width, 3)`.
Examples:
- ```python
+ ```
import cv2
import numpy as np
import supervision as sv
- image = cv2.imread()
+ image = cv2.imread("source.png")
overlay = np.zeros((400, 400, 3), dtype=np.uint8)
- result_image = sv.overlay_image(image=image, overlay=overlay, anchor=(200, 400))
+ overlay[:] = (0, 255, 0) # Green overlay
+
+ result_image = sv.overlay_image(
+ image=image, overlay=overlay, anchor=(200, 400)
+ )
+ cv2.imwrite("target.png", result_image)
```
- { align=center width="800" }
- """ # noqa E501 // docs
+ ```
+ import cv2
+ import numpy as np
+ import supervision as sv
+
+ image = cv2.imread("source.png")
+ overlay = cv2.imread("overlay.png", cv2.IMREAD_UNCHANGED)
+
+ result_image = sv.overlay_image(
+ image=image, overlay=overlay, anchor=(100, 100)
+ )
+ cv2.imwrite("target.png", result_image)
+ ```
+ """
scene_height, scene_width = image.shape[:2]
image_height, image_width = overlay.shape[:2]
anchor_x, anchor_y = anchor
@@ -371,6 +360,98 @@ def overlay_image(
return image
+@ensure_cv2_image_for_standalone_function
+def tint_image(
+ image: ImageType,
+ color: Color = Color.BLACK,
+ opacity: float = 0.5,
+) -> ImageType:
+ """
+ Tint image with solid color overlay at specified opacity.
+
+ Args:
+ image (`numpy.ndarray` or `PIL.Image.Image`): The image to tint.
+ color (`Color`): Overlay tint color. Defaults to `Color.BLACK`.
+ opacity (`float`): Blend ratio between overlay and image (0.0-1.0).
+ Defaults to `0.5`.
+
+ Returns:
+ (`numpy.ndarray` or `PIL.Image.Image`): Tinted image matching input
+ type.
+
+ Raises:
+ ValueError: If opacity is outside range [0.0, 1.0].
+
+ Examples:
+ ```python
+ import cv2
+ import supervision as sv
+
+ image = cv2.imread("source.png")
+ tinted_image = sv.tint_image(
+ image=image, color=sv.Color.BLACK, opacity=0.5
+ )
+ cv2.imwrite("target.png", tinted_image)
+ ```
+
+ ```python
+ from PIL import Image
+ import supervision as sv
+
+ image = Image.open("source.png")
+ tinted_image = sv.tint_image(
+ image=image, color=sv.Color.BLACK, opacity=0.5
+ )
+ tinted_image.save("target.png")
+ ```
+ """
+ if not 0.0 <= opacity <= 1.0:
+ raise ValueError("opacity must be between 0.0 and 1.0")
+
+ overlay = np.full_like(image, fill_value=color.as_bgr(), dtype=image.dtype)
+ cv2.addWeighted(
+ src1=overlay, alpha=opacity, src2=image, beta=1 - opacity, gamma=0, dst=image
+ )
+ return image
+
+
+@ensure_cv2_image_for_standalone_function
+def grayscale_image(image: ImageType) -> ImageType:
+ """
+ Convert image to 3-channel grayscale. Luminance channel is broadcast to
+ all three channels for compatibility with color-based drawing helpers.
+
+ Args:
+ image (`numpy.ndarray` or `PIL.Image.Image`): The image to convert to
+ grayscale.
+
+ Returns:
+ (`numpy.ndarray` or `PIL.Image.Image`): 3-channel grayscale image
+ matching input type.
+
+ Examples:
+ ```python
+ import cv2
+ import supervision as sv
+
+ image = cv2.imread("source.png")
+ grayscale_image = sv.grayscale_image(image=image)
+ cv2.imwrite("target.png", grayscale_image)
+ ```
+
+ ```python
+ from PIL import Image
+ import supervision as sv
+
+ image = Image.open("source.png")
+ grayscale_image = sv.grayscale_image(image=image)
+ grayscale_image.save("target.png")
+ ```
+ """
+ grayscaled = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
+ return cv2.cvtColor(grayscaled, cv2.COLOR_GRAY2BGR)
+
+
class ImageSink:
def __init__(
self,
@@ -379,27 +460,64 @@ class ImageSink:
image_name_pattern: str = "image_{:05d}.png",
):
"""
- Initialize a context manager for saving images.
+ Initialize context manager for saving images to directory.
Args:
- target_dir_path (str): The target directory where images will be saved.
- overwrite (bool): Whether to overwrite the existing directory.
- Defaults to False.
- image_name_pattern (str): The image file name pattern.
- Defaults to "image_{:05d}.png".
+ target_dir_path (`str`): Target directory path where images will be
+ saved.
+ overwrite (`bool`): Whether to overwrite existing directory.
+ Defaults to `False`.
+ image_name_pattern (`str`): File name pattern for saved images.
+ Defaults to `"image_{:05d}.png"`.
Examples:
```python
import supervision as sv
- frames_generator = sv.get_video_frames_generator(, stride=2)
+ frames_generator = sv.get_video_frames_generator(
+ "source.mp4", stride=2
+ )
- with sv.ImageSink(target_dir_path=) as sink:
+ with sv.ImageSink(target_dir_path="output_frames") as sink:
for image in frames_generator:
sink.save_image(image=image)
- ```
- """ # noqa E501 // docs
+ # Directory structure:
+ # output_frames/
+ # ├── image_00000.png
+ # ├── image_00001.png
+ # ├── image_00002.png
+ # └── image_00003.png
+ ```
+
+ ```python
+ import cv2
+ import supervision as sv
+
+ image = cv2.imread("source.png")
+ crop_boxes = [
+ ( 0, 0, 400, 400),
+ (400, 0, 800, 400),
+ ( 0, 400, 400, 800),
+ (400, 400, 800, 800)
+ ]
+
+ with sv.ImageSink(
+ target_dir_path="image_crops",
+ overwrite=True
+ ) as sink:
+ for i, xyxy in enumerate(crop_boxes):
+ crop = sv.crop_image(image=image, xyxy=xyxy)
+ sink.save_image(image=crop, image_name=f"crop_{i}.png")
+
+ # Directory structure:
+ # image_crops/
+ # ├── crop_0.png
+ # ├── crop_1.png
+ # ├── crop_2.png
+ # └── crop_3.png
+ ```
+ """
self.target_dir_path = target_dir_path
self.overwrite = overwrite
self.image_name_pattern = image_name_pattern
@@ -417,14 +535,14 @@ class ImageSink:
def save_image(self, image: np.ndarray, image_name: str | None = None):
"""
- Save a given image in the target directory.
+ Save image to target directory with optional custom filename.
Args:
- image (np.ndarray): The image to be saved. The image must be in BGR color
- format.
- image_name (Optional[str]): The name to use for the saved image.
- If not provided, a name will be
- generated using the `image_name_pattern`.
+ image (`numpy.array`): Image to save with shape `(height, width, 3)`
+ in BGR format.
+ image_name (`str` or `None`): Custom filename for saved image. If
+ `None`, generates name using `image_name_pattern`. Defaults to
+ `None`.
"""
if image_name is None:
image_name = self.image_name_pattern.format(self.image_count)
@@ -435,355 +553,3 @@ class ImageSink:
def __exit__(self, exc_type, exc_value, exc_traceback):
pass
-
-
-def create_tiles(
- images: list[ImageType],
- grid_size: tuple[int | None, int | None] | None = None,
- single_tile_size: tuple[int, int] | None = None,
- tile_scaling: Literal["min", "max", "avg"] = "avg",
- tile_padding_color: tuple[int, int, int] | Color = Color.from_hex("#D9D9D9"),
- tile_margin: int = 10,
- tile_margin_color: tuple[int, int, int] | Color = Color.from_hex("#BFBEBD"),
- return_type: Literal["auto", "cv2", "pillow"] = "auto",
- titles: list[str | None] | None = None,
- titles_anchors: Point | list[Point | None] | None = None,
- titles_color: tuple[int, int, int] | Color = Color.from_hex("#262523"),
- titles_scale: float | None = None,
- titles_thickness: int = 1,
- titles_padding: int = 10,
- titles_text_font: int = cv2.FONT_HERSHEY_SIMPLEX,
- titles_background_color: tuple[int, int, int] | Color = Color.from_hex("#D9D9D9"),
- default_title_placement: RelativePosition = "top",
-) -> ImageType:
- """
- Creates tiles mosaic from input images, automating grid placement and
- converting images to common resolution maintaining aspect ratio. It is
- also possible to render text titles on tiles, using optional set of
- parameters specifying text drawing (see parameters description).
-
- Automated grid placement will try to maintain square shape of grid
- (with size being the nearest integer square root of #images), up to two exceptions:
- * if there are up to 3 images - images will be displayed in single row
- * if square-grid placement causes last row to be empty - number of rows is trimmed
- until last row has at least one image
-
- Args:
- images (List[ImageType]): Images to create tiles. Elements can be either
- np.ndarray or PIL.Image, common representation will be agreed by the
- function.
- grid_size (Optional[Tuple[Optional[int], Optional[int]]]): 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 will be created with either n_rows or n_cols fixed,
- leaving the other dimension to be adjusted by the number of images
- single_tile_size (Optional[Tuple[int, int]]): sizeof a single tile element
- provided in (width, height) format. If not given - size of tile will be
- automatically calculated based on `tile_scaling` parameter.
- tile_scaling (Literal["min", "max", "avg"]): If `single_tile_size` is not
- given - parameter will be used to calculate tile size - using
- min / max / avg size of image provided in `images` list.
- tile_padding_color (Union[Tuple[int, int, int], sv.Color]): Color to be used in
- images letterbox procedure (while standardising tiles sizes) as a padding.
- If tuple provided - should be BGR.
- tile_margin (int): size of margin between tiles (in pixels)
- tile_margin_color (Union[Tuple[int, int, int], sv.Color]): Color of tile margin.
- If tuple provided - should be BGR.
- return_type (Literal["auto", "cv2", "pillow"]): Parameter dictates the format of
- return image. One may choose specific type ("cv2" or "pillow") to enforce
- conversion. "auto" mode takes a majority vote between types of elements in
- `images` list - resolving draws in favour of OpenCV format. "auto" can be
- safely used when all input images are of the same type.
- titles (Optional[List[Optional[str]]]): Optional titles to be added to tiles.
- Elements of that list may be empty - then specific tile (in order presented
- in `images` parameter) will not be filled with title. It is possible to
- provide list of titles shorter than `images` - then remaining titles will
- be assumed empty.
- titles_anchors (Optional[Union[Point, List[Optional[Point]]]]): Parameter to
- specify anchor points for titles. It is possible to specify anchor either
- globally or for specific tiles (following order of `images`).
- If not given (either globally, or for specific element of the list),
- it will be calculated automatically based on `default_title_placement`.
- titles_color (Union[Tuple[int, int, int], Color]): Color of titles text.
- If tuple provided - should be BGR.
- titles_scale (Optional[float]): Scale of titles. If not provided - value will
- be calculated using `calculate_optimal_text_scale(...)`.
- titles_thickness (int): Thickness of titles text.
- titles_padding (int): Size of titles padding.
- titles_text_font (int): Font to be used to render titles. Must be integer
- constant representing OpenCV font.
- (See docs: https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html)
- titles_background_color (Union[Tuple[int, int, int], Color]): Color of title
- text padding.
- default_title_placement (Literal["top", "bottom"]): Parameter specifies title
- anchor placement in case if explicit anchor is not provided.
-
- Returns:
- ImageType: Image with all input images located in tails grid. The output type is
- determined by `return_type` parameter.
-
- Raises:
- ValueError: In case when input images list is empty, provided `grid_size` is too
- small to fit all images, `tile_scaling` mode is invalid.
- """
- if len(images) == 0:
- raise ValueError("Could not create image tiles from empty list of images.")
- if return_type == "auto":
- return_type = _negotiate_tiles_format(images=images)
- tile_padding_color = unify_to_bgr(color=tile_padding_color)
- tile_margin_color = unify_to_bgr(color=tile_margin_color)
- images = images_to_cv2(images=images)
- if single_tile_size is None:
- single_tile_size = _aggregate_images_shape(images=images, mode=tile_scaling)
- resized_images = [
- letterbox_image(
- image=i, resolution_wh=single_tile_size, color=tile_padding_color
- )
- for i in images
- ]
- grid_size = _establish_grid_size(images=images, grid_size=grid_size)
- if len(images) > grid_size[0] * grid_size[1]:
- raise ValueError(
- f"Could not place {len(images)} in grid with size: {grid_size}."
- )
- if titles is not None:
- titles = fill(sequence=titles, desired_size=len(images), content=None)
- titles_anchors = (
- [titles_anchors]
- if not issubclass(type(titles_anchors), list)
- else titles_anchors
- )
- titles_anchors = fill(
- sequence=titles_anchors, desired_size=len(images), content=None
- )
- titles_color = unify_to_bgr(color=titles_color)
- titles_background_color = unify_to_bgr(color=titles_background_color)
- tiles = _generate_tiles(
- images=resized_images,
- grid_size=grid_size,
- single_tile_size=single_tile_size,
- tile_padding_color=tile_padding_color,
- tile_margin=tile_margin,
- tile_margin_color=tile_margin_color,
- titles=titles,
- titles_anchors=titles_anchors,
- titles_color=titles_color,
- titles_scale=titles_scale,
- titles_thickness=titles_thickness,
- titles_padding=titles_padding,
- titles_text_font=titles_text_font,
- titles_background_color=titles_background_color,
- default_title_placement=default_title_placement,
- )
- if return_type == "pillow":
- tiles = cv2_to_pillow(image=tiles)
- return tiles
-
-
-def _negotiate_tiles_format(images: list[ImageType]) -> Literal["cv2", "pillow"]:
- number_of_np_arrays = sum(issubclass(type(i), np.ndarray) for i in images)
- if number_of_np_arrays >= (len(images) // 2):
- return "cv2"
- return "pillow"
-
-
-def _calculate_aggregated_images_shape(
- images: list[np.ndarray], aggregator: Callable[[list[int]], float]
-) -> tuple[int, int]:
- height = round(aggregator([i.shape[0] for i in images]))
- width = round(aggregator([i.shape[1] for i in images]))
- return width, height
-
-
-SHAPE_AGGREGATION_FUN = {
- "min": partial(_calculate_aggregated_images_shape, aggregator=np.min),
- "max": partial(_calculate_aggregated_images_shape, aggregator=np.max),
- "avg": partial(_calculate_aggregated_images_shape, aggregator=np.average),
-}
-
-
-def _aggregate_images_shape(
- images: list[np.ndarray], mode: Literal["min", "max", "avg"]
-) -> tuple[int, int]:
- if mode not in SHAPE_AGGREGATION_FUN:
- raise ValueError(
- f"Could not aggregate images shape - provided unknown mode: {mode}. "
- f"Supported modes: {list(SHAPE_AGGREGATION_FUN.keys())}."
- )
- return SHAPE_AGGREGATION_FUN[mode](images)
-
-
-def _establish_grid_size(
- images: list[np.ndarray], grid_size: tuple[int | None, int | None] | None
-) -> tuple[int, int]:
- if grid_size is None or all(e is None for e in grid_size):
- return _negotiate_grid_size(images=images)
- if grid_size[0] is None:
- return math.ceil(len(images) / grid_size[1]), grid_size[1]
- if grid_size[1] is None:
- return grid_size[0], math.ceil(len(images) / grid_size[0])
- return grid_size
-
-
-def _negotiate_grid_size(images: list[np.ndarray]) -> tuple[int, int]:
- if len(images) <= MAX_COLUMNS_FOR_SINGLE_ROW_GRID:
- return 1, len(images)
- nearest_sqrt = math.ceil(np.sqrt(len(images)))
- proposed_columns = nearest_sqrt
- proposed_rows = nearest_sqrt
- while proposed_columns * (proposed_rows - 1) >= len(images):
- proposed_rows -= 1
- return proposed_rows, proposed_columns
-
-
-def _generate_tiles(
- images: list[np.ndarray],
- grid_size: tuple[int, int],
- single_tile_size: tuple[int, int],
- tile_padding_color: tuple[int, int, int],
- tile_margin: int,
- tile_margin_color: tuple[int, int, int],
- titles: list[str | None] | None,
- titles_anchors: list[Point | None],
- titles_color: tuple[int, int, int],
- titles_scale: float | None,
- titles_thickness: int,
- titles_padding: int,
- titles_text_font: int,
- titles_background_color: tuple[int, int, int],
- default_title_placement: RelativePosition,
-) -> np.ndarray:
- images = _draw_texts(
- images=images,
- titles=titles,
- titles_anchors=titles_anchors,
- titles_color=titles_color,
- titles_scale=titles_scale,
- titles_thickness=titles_thickness,
- titles_padding=titles_padding,
- titles_text_font=titles_text_font,
- titles_background_color=titles_background_color,
- default_title_placement=default_title_placement,
- )
- rows, columns = grid_size
- tiles_elements = list(create_batches(sequence=images, batch_size=columns))
- while len(tiles_elements[-1]) < columns:
- tiles_elements[-1].append(
- _generate_color_image(shape=single_tile_size, color=tile_padding_color)
- )
- while len(tiles_elements) < rows:
- tiles_elements.append(
- [_generate_color_image(shape=single_tile_size, color=tile_padding_color)]
- * columns
- )
- return _merge_tiles_elements(
- tiles_elements=tiles_elements,
- grid_size=grid_size,
- single_tile_size=single_tile_size,
- tile_margin=tile_margin,
- tile_margin_color=tile_margin_color,
- )
-
-
-def _draw_texts(
- images: list[np.ndarray],
- titles: list[str | None] | None,
- titles_anchors: list[Point | None],
- titles_color: tuple[int, int, int],
- titles_scale: float | None,
- titles_thickness: int,
- titles_padding: int,
- titles_text_font: int,
- titles_background_color: tuple[int, int, int],
- default_title_placement: RelativePosition,
-) -> list[np.ndarray]:
- if titles is None:
- return images
- titles_anchors = _prepare_default_titles_anchors(
- images=images,
- titles_anchors=titles_anchors,
- default_title_placement=default_title_placement,
- )
- if titles_scale is None:
- image_height, image_width = images[0].shape[:2]
- titles_scale = calculate_optimal_text_scale(
- resolution_wh=(image_width, image_height)
- )
- result = []
- for image, text, anchor in zip(images, titles, titles_anchors):
- if text is None:
- result.append(image)
- continue
- processed_image = draw_text(
- scene=image,
- text=text,
- text_anchor=anchor,
- text_color=Color.from_bgr_tuple(titles_color),
- text_scale=titles_scale,
- text_thickness=titles_thickness,
- text_padding=titles_padding,
- text_font=titles_text_font,
- background_color=Color.from_bgr_tuple(titles_background_color),
- )
- result.append(processed_image)
- return result
-
-
-def _prepare_default_titles_anchors(
- images: list[np.ndarray],
- titles_anchors: list[Point | None],
- default_title_placement: RelativePosition,
-) -> list[Point]:
- result = []
- for image, anchor in zip(images, titles_anchors):
- if anchor is not None:
- result.append(anchor)
- continue
- image_height, image_width = image.shape[:2]
- if default_title_placement == "top":
- default_anchor = Point(x=image_width / 2, y=image_height * 0.1)
- else:
- default_anchor = Point(x=image_width / 2, y=image_height * 0.9)
- result.append(default_anchor)
- return result
-
-
-def _merge_tiles_elements(
- tiles_elements: list[list[np.ndarray]],
- grid_size: tuple[int, int],
- single_tile_size: tuple[int, int],
- tile_margin: int,
- tile_margin_color: tuple[int, int, int],
-) -> np.ndarray:
- vertical_padding = (
- np.ones((single_tile_size[1], tile_margin, 3)) * tile_margin_color
- )
- merged_rows = [
- np.concatenate(
- list(
- itertools.chain.from_iterable(
- zip(row, [vertical_padding] * grid_size[1])
- )
- )[:-1],
- axis=1,
- )
- for row in tiles_elements
- ]
- row_width = merged_rows[0].shape[1]
- horizontal_padding = (
- np.ones((tile_margin, row_width, 3), dtype=np.uint8) * tile_margin_color
- )
- rows_with_paddings = []
- for row in merged_rows:
- rows_with_paddings.append(row)
- rows_with_paddings.append(horizontal_padding)
- return np.concatenate(
- rows_with_paddings[:-1],
- axis=0,
- ).astype(np.uint8)
-
-
-def _generate_color_image(
- shape: tuple[int, int], color: tuple[int, int, int]
-) -> np.ndarray:
- return np.ones((*shape[::-1], 3), dtype=np.uint8) * color
diff --git a/supervision/utils/notebook.py b/supervision/utils/notebook.py
index 9262f12b..3af09ebb 100644
--- a/supervision/utils/notebook.py
+++ b/supervision/utils/notebook.py
@@ -4,7 +4,7 @@ import cv2
import matplotlib.pyplot as plt
from PIL import Image
-from supervision.annotators.base import ImageType
+from supervision.draw.base import ImageType
from supervision.utils.conversion import pillow_to_cv2
diff --git a/test/utils/test_conversion.py b/test/utils/test_conversion.py
index 65cbd8a1..e9fabb0d 100644
--- a/test/utils/test_conversion.py
+++ b/test/utils/test_conversion.py
@@ -3,7 +3,7 @@ from PIL import Image, ImageChops
from supervision.utils.conversion import (
cv2_to_pillow,
- ensure_cv2_image_for_processing,
+ ensure_cv2_image_for_standalone_function,
images_to_cv2,
pillow_to_cv2,
)
@@ -16,7 +16,7 @@ def test_ensure_cv2_image_for_processing_when_pillow_image_submitted(
param_a_value = 3
param_b_value = "some"
- @ensure_cv2_image_for_processing
+ @ensure_cv2_image_for_standalone_function
def my_custom_processing_function(
image: np.ndarray,
param_a: int,
@@ -55,7 +55,7 @@ def test_ensure_cv2_image_for_processing_when_cv2_image_submitted(
param_a_value = 3
param_b_value = "some"
- @ensure_cv2_image_for_processing
+ @ensure_cv2_image_for_standalone_function
def my_custom_processing_function(
image: np.ndarray,
param_a: int,
diff --git a/test/utils/test_image.py b/test/utils/test_image.py
index 39640330..6ae9567b 100644
--- a/test/utils/test_image.py
+++ b/test/utils/test_image.py
@@ -1,9 +1,7 @@
import numpy as np
-import pytest
from PIL import Image, ImageChops
-from supervision import Color, Point
-from supervision.utils.image import create_tiles, letterbox_image, resize_image
+from supervision.utils.image import letterbox_image, resize_image
def test_resize_image_for_opencv_image() -> None:
@@ -96,147 +94,3 @@ def test_letterbox_image_for_pillow_image() -> None:
assert difference.getbbox() is None, (
"Expected padding to be added top and bottom with padding added top and bottom"
)
-
-
-def test_create_tiles_with_one_image(
- one_image: np.ndarray, single_image_tile: np.ndarray
-) -> None:
- # when
- result = create_tiles(images=[one_image], single_tile_size=(240, 240))
-
- # # then
- assert np.allclose(result, single_image_tile, atol=5.0)
-
-
-def test_create_tiles_with_one_image_and_enforced_grid(
- one_image: np.ndarray, single_image_tile_enforced_grid: np.ndarray
-) -> None:
- # when
- result = create_tiles(
- images=[one_image],
- grid_size=(None, 3),
- single_tile_size=(240, 240),
- )
-
- # then
- assert np.allclose(result, single_image_tile_enforced_grid, atol=5.0)
-
-
-def test_create_tiles_with_two_images(
- two_images: list[np.ndarray], two_images_tile: np.ndarray
-) -> None:
- # when
- result = create_tiles(images=two_images, single_tile_size=(240, 240))
-
- # then
- assert np.allclose(result, two_images_tile, atol=5.0)
-
-
-def test_create_tiles_with_three_images(
- three_images: list[np.ndarray], three_images_tile: np.ndarray
-) -> None:
- # when
- result = create_tiles(images=three_images, single_tile_size=(240, 240))
-
- # then
- assert np.allclose(result, three_images_tile, atol=5.0)
-
-
-def test_create_tiles_with_four_images(
- four_images: list[np.ndarray],
- four_images_tile: np.ndarray,
-) -> None:
- # when
- result = create_tiles(images=four_images, single_tile_size=(240, 240))
-
- # then
- assert np.allclose(result, four_images_tile, atol=5.0)
-
-
-def test_create_tiles_with_all_images(
- all_images: list[np.ndarray],
- all_images_tile: np.ndarray,
-) -> None:
- # when
- result = create_tiles(images=all_images, single_tile_size=(240, 240))
-
- # then
- assert np.allclose(result, all_images_tile, atol=5.0)
-
-
-def test_create_tiles_with_all_images_and_custom_grid(
- all_images: list[np.ndarray], all_images_tile_and_custom_grid: np.ndarray
-) -> None:
- # when
- result = create_tiles(
- images=all_images,
- grid_size=(3, 3),
- single_tile_size=(240, 240),
- )
-
- # then
- assert np.allclose(result, all_images_tile_and_custom_grid, atol=5.0)
-
-
-def test_create_tiles_with_all_images_and_custom_colors(
- all_images: list[np.ndarray], all_images_tile_and_custom_colors: np.ndarray
-) -> None:
- # when
- result = create_tiles(
- images=all_images,
- tile_margin_color=(127, 127, 127),
- tile_padding_color=(224, 224, 224),
- single_tile_size=(240, 240),
- )
-
- # then
- assert np.allclose(result, all_images_tile_and_custom_colors, atol=5.0)
-
-
-def test_create_tiles_with_all_images_and_titles(
- all_images: list[np.ndarray],
- all_images_tile_and_custom_colors_and_titles: np.ndarray,
-) -> None:
- # when
- result = create_tiles(
- images=all_images,
- titles=["Image 1", None, "Image 3", "Image 4"],
- single_tile_size=(240, 240),
- )
-
- # then
- assert np.allclose(result, all_images_tile_and_custom_colors_and_titles, atol=5.0)
-
-
-def test_create_tiles_with_all_images_and_titles_with_custom_configs(
- all_images: list[np.ndarray],
- all_images_tile_and_titles_with_custom_configs: np.ndarray,
-) -> None:
- # when
- result = create_tiles(
- images=all_images,
- titles=["Image 1", None, "Image 3", "Image 4"],
- single_tile_size=(240, 240),
- titles_anchors=[
- Point(x=200, y=300),
- Point(x=300, y=400),
- None,
- Point(x=300, y=400),
- ],
- titles_color=Color.RED,
- titles_scale=1.5,
- titles_thickness=3,
- titles_padding=20,
- titles_background_color=Color.BLACK,
- default_title_placement="bottom",
- )
-
- # then
- assert np.allclose(result, all_images_tile_and_titles_with_custom_configs, atol=5.0)
-
-
-def test_create_tiles_with_all_images_and_custom_grid_to_small_to_fit_images(
- all_images: list[np.ndarray],
-) -> None:
- with pytest.raises(ValueError):
- _ = create_tiles(images=all_images, grid_size=(2, 2))