Skip to content

image_function

data.transform.image_function

ImageSizeType module-attribute

ImageSizeType = Sequence[int]

RESAMPLE_PIL module-attribute

RESAMPLE_PIL = {'nearest': NEAREST, 'bilinear': BILINEAR, 'bicubic': BICUBIC}

RESAMPLE_CV2 module-attribute

RESAMPLE_CV2 = {'nearest': INTER_NEAREST, 'bilinear': INTER_LINEAR, 'bicubic': INTER_CUBIC}

RESAMPLE_TORCH module-attribute

RESAMPLE_TORCH = {'nearest': 'nearest-exact', 'bilinear': 'bilinear', 'bicubic': 'bicubic'}

IMAGE_MODES module-attribute

IMAGE_MODES = ('RGB', 'L')

read_image_size

read_image_size(image: Union[Image, Tensor, ndarray]) -> ImageSizeType

image size: (W, H)

Source code in SaigeToolkit/data/transform/image_function.py
def read_image_size(image: Union[Image.Image, torch.Tensor, np.ndarray]) -> ImageSizeType:
    """image size: (W, H)"""
    if isinstance(image, Image.Image):
        return image.size
    elif isinstance(image, torch.Tensor) and image.ndim in (2, 3, 4):  # HW, CHW, BCHW
        return (image.shape[-1], image.shape[-2])
    elif isinstance(image, np.ndarray) and image.ndim in (2, 3):  # HW, HWC
        return (image.shape[1], image.shape[0])
    else:
        raise NotImplementedError

resize_image

resize_image(image: Union[Image, Tensor, ndarray], target_size: ImageSizeType, resampling: str = 'bilinear', use_cv2_for_numpy: bool = True) -> Union[Image, Tensor, ndarray]

이미지를 resize합니다.

Parameters:

  • image (Union[Image, Tensor, ndarray]) –

    image data

  • target_size (ImageSizeType) –

    [W, H]

  • resampling (str, default: 'bilinear' ) –

    resampling method. Defaults to "bilinear".

  • use_cv2_for_numpy (bool, default: True ) –

    use cv2 instead of PIL for faster numpy array image resizing. Defaults to True.

Returns:

  • Union[Image, Tensor, ndarray]

    Union[Image.Image, torch.Tensor, np.ndarray]: resized image

Source code in SaigeToolkit/data/transform/image_function.py
def resize_image(
    image: Union[Image.Image, torch.Tensor, np.ndarray],
    target_size: ImageSizeType,
    resampling: str = "bilinear",
    use_cv2_for_numpy: bool = True,
) -> Union[Image.Image, torch.Tensor, np.ndarray]:
    """이미지를 resize합니다.

    Args:
        image (Union[Image.Image, torch.Tensor, np.ndarray]): image data
        target_size (ImageSizeType): [W, H]
        resampling (str): resampling method. Defaults to "bilinear".
        use_cv2_for_numpy (bool): use cv2 instead of PIL for faster numpy array image resizing. Defaults to True.

    Returns:
        Union[Image.Image, torch.Tensor, np.ndarray]: resized image
    """
    if isinstance(image, Image.Image):
        image = image.resize(target_size, RESAMPLE_PIL[resampling])
    elif isinstance(image, torch.Tensor):  # CHW, BCHW
        is_three_dim = image.ndim == 3
        if is_three_dim:  # CHW -> 1CHW
            image = image.unsqueeze(0)
        image = torch.nn.functional.interpolate(
            image,
            size=(target_size[1], target_size[0]),
            mode=RESAMPLE_TORCH[resampling],
            antialias=None if resampling == "nearest" else True,
        )
        if is_three_dim:
            image = image.squeeze(0)
    elif isinstance(image, np.ndarray):  # HWC, HW
        if use_cv2_for_numpy:
            # NOTE: cv2 resize is different from PIL/torch (inaccurate but fast)
            image = cv2.resize(image, dsize=target_size, interpolation=RESAMPLE_CV2[resampling])
        else:
            is_single_channel = image.ndim == 3 and image.shape[-1] == 1
            if is_single_channel:
                image = image[:, :, 0]
            image = Image.fromarray(image)
            image = image.resize(target_size, RESAMPLE_PIL[resampling])
            image = np.array(image)
            if is_single_channel:
                image = image[:, :, np.newaxis]
    return image

