'''Tools for editing and manipulating images, and image colors, sizes, and representations'''
__author__ = 'Timotej Bernat'
__email__ = 'timotej.bernat@colorado.edu'
from typing import Union
import numpy as np
from io import BytesIO
import PIL
from PIL.Image import Image
# CONVERSION FUNCTIONS
[docs]
def img_from_bytes(img_bytes : bytearray) -> Image:
'''Load an image from a bytestream'''
return PIL.Image.open(BytesIO(img_bytes))
[docs]
def img_to_array(image : Image, encoding : str='RGB') -> np.ndarray:
'''Convert an image to a numpy array'''
return np.asarray(image.convert(encoding))
# BOUNDING BOXES AND BACKGROUND REMOVAL
[docs]
def get_axis_bounds(image : Image, bg_color : Union[int, tuple[int]]) -> tuple[tuple[int, int], tuple[int, int]]:
'''Takes an image and returns a pair of tuples containing the min and max non-background
coordinates along the x- and y-axis (respectively) for some chosen background color'''
y, x, ch = np.where((np.asarray(image) != bg_color)) # TODO : generalize this to work for greyscale (too many dimensions)
x_bounds = (x.min(), x.max())
y_bounds = (y.min(), y.max())
return x_bounds, y_bounds
[docs]
def get_tight_bbox(image : Image, bg_color : Union[int, tuple[int]]) -> tuple[int, ...]:
'''Takes an image and returns a 4-tuple of the coordinates of the tight bounding box based on a choice of background color'''
(x_min, x_max), (y_min, y_max) = get_axis_bounds(image, bg_color=bg_color)
tight_bbox = (x_min, y_min, x_max, y_max)
return tight_bbox
[docs]
def crop_borders(image : Image, bg_color : Union[int, tuple[int]]) -> Image:
'''Takes an image and returns an image with all extraneous borders of a particular background color cropped off'''
return image.crop(get_tight_bbox(image, bg_color=bg_color))