Loading imgutils/data/__init__.py +2 −1 Original line number Diff line number Diff line Loading @@ -8,4 +8,5 @@ from .decode import * from .encode import * from .image import * from .layer import * from .pad import * from .url import * imgutils/data/pad.py 0 → 100644 +65 −0 Original line number Diff line number Diff line from typing import Union, Tuple, Literal from PIL import ImageColor, Image from .image import ImageTyping, load_image __all__ = [ 'pad_image_to_size', ] def _parse_size(size): if isinstance(size, int): return size, size elif isinstance(size, (list, tuple)) and len(size) == 2: return int(size[0]), int(size[1]) else: raise TypeError("Size must be int or tuple of two ints") def _parse_color_to_rgba(color): if isinstance(color, str): rgba = ImageColor.getrgb(color) + (255,) if len(rgba) < 4: rgba = rgba[:3] + (255,) elif isinstance(color, int): rgba = (color, color, color, 255) elif isinstance(color, tuple): rgba = color + (255,) * (4 - len(color)) else: raise TypeError(f"Invalid color type: {type(color)}") return rgba def _parse_color_to_mode(color, mode: Literal['RGB', 'RGBA', 'P', 'L', 'LA']): rgba = _parse_color_to_rgba(color) if mode == 'L' or mode == 'P': return int(0.299 * rgba[0] + 0.587 * rgba[1] + 0.114 * rgba[2]) elif mode == "LA": gray = int(0.299 * rgba[0] + 0.587 * rgba[1] + 0.114 * rgba[2]) return gray, rgba[3] elif mode == "RGB": return rgba[:3] elif mode == "RGBA": return rgba else: raise ValueError(f"Unsupported mode: {mode}") def pad_image_to_size(pic: ImageTyping, size: Union[int, Tuple[int, int]], background_color: Union[str, int, Tuple[int, int, int], Tuple[int, int, int, int]] = 'white', interpolation: int = Image.BILINEAR): pic = load_image(pic, force_background=None, mode=None) target_w, target_h = _parse_size(size) original_w, original_h = pic.size ratio = min(target_w / original_w, target_h / original_h) new_w, new_h = round(original_w * ratio), round(original_h * ratio) resized = pic.resize((new_w, new_h), interpolation) bg_color = _parse_color_to_mode(background_color, pic.mode) canvas = Image.new(pic.mode, (target_w, target_h), bg_color) canvas.paste(resized, ((target_w - new_w) // 2, (target_h - new_h) // 2)) return canvas imgutils/preprocess/pillow.py +52 −1 Original line number Diff line number Diff line Loading @@ -19,7 +19,7 @@ import numpy as np from PIL import Image from .base import NotParseTarget from ..data import load_image from ..data import load_image, pad_image_to_size # noinspection PyUnresolvedReferences _INT_TO_PILLOW = { Loading Loading @@ -800,6 +800,57 @@ def _parse_rescale(obj): } class PillowPadToSize: def __init__(self, size: Union[Tuple[int, int], int], background_color: Union[str, int, Tuple[int, int, int], Tuple[int, int, int, int]] = 'white', interpolation: int = Image.BILINEAR): from ..data.pad import _parse_size, _parse_color_to_rgba self.size = _parse_size(size) self.background_color = background_color self.interpolation = interpolation _parse_color_to_rgba(self.background_color) def __call__(self, pic): if not isinstance(pic, Image.Image): raise TypeError('pic should be PIL Image. Got {}'.format(type(pic))) return pad_image_to_size( pic=pic, size=self.size, background_color=self.background_color, interpolation=self.interpolation, ) def __repr__(self) -> str: interpolate_str = _PILLOW_TO_STR[self.interpolation] detail = f"(size={self.size}, interpolation={interpolate_str}, background_color={self.background_color})" return f"{self.__class__.__name__}{detail}" @register_pillow_transform('pad_to_size') def _create_pad_to_size(size: Union[Tuple[int, int], int], background_color: Union[str, int, Tuple[int, int, int], Tuple[int, int, int, int]] = 'white', interpolation: str = 'bilinear'): return PillowPadToSize( size=size, background_color=background_color, interpolation=_STR_TO_PILLOW[interpolation], ) @register_pillow_parse('pad_to_size') def _parse_pad_to_size(obj): if not isinstance(obj, PillowPadToSize): raise NotParseTarget obj: PillowPadToSize return { 'size': list(obj.size), 'background_color': obj.background_color, 'interpolation': _PILLOW_TO_STR[obj.interpolation], } class PillowCompose: """ Composes several transforms together into a single transform. Loading imgutils/preprocess/torchvision.py +88 −1 Original line number Diff line number Diff line Loading @@ -9,14 +9,20 @@ and normalization. It provides a flexible framework for extending with additiona import copy from functools import wraps from typing import Union from typing import Union, Tuple from PIL import Image from .base import NotParseTarget from ..data import pad_image_to_size try: import torchvision import torch except (ImportError, ModuleNotFoundError): _HAS_TORCHVISION = False torchvision = None torch = None else: _HAS_TORCHVISION = True Loading Loading @@ -71,6 +77,22 @@ def _get_interpolation_mode(value): raise TypeError(f'Unknown type of interpolation mode - {value!r}.') def _get_int_from_interpolation_mode(value): from torchvision.transforms import InterpolationMode if not isinstance(value, InterpolationMode): raise TypeError(f'Unknown type of interpolation mode, cannot be transformed to int - {value!r}') _INTERMODE_TO_INT = { InterpolationMode.NEAREST: 0, InterpolationMode.BILINEAR: 2, InterpolationMode.BICUBIC: 3, InterpolationMode.BOX: 4, InterpolationMode.HAMMING: 5, InterpolationMode.LANCZOS: 1, } return _INTERMODE_TO_INT[value] _TRANS_CREATORS = {} Loading Loading @@ -327,6 +349,71 @@ def _parse_normalize(obj): } if _HAS_TORCHVISION: from torchvision.transforms import InterpolationMode class PadToSize(torch.nn.Module): """ Resize and center-pad PIL image to target size with background color. TorchVision-compatible transform that can be composed. """ def __init__(self, size: Union[Tuple[int, int], int], background_color: Union[str, int, Tuple[int, int, int], Tuple[int, int, int, int]] = 'white', interpolation: InterpolationMode = InterpolationMode.BILINEAR): super().__init__() from ..data.pad import _parse_size, _parse_color_to_rgba self.size: Tuple[int, int] = _parse_size(size) self.background_color = background_color self.interpolation: InterpolationMode = interpolation _parse_color_to_rgba(self.background_color) def forward(self, pic): if not isinstance(pic, Image.Image): raise TypeError('pic should be PIL Image. Got {}'.format(type(pic))) return pad_image_to_size( pic=pic, size=self.size, background_color=self.background_color, interpolation=_get_int_from_interpolation_mode(self.interpolation), ) def __repr__(self) -> str: detail = f"(size={self.size}, interpolation={self.interpolation.value}, background_color={self.background_color})" return f"{self.__class__.__name__}{detail}" else: PadToSize = None @_register_transform('pad_to_size', safe=False) def _create_pad_to_size(size: Union[Tuple[int, int], int], background_color: Union[str, int, Tuple[int, int, int], Tuple[int, int, int, int]] = 'white', interpolation='bilinear'): assert PadToSize is not None return PadToSize( size=size, background_color=background_color, interpolation=_get_interpolation_mode(interpolation), ) @_register_parse('pad_to_size', safe=False) def _parse_pad_to_size(obj): assert PadToSize is not None if not isinstance(obj, PadToSize): raise NotParseTarget obj: PadToSize return { 'size': list(obj.size), 'background_color': obj.background_color, 'interpolation': obj.interpolation.value, } def create_torchvision_transforms(tvalue: Union[list, dict]): """ Create torchvision transforms from config. Loading Loading
imgutils/data/__init__.py +2 −1 Original line number Diff line number Diff line Loading @@ -8,4 +8,5 @@ from .decode import * from .encode import * from .image import * from .layer import * from .pad import * from .url import *
imgutils/data/pad.py 0 → 100644 +65 −0 Original line number Diff line number Diff line from typing import Union, Tuple, Literal from PIL import ImageColor, Image from .image import ImageTyping, load_image __all__ = [ 'pad_image_to_size', ] def _parse_size(size): if isinstance(size, int): return size, size elif isinstance(size, (list, tuple)) and len(size) == 2: return int(size[0]), int(size[1]) else: raise TypeError("Size must be int or tuple of two ints") def _parse_color_to_rgba(color): if isinstance(color, str): rgba = ImageColor.getrgb(color) + (255,) if len(rgba) < 4: rgba = rgba[:3] + (255,) elif isinstance(color, int): rgba = (color, color, color, 255) elif isinstance(color, tuple): rgba = color + (255,) * (4 - len(color)) else: raise TypeError(f"Invalid color type: {type(color)}") return rgba def _parse_color_to_mode(color, mode: Literal['RGB', 'RGBA', 'P', 'L', 'LA']): rgba = _parse_color_to_rgba(color) if mode == 'L' or mode == 'P': return int(0.299 * rgba[0] + 0.587 * rgba[1] + 0.114 * rgba[2]) elif mode == "LA": gray = int(0.299 * rgba[0] + 0.587 * rgba[1] + 0.114 * rgba[2]) return gray, rgba[3] elif mode == "RGB": return rgba[:3] elif mode == "RGBA": return rgba else: raise ValueError(f"Unsupported mode: {mode}") def pad_image_to_size(pic: ImageTyping, size: Union[int, Tuple[int, int]], background_color: Union[str, int, Tuple[int, int, int], Tuple[int, int, int, int]] = 'white', interpolation: int = Image.BILINEAR): pic = load_image(pic, force_background=None, mode=None) target_w, target_h = _parse_size(size) original_w, original_h = pic.size ratio = min(target_w / original_w, target_h / original_h) new_w, new_h = round(original_w * ratio), round(original_h * ratio) resized = pic.resize((new_w, new_h), interpolation) bg_color = _parse_color_to_mode(background_color, pic.mode) canvas = Image.new(pic.mode, (target_w, target_h), bg_color) canvas.paste(resized, ((target_w - new_w) // 2, (target_h - new_h) // 2)) return canvas
imgutils/preprocess/pillow.py +52 −1 Original line number Diff line number Diff line Loading @@ -19,7 +19,7 @@ import numpy as np from PIL import Image from .base import NotParseTarget from ..data import load_image from ..data import load_image, pad_image_to_size # noinspection PyUnresolvedReferences _INT_TO_PILLOW = { Loading Loading @@ -800,6 +800,57 @@ def _parse_rescale(obj): } class PillowPadToSize: def __init__(self, size: Union[Tuple[int, int], int], background_color: Union[str, int, Tuple[int, int, int], Tuple[int, int, int, int]] = 'white', interpolation: int = Image.BILINEAR): from ..data.pad import _parse_size, _parse_color_to_rgba self.size = _parse_size(size) self.background_color = background_color self.interpolation = interpolation _parse_color_to_rgba(self.background_color) def __call__(self, pic): if not isinstance(pic, Image.Image): raise TypeError('pic should be PIL Image. Got {}'.format(type(pic))) return pad_image_to_size( pic=pic, size=self.size, background_color=self.background_color, interpolation=self.interpolation, ) def __repr__(self) -> str: interpolate_str = _PILLOW_TO_STR[self.interpolation] detail = f"(size={self.size}, interpolation={interpolate_str}, background_color={self.background_color})" return f"{self.__class__.__name__}{detail}" @register_pillow_transform('pad_to_size') def _create_pad_to_size(size: Union[Tuple[int, int], int], background_color: Union[str, int, Tuple[int, int, int], Tuple[int, int, int, int]] = 'white', interpolation: str = 'bilinear'): return PillowPadToSize( size=size, background_color=background_color, interpolation=_STR_TO_PILLOW[interpolation], ) @register_pillow_parse('pad_to_size') def _parse_pad_to_size(obj): if not isinstance(obj, PillowPadToSize): raise NotParseTarget obj: PillowPadToSize return { 'size': list(obj.size), 'background_color': obj.background_color, 'interpolation': _PILLOW_TO_STR[obj.interpolation], } class PillowCompose: """ Composes several transforms together into a single transform. Loading
imgutils/preprocess/torchvision.py +88 −1 Original line number Diff line number Diff line Loading @@ -9,14 +9,20 @@ and normalization. It provides a flexible framework for extending with additiona import copy from functools import wraps from typing import Union from typing import Union, Tuple from PIL import Image from .base import NotParseTarget from ..data import pad_image_to_size try: import torchvision import torch except (ImportError, ModuleNotFoundError): _HAS_TORCHVISION = False torchvision = None torch = None else: _HAS_TORCHVISION = True Loading Loading @@ -71,6 +77,22 @@ def _get_interpolation_mode(value): raise TypeError(f'Unknown type of interpolation mode - {value!r}.') def _get_int_from_interpolation_mode(value): from torchvision.transforms import InterpolationMode if not isinstance(value, InterpolationMode): raise TypeError(f'Unknown type of interpolation mode, cannot be transformed to int - {value!r}') _INTERMODE_TO_INT = { InterpolationMode.NEAREST: 0, InterpolationMode.BILINEAR: 2, InterpolationMode.BICUBIC: 3, InterpolationMode.BOX: 4, InterpolationMode.HAMMING: 5, InterpolationMode.LANCZOS: 1, } return _INTERMODE_TO_INT[value] _TRANS_CREATORS = {} Loading Loading @@ -327,6 +349,71 @@ def _parse_normalize(obj): } if _HAS_TORCHVISION: from torchvision.transforms import InterpolationMode class PadToSize(torch.nn.Module): """ Resize and center-pad PIL image to target size with background color. TorchVision-compatible transform that can be composed. """ def __init__(self, size: Union[Tuple[int, int], int], background_color: Union[str, int, Tuple[int, int, int], Tuple[int, int, int, int]] = 'white', interpolation: InterpolationMode = InterpolationMode.BILINEAR): super().__init__() from ..data.pad import _parse_size, _parse_color_to_rgba self.size: Tuple[int, int] = _parse_size(size) self.background_color = background_color self.interpolation: InterpolationMode = interpolation _parse_color_to_rgba(self.background_color) def forward(self, pic): if not isinstance(pic, Image.Image): raise TypeError('pic should be PIL Image. Got {}'.format(type(pic))) return pad_image_to_size( pic=pic, size=self.size, background_color=self.background_color, interpolation=_get_int_from_interpolation_mode(self.interpolation), ) def __repr__(self) -> str: detail = f"(size={self.size}, interpolation={self.interpolation.value}, background_color={self.background_color})" return f"{self.__class__.__name__}{detail}" else: PadToSize = None @_register_transform('pad_to_size', safe=False) def _create_pad_to_size(size: Union[Tuple[int, int], int], background_color: Union[str, int, Tuple[int, int, int], Tuple[int, int, int, int]] = 'white', interpolation='bilinear'): assert PadToSize is not None return PadToSize( size=size, background_color=background_color, interpolation=_get_interpolation_mode(interpolation), ) @_register_parse('pad_to_size', safe=False) def _parse_pad_to_size(obj): assert PadToSize is not None if not isinstance(obj, PadToSize): raise NotParseTarget obj: PadToSize return { 'size': list(obj.size), 'background_color': obj.background_color, 'interpolation': obj.interpolation.value, } def create_torchvision_transforms(tvalue: Union[list, dict]): """ Create torchvision transforms from config. Loading