resize_mask

resize_mask(mask: Union[Image, Tensor, ndarray], target_size: ImageSizeType) -> Union[Image, Tensor, ndarray]

mask 이미지를 resize합니다. (NEAREST resampling)

Parameters:

  • mask (Union[Image, Tensor, ndarray]) –

    mask image

  • target_size (ImageSizeType) –

    [W, H]

Returns:

  • Union[Image, Tensor, ndarray]

    Union[Image.Image, torch.Tensor, np.ndarray]: resized mask image

Source code in SaigeToolkit/data/transform/image_function.py
def resize_mask(
    mask: Union[Image.Image, torch.Tensor, np.ndarray],
    target_size: ImageSizeType,
) -> Union[Image.Image, torch.Tensor, np.ndarray]:
    """mask 이미지를 resize합니다. (NEAREST resampling)

    Args:
        mask (Union[Image.Image, torch.Tensor, np.ndarray]): mask image
        target_size (ImageSizeType): [W, H]

    Returns:
        Union[Image.Image, torch.Tensor, np.ndarray]: resized mask image
    """
    if isinstance(mask, Image.Image):
        mask = mask.resize(target_size, Image.Resampling.NEAREST)
    elif isinstance(mask, torch.Tensor):  # HW, BHW
        is_two_dim = mask.ndim == 2
        if is_two_dim:  # HW -> 1HW
            mask = mask.unsqueeze(0)
        mask = torch.nn.functional.interpolate(
            mask.unsqueeze(1),  # BHW -> B1HW
            size=(target_size[1], target_size[0]),
            mode="nearest-exact",
        ).squeeze(1)
        if is_two_dim:
            mask = mask.squeeze(0)
    elif isinstance(mask, np.ndarray):  # HW
        mask = Image.fromarray(mask)
        mask = mask.resize(target_size, Image.Resampling.NEAREST)
        mask = np.array(mask)
    return mask

resize_array

resize_array(array: Union[Tensor, ndarray], target_size: ImageSizeType, resampling: str = 'nearest') -> Union[Tensor, ndarray]

[HW, BHW]의 array를 resize합니다.

Parameters:

  • array (Union[Tensor, ndarray]) –

    array data [HW, BHW]

  • target_size (ImageSizeType) –

    [W, H]

  • resampling (str, default: 'nearest' ) –

    resampling method. Defaults to "nearest".

Returns:

  • Union[Tensor, ndarray]

    Union[torch.Tensor, np.ndarray]: resized array

Source code in SaigeToolkit/data/transform/image_function.py
def resize_array(
    array: Union[torch.Tensor, np.ndarray],
    target_size: ImageSizeType,
    resampling: str = "nearest",
) -> Union[torch.Tensor, np.ndarray]:
    """[HW, BHW]의 array를 resize합니다.

    Args:
        array (Union[torch.Tensor, np.ndarray]): array data [HW, BHW]
        target_size (ImageSizeType): [W, H]
        resampling (str, optional): resampling method. Defaults to "nearest".

    Returns:
        Union[torch.Tensor, np.ndarray]: resized array
    """
    is_np_array = isinstance(array, np.ndarray)
    if is_np_array:  # np.ndarray -> torch.Tensor
        array = torch.tensor(array)

    is_two_dim = array.ndim == 2
    if is_two_dim:  # HW -> 1HW
        array = array.unsqueeze(0)

    array = torch.nn.functional.interpolate(
        array.unsqueeze(1),  # BHW -> B1HW
        size=(target_size[1], target_size[0]),
        mode=RESAMPLE_TORCH[resampling],
        antialias=None if resampling == "nearest" else True,
    ).squeeze(1)

    if is_two_dim:  # 1HW -> HW
        array = array.squeeze(0)

    if is_np_array:  # torch.Tensor -> np.ndarray
        array = array.numpy()

    return array

crop

crop(image: Union[Image, Tensor, ndarray], coordinates: Union[List[int], Tuple[int, int, int, int]]) -> Union[Image, Tensor, ndarray]

이미지의 coordinates 좌표영역을 크롭합니다. coordinates가 이미지를 벗어나는 경우 zero padding 합니다.

Parameters:

  • image (Union[Image, Tensor, ndarray]) –

    image

  • coordinates (Union[List[int], Tuple[int, int, int, int]]) –

    [left, top, right, bottom]

