reimplement `crop_image` to skip type casting when Pillow Image given
`get_image_resolution_wh` added
This commit is contained in:
parent
8d464aa1b5
commit
c8f5c741fd
|
|
@ -41,6 +41,12 @@ status: new
|
|||
|
||||
:::supervision.utils.image.grayscale_image
|
||||
|
||||
<div class="md-typeset">
|
||||
<h2><a href="#supervision.utils.image.get_image_resolution_wh">get_image_resolution_wh</a></h2>
|
||||
</div>
|
||||
|
||||
:::supervision.utils.image.get_image_resolution_wh
|
||||
|
||||
<div class="md-typeset">
|
||||
<h2><a href="#supervision.utils.image.ImageSink">ImageSink</a></h2>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ from supervision.utils.image import (
|
|||
resize_image,
|
||||
scale_image,
|
||||
tint_image,
|
||||
get_image_resolution_wh,
|
||||
)
|
||||
from supervision.utils.notebook import plot_image, plot_images_grid
|
||||
from supervision.utils.video import (
|
||||
|
|
@ -163,6 +164,7 @@ __all__ = [
|
|||
"DetectionsSmoother",
|
||||
"DotAnnotator",
|
||||
"EdgeAnnotator",
|
||||
"get_image_resolution_wh",
|
||||
"EllipseAnnotator",
|
||||
"FPSMonitor",
|
||||
"HaloAnnotator",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import shutil
|
|||
import cv2
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
from PIL import Image
|
||||
|
||||
from supervision.annotators.base import ImageType
|
||||
from supervision.draw.color import Color, unify_to_bgr
|
||||
|
|
@ -15,7 +16,6 @@ from supervision.utils.conversion import (
|
|||
from supervision.utils.internal import deprecated
|
||||
|
||||
|
||||
@ensure_cv2_image_for_standalone_function
|
||||
def crop_image(
|
||||
image: ImageType,
|
||||
xyxy: npt.NDArray[int] | list[int] | tuple[int, int, int, int],
|
||||
|
|
@ -65,9 +65,20 @@ def crop_image(
|
|||
""" # noqa E501 // docs
|
||||
if isinstance(xyxy, (list, tuple)):
|
||||
xyxy = np.array(xyxy)
|
||||
|
||||
xyxy = np.round(xyxy).astype(int)
|
||||
x_min, y_min, x_max, y_max = xyxy.flatten()
|
||||
return image[y_min:y_max, x_min:x_max]
|
||||
|
||||
if isinstance(image, np.ndarray):
|
||||
return image[y_min:y_max, x_min:x_max]
|
||||
|
||||
if isinstance(image, Image.Image):
|
||||
return image.crop((x_min, y_min, x_max, y_max))
|
||||
|
||||
raise TypeError(
|
||||
"`image` must be a numpy.ndarray or PIL.Image.Image. "
|
||||
f"Received {type(image)}"
|
||||
)
|
||||
|
||||
|
||||
@ensure_cv2_image_for_standalone_function
|
||||
|
|
@ -460,6 +471,61 @@ def grayscale_image(image: ImageType) -> ImageType:
|
|||
return cv2.cvtColor(grayscaled, cv2.COLOR_GRAY2BGR)
|
||||
|
||||
|
||||
def get_image_resolution_wh(image: ImageType) -> tuple[int, int]:
|
||||
"""
|
||||
Get image width and height as a tuple `(width, height)` for various image formats.
|
||||
|
||||
Supports both `numpy.ndarray` images (with shape `(H, W, ...)`) and
|
||||
`PIL.Image.Image` inputs.
|
||||
|
||||
Args:
|
||||
image (`numpy.ndarray` or `PIL.Image.Image`): Input image.
|
||||
|
||||
Returns:
|
||||
(`tuple[int, int]`): Image resolution as `(width, height)`.
|
||||
|
||||
Raises:
|
||||
ValueError: If a `numpy.ndarray` image has fewer than 2 dimensions.
|
||||
TypeError: If `image` is not a supported type (`numpy.ndarray` or
|
||||
`PIL.Image.Image`).
|
||||
|
||||
Examples:
|
||||
```python
|
||||
import cv2
|
||||
import supervision as sv
|
||||
|
||||
image = cv2.imread("example.png")
|
||||
sv.get_image_resolution_wh(image)
|
||||
# (1920, 1080)
|
||||
```
|
||||
|
||||
```python
|
||||
from PIL import Image
|
||||
import supervision as sv
|
||||
|
||||
image = Image.open("example.png")
|
||||
sv.get_image_resolution_wh(image)
|
||||
# (1920, 1080)
|
||||
```
|
||||
"""
|
||||
if isinstance(image, np.ndarray):
|
||||
if image.ndim < 2:
|
||||
raise ValueError(
|
||||
"NumPy image must have at least 2 dimensions (H, W, ...). "
|
||||
f"Received shape: {image.shape}"
|
||||
)
|
||||
height, width = image.shape[:2]
|
||||
return int(width), int(height)
|
||||
|
||||
if isinstance(image, Image.Image):
|
||||
width, height = image.size
|
||||
return int(width), int(height)
|
||||
|
||||
raise TypeError(
|
||||
"`image` must be a numpy.ndarray or PIL.Image.Image. "
|
||||
f"Received type: {type(image)}"
|
||||
)
|
||||
|
||||
class ImageSink:
|
||||
def __init__(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import numpy as np
|
||||
import pytest
|
||||
from PIL import Image, ImageChops
|
||||
|
||||
from supervision.utils.image import letterbox_image, resize_image
|
||||
from supervision.utils.image import letterbox_image, resize_image, crop_image, \
|
||||
get_image_resolution_wh
|
||||
|
||||
|
||||
def test_resize_image_for_opencv_image() -> None:
|
||||
|
|
@ -94,3 +96,67 @@ 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"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"image, xyxy, expected_size",
|
||||
[
|
||||
# NumPy RGB
|
||||
(
|
||||
np.zeros((4, 6, 3), dtype=np.uint8),
|
||||
(2, 1, 5, 3),
|
||||
(3, 2), # width = 5-2, height = 3-1
|
||||
),
|
||||
|
||||
# NumPy grayscale
|
||||
(
|
||||
np.zeros((5, 5), dtype=np.uint8),
|
||||
(1, 1, 4, 4),
|
||||
(3, 3),
|
||||
),
|
||||
|
||||
# Pillow RGB
|
||||
(
|
||||
Image.new("RGB", (6, 4), color=0),
|
||||
(2, 1, 5, 3),
|
||||
(3, 2),
|
||||
),
|
||||
|
||||
# Pillow grayscale
|
||||
(
|
||||
Image.new("L", (5, 5), color=0),
|
||||
(1, 1, 4, 4),
|
||||
(3, 3),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_crop_image(image, xyxy, expected_size):
|
||||
cropped = crop_image(image=image, xyxy=xyxy)
|
||||
if isinstance(image, np.ndarray):
|
||||
assert isinstance(cropped, np.ndarray)
|
||||
assert cropped.shape[1] == expected_size[0] # width
|
||||
assert cropped.shape[0] == expected_size[1] # height
|
||||
else:
|
||||
assert isinstance(cropped, Image.Image)
|
||||
assert cropped.size == expected_size
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"image, expected",
|
||||
[
|
||||
# NumPy RGB
|
||||
(np.zeros((4, 6, 3), dtype=np.uint8), (6, 4)),
|
||||
|
||||
# NumPy grayscale
|
||||
(np.zeros((10, 20), dtype=np.uint8), (20, 10)),
|
||||
|
||||
# Pillow RGB
|
||||
(Image.new("RGB", (6, 4), color=0), (6, 4)),
|
||||
|
||||
# Pillow grayscale
|
||||
(Image.new("L", (20, 10), color=0), (20, 10)),
|
||||
],
|
||||
)
|
||||
def test_get_image_resolution_wh(image, expected):
|
||||
resolution = get_image_resolution_wh(image)
|
||||
assert resolution == expected
|
||||
Loading…
Reference in New Issue