"""
Overview:
A utility for squeezing a specified region of an image.
"""
from typing import Optional
import numpy as np
from scipy import ndimage
from ..data import ImageTyping, load_image
[docs]def squeeze(image: ImageTyping, mask: np.ndarray):
"""
Extracts the corresponding region from the original image based on the provided mask (HxW format)
and crops the image to fit the mask tightly.
:param image: The input image.
:type image: ImageTyping
:param mask: The mask representing the region of interest. It should be a NumPy array with shape ``(H, W)``.
:type mask: np.ndarray
:raises ValueError: If the shape of the image and mask do not match.
:return: The cropped image that fits the mask tightly.
:rtype: Image.Image
Examples::
>>> from PIL import Image
>>> from imgutils.operate import squeeze
>>>
>>> origin = Image.open('jerry_with_space.png')
>>> mask = ... # set your custom mask, format: bool[H, W]
>>>
>>> squeezed = squeeze(origin, mask)
This is the result:
.. image:: squeeze.plot.py.svg
:align: center
"""
image = load_image(image, force_background=None)
if (image.height, image.width) != mask.shape:
raise ValueError(f'Image shape not matched, '
f'{mask.shape!r} in mask but {(image.height, image.width)!r} in image.')
mask = mask.astype(bool).astype(int)
x_idx, = np.where(mask.sum(axis=1) > 0)
x_min, x_max = x_idx.min(), x_idx.max()
y_idx, = np.where(mask.sum(axis=0) > 0)
y_min, y_max = y_idx.min(), y_idx.max()
return image.crop((y_min, x_min, y_max, x_max))
def _get_mask_of_transparency(image: ImageTyping, threshold: float = 0.7, median_filter: Optional[int] = 5):
image = load_image(image, mode='RGBA', force_background=None)
mask = ((np.array(image)[:, :, 3].astype(np.float32) / 255.0) > threshold).astype(int)
if median_filter is not None:
mask = ndimage.median_filter(mask, size=median_filter)
return mask.astype(bool)
[docs]def squeeze_with_transparency(image: ImageTyping, threshold: float = 0.7, median_filter: Optional[int] = 5):
"""
Automatically crops the image based on the transparency of each pixel using the :func:`squeeze` function.
:param image: The input image.
:type image: ImageTyping
:param threshold: The threshold value for pixel transparency. Pixels with transparency above this threshold
will be considered as part of the region of interest. Default is ``0.7``.
:type threshold: float
:param median_filter: The size of the median filter kernel to apply to the transparency mask.
A larger value helps reduce noise in the mask. Set to None or 0 to disable median filtering. Default is ``5``.
:type median_filter: Optional[int]
:return: The cropped image based on the transparency of each pixel.
:rtype: Image.Image
Examples::
>>> from PIL import Image
>>> from imgutils.operate import squeeze_with_transparency
>>>
>>> origin = Image.open('jerry_with_space.png')
>>>
>>> squeezed = squeeze_with_transparency(origin)
This is the result:
.. image:: squeeze_with_transparency.plot.py.svg
:align: center
"""
return squeeze(image, _get_mask_of_transparency(image, threshold, median_filter))