Returns:

  • Union[Image, Tensor, ndarray]

    Union[Image.Image, torch.Tensor, np.ndarray]: cropped image

Source code in SaigeToolkit/data/transform/image_function.py
def crop(
    image: Union[Image.Image, torch.Tensor, np.ndarray],
    coordinates: Union[List[int], Tuple[int, int, int, int]],
) -> Union[Image.Image, torch.Tensor, np.ndarray]:
    """이미지의 coordinates 좌표영역을 크롭합니다. coordinates가 이미지를 벗어나는 경우 zero padding 합니다.

    Args:
        image (Union[Image.Image, torch.Tensor, np.ndarray]): image
        coordinates (Union[List[int], Tuple[int, int, int, int]]): [left, top, right, bottom]

    Returns:
        Union[Image.Image, torch.Tensor, np.ndarray]: cropped image
    """
    if isinstance(image, Image.Image):
        return image.crop(coordinates)
    else:
        left, top, right, bottom = coordinates
        image_w, image_h = read_image_size(image)
        cropped_shape = (bottom - top, right - left)
        if image.ndim == 3:
            cropped_shape = (*cropped_shape, image.shape[2])
        if isinstance(image, torch.Tensor):
            cropped_image = torch.zeros(*cropped_shape, dtype=image.dtype, device=image.device)
        else:
            cropped_image = np.zeros(cropped_shape, dtype=image.dtype)
        cropped_image[
            np.clip(0, top, bottom) - top : np.clip(image_h, top, bottom) - top,
            np.clip(0, left, right) - left : np.clip(image_w, left, right) - left,
        ] = image[
            np.clip(top, 0, image_h) : np.clip(bottom, 0, image_h),
            np.clip(left, 0, image_w) : np.clip(right, 0, image_w),
        ]
        return cropped_image

add_constant_margin

add_constant_margin(image: Union[Image, Tensor, ndarray], left: int, top: int, right: int, bottom: int, value: Union[float, int]) -> Union[Image, Tensor, ndarray]

image에 left, top, right, bottom 만큼 constant value로 padding 합니다.

Parameters:

  • image (Union[Image, Tensor, ndarray]) –

    image

  • left (int) –

    image의 왼쪽 padding size 입니다.

  • top (int) –

    image의 위쪽 padding size 입니다.

  • right (int) –

    image의 오른쪽 padding size 입니다.

  • bottom (int) –

    image의 아래쪽 padding size 입니다.

  • value (Union[float, int]) –

    padding된 영역에 들어갈 value 입니다.

Source code in SaigeToolkit/data/transform/image_function.py
def add_constant_margin(
    image: Union[Image.Image, torch.Tensor, np.ndarray],
    left: int,
    top: int,
    right: int,
    bottom: int,
    value: Union[float, int],
) -> Union[Image.Image, torch.Tensor, np.ndarray]:
    """image에 left, top, right, bottom 만큼 constant value로 padding 합니다.

    Args:
        image (Union[Image.Image, torch.Tensor, np.ndarray]): image
        left (int): image의 왼쪽 padding size 입니다.
        top (int): image의 위쪽 padding size 입니다.
        right (int): image의 오른쪽 padding size 입니다.
        bottom (int): image의 아래쪽 padding size 입니다.
        value (Union[float, int]): padding된 영역에 들어갈 value 입니다.
    """
    width, height = read_image_size(image)
    new_width = width + left + right
    new_height = height + top + bottom
    if isinstance(image, Image.Image):
        if image.mode == "RGB":
            value = [int(value)] * 3
        elif image.mode == "L":
            value = [int(value)]
        else:
            raise NotImplementedError
        result = Image.new(image.mode, (new_width, new_height), tuple(value))
        result.paste(image, (left, top))
    elif isinstance(image, np.ndarray):
        result = np.ones((new_height, new_width) + image.shape[2:]) * value
        result = result.astype(image.dtype)
        result[top : top + height, left : left + width] = image
    elif isinstance(image, torch.Tensor):
        result = torch.ones(image.shape[:-2] + (new_height, new_width), device=image.device) * value
        result = result.to(image.dtype)
        result[..., top : top + height, left : left + width] = image
    else:
        raise NotImplementedError

    return result

add_constant_margin_array

add_constant_margin_array(array: Union[Tensor, ndarray], left: int, top: int, right: int, bottom: int, value: Union[float, int]) -> Union[Tensor, ndarray]

array에 left, top, right, bottom 만큼 constant value로 padding 합니다.

Parameters:

  • array (Union[Tensor, ndarray]) –

    array data [HW, BHW]

  • left (int) –

    array의 왼쪽 padding size 입니다.

  • top (int) –

    array의 위쪽 padding size 입니다.

  • right (int) –

    array의 오른쪽 padding size 입니다.

  • bottom (int) –

    array의 아래쪽 padding size 입니다.

  • value (Union[float, int]) –

    padding된 영역에 들어갈 value 입니다.

Source code in SaigeToolkit/data/transform/image_function.py
def add_constant_margin_array(
    array: Union[torch.Tensor, np.ndarray],
    left: int,
    top: int,
    right: int,
    bottom: int,
    value: Union[float, int],
) -> Union[torch.Tensor, np.ndarray]:
    """array에 left, top, right, bottom 만큼 constant value로 padding 합니다.

    Args:
        array (Union[torch.Tensor, np.ndarray]): array data [HW, BHW]
        left (int): array의 왼쪽 padding size 입니다.
        top (int): array의 위쪽 padding size 입니다.
        right (int): array의 오른쪽 padding size 입니다.
        bottom (int): array의 아래쪽 padding size 입니다.
        value (Union[float, int]): padding된 영역에 들어갈 value 입니다.
    """
    if isinstance(array, np.ndarray):
        is_ndarray = True
        array = torch.from_numpy(array)
    else:
        is_ndarray = False

    result = torch.nn.functional.pad(array, (left, right, top, bottom), value=value)

    if is_ndarray:
        result = result.numpy(force=True)

    return result

fill_pixels_with_mask

fill_pixels_with_mask(image: Union[Image, ndarray], bool_mask: ndarray, value: Union[int, float] = 0) -> Union[Image, ndarray]

image 중 bool_mask=True인 픽셀들을 value로 채웁니다.

Parameters:

  • image (Union[Image, ndarray]) –

    image (HW or HWC)

  • bool_mask (ndarray) –

    boolean mask (HW)

  • value (Union[int, float], default: 0 ) –

    . Defaults to 0.

Returns:

  • Union[Image, ndarray]

    Union[Image.Image, np.ndarray]: 결과 이미지

Note
  • image가 Image.Image인 경우, 결과 이미지도 Image.Image로 반환합니다.
  • image의 값이 inplace로 변경됩니다. 값이 변경되지 않길 원한다면, copy를 해주세요.
Source code in SaigeToolkit/data/transform/image_function.py
def fill_pixels_with_mask(
    image: Union[Image.Image, np.ndarray],
    bool_mask: np.ndarray,
    value: Union[int, float] = 0,
) -> Union[Image.Image, np.ndarray]:
    """image 중 bool_mask=True인 픽셀들을 value로 채웁니다.

    Args:
        image (Union[Image.Image, np.ndarray]): image (HW or HWC)
        bool_mask (np.ndarray): boolean mask (HW)
        value (Union[int, float], optional): . Defaults to 0.

    Returns:
        Union[Image.Image, np.ndarray]: 결과 이미지

    Note:
        - image가 Image.Image인 경우, 결과 이미지도 Image.Image로 반환합니다.
        - image의 값이 inplace로 변경됩니다. 값이 변경되지 않길 원한다면, copy를 해주세요.
    """
    is_pil = isinstance(image, Image.Image)
    image = np.asarray(image)

    assert image.ndim in [2, 3] and image.shape[:2] == bool_mask.shape  # HW or HWC

    if image.ndim > bool_mask.ndim:  # max ndim difference is 1
        bool_mask = np.expand_dims(bool_mask, axis=-1)

    image = np.where(bool_mask, value, image)

    if is_pil:
        image = Image.fromarray(image)

    return image

to_numpy

to_numpy(image: Union[Image, Tensor, ndarray], copy: bool = False) -> ndarray
Source code in SaigeToolkit/data/transform/image_function.py
def to_numpy(image: Union[Image.Image, torch.Tensor, np.ndarray], copy: bool = False) -> np.ndarray:
    if isinstance(image, Image.Image):
        image = np.array(image)  # Image.Image -> np.ndarray
    elif isinstance(image, torch.Tensor):
        assert image.ndim in [3, 4]  # BCHW / CHW

        if image.ndim == 4:  # if BCHW
            assert image.shape[0] == 1  # Batch size must be 1
            image = image.squeeze(0)  # BCHW -> CHW

        image = image.permute(1, 2, 0)  # CHW -> HWC

        if image.shape[2] == 1:  # if gray image
            image = image.squeeze(2)  # HWC -> HW

        image = image.detach().cpu().numpy()  # torch.Tensor, HW(C) -> np.ndarray, HW(C)
    elif isinstance(image, np.ndarray):
        if copy:
            image = image.copy()
    else:
        raise NotImplementedError

    # gray: HW / rgb & rgba: HWC
    return image

to_pil

to_pil(image: Union[Image, Tensor, ndarray], copy: bool = False) -> Image
Source code in SaigeToolkit/data/transform/image_function.py
def to_pil(image: Union[Image.Image, torch.Tensor, np.ndarray], copy: bool = False) -> Image.Image:
    if isinstance(image, Image.Image):
        if copy:
            image = image.copy()
    elif isinstance(image, torch.Tensor):
        image = to_numpy(image)  # torch.Tensor -> np.ndarray
        image = Image.fromarray(image)  # np.ndarray -> Image.Image
    elif isinstance(image, np.ndarray):
        assert image.ndim in [2, 3]  # HW / HWC

        if image.ndim == 3 and image.shape[2] == 1:  # grayscale shape HW1
            image = np.squeeze(image, axis=2)  # HW1 -> HW

        image = Image.fromarray(image)  # np.ndarray -> Image.Image
    else:
        raise NotImplementedError

    return image

convert_image_mode

convert_image_mode(image: Union[Image, ndarray], mode: str, copy: bool = False) -> Union[Image, ndarray]

convert image mode

Parameters:

  • image (Union[Image, ndarray]) –

    image

  • mode (str) –

    "RGB" or "L"

  • copy (bool, default: False ) –

    to copy data. Defaults to False.

Returns:

  • Union[Image, ndarray]

    Union[Image.Image, np.ndarray]: converted image

Note

RGB -> L 변환 시 Image.Image와 np.ndarray 연산 결과가 다를 수 있음. (PIL과 cv2 에서 변환식은 L = R * 299/1000 + G * 587/1000 + B * 114/1000 로 동일하나 소숫점 처리 방식이 다름)

Source code in SaigeToolkit/data/transform/image_function.py
def convert_image_mode(
    image: Union[Image.Image, np.ndarray], mode: str, copy: bool = False
) -> Union[Image.Image, np.ndarray]:
    """convert image mode

    Args:
        image (Union[Image.Image, np.ndarray]): image
        mode (str): "RGB" or "L"
        copy (bool, optional): to copy data. Defaults to False.

    Returns:
        Union[Image.Image, np.ndarray]: converted image

    Note:
        RGB -> L 변환 시 Image.Image와 np.ndarray 연산 결과가 다를 수 있음.
        (PIL과 cv2 에서 변환식은 L = R * 299/1000 + G * 587/1000 + B * 114/1000 로 동일하나 소숫점 처리 방식이 다름)
    """
    if mode not in IMAGE_MODES:
        raise NotImplementedError

    if isinstance(image, Image.Image):
        if image.mode != mode:
            image = image.convert(mode)
        elif copy:
            image = image.copy()
    elif isinstance(image, np.ndarray):
        # check original mode & regularize (RGB: HW3 / L: HW)
        if image.ndim == 3:
            if image.shape[2] == 3:
                original_mode = "RGB"
            elif image.shape[2] == 1:
                original_mode = "L"
                image = image[:, :, 0]  # HW1 -> HW (no copy)
            elif image.shape[2] == 4:
                original_mode = "RGB"
                image = image[:, :, :3]  # RGBA -> RGB (no copy)
            else:
                raise NotImplementedError
        elif image.ndim == 2:
            original_mode = "L"
        else:
            raise NotImplementedError
        # convert
        if original_mode == "RGB" and mode == "L":
            image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        elif original_mode == "L" and mode == "RGB":
            image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
        elif copy:
            image = image.copy()
    else:
        raise NotImplementedError
    return image