Skip to content

transform

Module diagram

classDiagram
  class transform {
  }
  class augmentation {
  }
  class api {
  }
  class augment_function {
  }
  class augment_transform {
  }
  class base_transform {
  }
  class builder {
  }
  class compose {
  }
  class base_compose {
  }
  class builder {
  }
  class compose {
  }
  class box_function {
  }
  class function_util {
  }
  class image_function {
  }
  class image_load {
  }
  class polygon_function {
  }
  class resize {
  }
  class roi {
  }
  class api {
  }
  class roi_calculator {
  }
  class roi_handler {
  }
  class transform {
  }
  class typevar {
  }
  augmentation --> builder
  api --> augment_function
  augment_transform --> augment_function
  augment_transform --> base_transform
  builder --> augment_transform
  builder --> builder
  builder --> compose
  compose --> base_compose
  box_function --> polygon_function
  box_function --> typevar
  function_util --> typevar
  image_function --> typevar
  image_load --> image_function
  polygon_function --> augment_function
  polygon_function --> typevar
  resize --> box_function
  resize --> image_function
  resize --> polygon_function
  resize --> typevar
  roi --> api
  roi --> roi_handler
  api --> roi_handler
  roi_handler --> roi_calculator
  transform --> augmentation
  transform --> image_function
  transform --> image_load
  transform --> resize
  transform --> roi_handler

data.transform

End-to-end 데이터 변환을 지원하는 Transform 클래스를 제공하는 모듈입니다.

Transfrom 클래스는 다음과 같은 순서로 transform을 적용합니다. 1. load PIL Image 2. 원본 이미지가 inspection_size_wh를 넘지 않도록 resize 했을 때의 image scale 계산 3. ROI 적용 (crop) @. roi_mask_first=True인 경우 ROI 적용 (blind mask) 4. 2번과 resize_factor를 하나로 묶어서 resize @. roi_mask_first=False인 경우 ROI 적용 (blind mask) 5. data augmentation

Note

roi_mask_first 옵션에 따라서 ROI의 blind mask를 적용하는 시점이 달라집니다. - roi_mask_first=True: ROI crop -> ROI mask -> resize - roi_mask_first=False: ROI crop -> resize -> ROI mask 일반적으로 resize 후에 mask를 적용하는 것이 더 효율적입니다.

augmentation

Data augmentation을 위한 기본 BaseTransform 클래스 및 Transform 구현, 그리고 여러 Data Transform들을 하나로 묶어서 관리해주는 기본 BaseCompose 클래스 및 Compose 구현을 제공합니다.

api

Image Augmentation Preview를 위한 API를 제공합니다.

ImageProcessor 클래스를 통해 각 augmentation들이 특정 파라미터 값에 대해 이미지를 어떻게 변형 시키는지 확인할 수 있습니다.

error_handler module-attribute
error_handler = get_error_handler(SaigeSegmentationError)
_APIDecorator

ImageProcessor에서 정의된 함수들을 decorate 해주는 헬퍼입니다.

staticmethod 와 decorator를 함께 사용할 경우 Cythonize시 제대로 동작하지 않는 이슈를 해결하기 위한 패치입니다. 참고: https://github.com/cython/cython/issues/1434

ImageProcessor의 각 함수에 다음과 같은 decorator를 씌우는 것과 동일한 역할을 합니다.

@staticmethod
@error_handler
@support_multi_image
@support_3dim_gray_image
def image_processor_function(image: np.ndarray, ...):
    ...

__init_subclass__
__init_subclass__(**kwargs)
Source code in SaigeToolkit/data/transform/augmentation/api.py
def __init_subclass__(cls, **kwargs):
    for name in dir(cls):
        if name in dir(_APIDecorator):
            continue
        func = getattr(cls, name)
        func = cls.support_3dim_gray_image(func)
        func = cls.support_multi_image(func)
        func = error_handler(func)
        func = staticmethod(func)
        setattr(cls, name, func)
support_3dim_gray_image staticmethod
support_3dim_gray_image(function)
Source code in SaigeToolkit/data/transform/augmentation/api.py
@staticmethod
def support_3dim_gray_image(function):
    @wraps(function)
    def decorator(image: ImageType, *args, **kwargs) -> Any:
        is_3dim_gray = image.ndim == 3 and image.shape[2] == 1
        if is_3dim_gray:
            image = image[:, :, 0]

        image, aug_params = function(image=image, *args, **kwargs)

        if is_3dim_gray:
            image = image[:, :, np.newaxis]

        return image, aug_params

    return decorator
support_multi_image staticmethod
support_multi_image(function)
Source code in SaigeToolkit/data/transform/augmentation/api.py
@staticmethod
def support_multi_image(function):
    @wraps(function)
    def decorator(image: Union[List[ImageType], ImageType], *args, **kwargs) -> Any:
        multipage = isinstance(image, List)
        if not multipage:
            image = [image]

        image_0, aug_params = function(image=image[0], *args, **kwargs)
        results = [image_0]
        for image_i in image[1:]:
            if aug_params is not None:
                results.append(function(image=image_i, fixed_aug_params=aug_params)[0])
            else:
                results.append(function(image=image_i, *args, **kwargs)[0])

        if not multipage:
            results = results[0]
        return results, aug_params

    return decorator
ImageProcessor

Bases: _APIDecorator

Image Augmentation Preview를 위한 API 입니다. 각 augmentation들이 특정 파라미터 값에 대해 이미지를 어떻게 변형 시키는지 확인할 수 있습니다.

Note1

일반적으로 학습 시에는 파라미터를 특정 이 아닌 범위로 설정하여 해당 범위에서 매번 랜덤한 값을 선택해 이미지에 적용합니다. 따라서 preview API의 입력 파라미터와 학습 시 넘겨주는 파라미터는 대부분 vs 범위의 차이를 가지게 됩니다. 예를 들어 preview API에서 rotate의 경우 angle (float) 값을 받지만, 학습 config에서는 angle_limit (List[float]) 범위를 받게됩니다. 각 augmentation을 학습에 사용시 필요한 config는 각 함수 설명의 Trainer Config 섹션을 참고하세요.

Note2

API 기획상, augmentation의 실제 자유도보다, 유저가 설정할 수 있는 파라미터가 적은 경우가 있습니다. (각 변 혹은 꼭짓점 마다 독립적으로 적용되는 ratio_jitter나 perspective_transform의 경우) 이러한 augmentation들은 api 호출 시, '값'을 입력 받아서, 이 '값'으로 부터 정의된 '범위'에서 필요한 값들을 랜덤하게 샘플링하게 됩니다. 이러한 augmentation들의 preview 함수를 정의할 때는, 인풋에 fixed_aug_params를 받을 수 있도록 해주어야합니다. (ImageProcessor.ratio_jitter 참고)

Usage

rotate augmentation preview 예제입니다. 상세 설명은 각 함수 설명 참고.

image = np.zeros((100, 100, 3), dtype=np.unit8)
error, augmented_result = ImageProcessor.rotate(image=image, angle=15)
augmented_image, augmentation_parameters = augmented_result

# In the case of 'rotate' augmentation, `augmentation_parameters` is None

vertical_flip
vertical_flip(image: ndarray) -> Tuple[ndarray, None]

image를 상하로 뒤집습니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.vertical_flip(image=image)
Trainer Config
{
    "_target_": "vertical_flip",
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def vertical_flip(image: np.ndarray) -> Tuple[np.ndarray, None]:
    """image를 상하로 뒤집습니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.vertical_flip(image=image)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "vertical_flip",
        }
        ```
    """
    return vertical_flip(image=image), None
horizontal_flip
horizontal_flip(image: ndarray) -> Tuple[ndarray, None]

image를 좌우로 뒤집습니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.horizontal_flip(image=image)
Trainer Config
{
    "_target_": "horizontal_flip",
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def horizontal_flip(image: np.ndarray) -> Tuple[np.ndarray, None]:
    """image를 좌우로 뒤집습니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.horizontal_flip(image=image)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "horizontal_flip",
        }
        ```
    """
    return horizontal_flip(image=image), None
rotate
rotate(image: ndarray, angle: float = 0.0) -> Tuple[ndarray, None]

image를 angle만큼 회전시킵니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • angle (float, default: 0.0 ) –

    회전하는 각도 입니다. 유효 범위는 다음과 같습니다. [-360.0, 360.0]. Defaults to 0.0.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.rotate(image=image, angle=60.0)
Trainer Config
{
    "_target_": "rotate",
    "angle_limit": List[float],  # angle 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def rotate(image: np.ndarray, angle: float = 0.0) -> Tuple[np.ndarray, None]:
    """image를 angle만큼 회전시킵니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        angle (float, optional): 회전하는 각도 입니다. 유효 범위는 다음과 같습니다. [-360.0, 360.0]. Defaults to 0.0.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.rotate(image=image, angle=60.0)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "rotate",
            "angle_limit": List[float],  # angle 최소 최대 범위
        }
        ```
    """
    return (
        rotate(
            image=image,
            angle=angle,
            interpolation=cv2.INTER_LINEAR,
            border_mode=cv2.BORDER_REFLECT_101,
            value=0,
            crop_border=False,
        ),
        None,
    )
random_rotate90
random_rotate90(image: ndarray, factor: int = 0) -> Tuple[ndarray, None]

image를 [0, 90, 180, 270] 중 랜덤한 각도만큼 회전시킵니다.

NOTE
  • 모든 이미지 픽셀은 유지되며, 회전 후 이미지 크기가 변경될 수 있습니다.
  • 예시: factor가 1일 경우 90도 회전하며, 이미지 사이즈는 (W, H) -> (H, W)로 변경됩니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • factor (int, default: 0 ) –

    회전하는 각도 입니다. factor에 90을 곱한 값만큼 회전합니다. (ex. factor가 1일 경우 90도) 유효범위는 다음과 같습니다 [0, 3]. Defaults to 0.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.random_rotate90(image=image, factor=1)
Trainer Config
{
    "_target_": "random_rotate90",
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def random_rotate90(image: np.ndarray, factor: int = 0) -> Tuple[np.ndarray, None]:
    """image를 [0, 90, 180, 270] 중 랜덤한 각도만큼 회전시킵니다.

    NOTE:
        - 모든 이미지 픽셀은 유지되며, 회전 후 이미지 크기가 변경될 수 있습니다.
        - 예시: factor가 1일 경우 90도 회전하며, 이미지 사이즈는 (W, H) -> (H, W)로 변경됩니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        factor (int, optional): 회전하는 각도 입니다. factor에 90을 곱한 값만큼 회전합니다. (ex. factor가 1일 경우 90도)
                                유효범위는 다음과 같습니다 [0, 3]. Defaults to 0.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.random_rotate90(image=image, factor=1)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "random_rotate90",
        }
        ```
    """
    return random_rotate90(image=image, factor=factor), None
color_jitter
color_jitter(image: ndarray, brightness: float = 1.0, contrast: float = 1.0, saturation: float = 1.0, hue: float = 0.0) -> Tuple[ndarray, None]

image의 밝기 (brightness), 대비 (contrast), 채도 (saturation), 색상 (hue)을 변경합니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • brightness (float, default: 1.0 ) –

    밝기를 담당하는 요소입니다. 유효 범위는 다음과 같습니다. [0.01, 10.00]. Defaults to 1.0.

  • contrast (float, default: 1.0 ) –

    대비를 담당하는 요소입니다. 유효 범위는 다음과 같습니다. [0.01, 10.00]. Defaults to 1.0.

  • saturation (float, default: 1.0 ) –

    채도를 담당하는 요소입니다. 유효 범위는 다음과 같습니다. [0.01, 10.00]. Defaults to 1.0.

  • hue (float, default: 0.0 ) –

    색상을 담당하는 요소입니다. 유효 범위는 다음과 같습니다. [-0.50, 0.50]. Defaults to 0.0.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.color_jitter(image=image,
                                                     brightness=0.5,
                                                     contrast=0.5,
                                                     saturation=0.5,
                                                     hue=0.1)  #  augmentation 적용
Trainer Config
{
    "_target_": "color_jitter",
    "brightness_limit": List[float],  # brightness 최소 최대 범위
    "contrast_limit": List[float],  # contrast 최소 최대 범위
    "saturation_limit": List[float],  # saturation 최소 최대 범위
    "hue_limit": List[float],  # hue 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def color_jitter(
    image: np.ndarray,
    brightness: float = 1.0,
    contrast: float = 1.0,
    saturation: float = 1.0,
    hue: float = 0.0,
) -> Tuple[np.ndarray, None]:
    """image의 밝기 (brightness), 대비 (contrast), 채도 (saturation), 색상 (hue)을 변경합니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        brightness (float, optional): 밝기를 담당하는 요소입니다. 유효 범위는 다음과 같습니다. [0.01, 10.00]. Defaults to 1.0.
        contrast (float, optional): 대비를 담당하는 요소입니다. 유효 범위는 다음과 같습니다. [0.01, 10.00]. Defaults to 1.0.
        saturation (float, optional): 채도를 담당하는 요소입니다. 유효 범위는 다음과 같습니다. [0.01, 10.00]. Defaults to 1.0.
        hue (float, optional): 색상을 담당하는 요소입니다. 유효 범위는 다음과 같습니다. [-0.50, 0.50]. Defaults to 0.0.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.color_jitter(image=image,
                                                             brightness=0.5,
                                                             contrast=0.5,
                                                             saturation=0.5,
                                                             hue=0.1)  #  augmentation 적용
        ```

    Trainer Config:
        ```python
        {
            "_target_": "color_jitter",
            "brightness_limit": List[float],  # brightness 최소 최대 범위
            "contrast_limit": List[float],  # contrast 최소 최대 범위
            "saturation_limit": List[float],  # saturation 최소 최대 범위
            "hue_limit": List[float],  # hue 최소 최대 범위
        }
        ```
    """
    return (
        color_jitter(
            image=image,
            brightness=brightness,
            contrast=contrast,
            saturation=saturation,
            hue=hue,
            order=[0, 1, 2, 3],
        ),
        None,
    )
blur
blur(image: ndarray, ksize: int = 1) -> Tuple[ndarray, None]

image에 averaging blur를 적용합니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • ksize (int, default: 1 ) –

    blur kernel의 크기 입니다. 유효 범위는 다음과 같습니다. [1, 100]. Defaults to 1.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.blur(image=image, ksize=3)
Trainer Config
{
    "_target_": "blur",
    "ksize_limit": List[int],  # ksize 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def blur(image: np.ndarray, ksize: int = 1) -> Tuple[np.ndarray, None]:
    """image에 averaging blur를 적용합니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        ksize (int, optional): blur kernel의 크기 입니다. 유효 범위는 다음과 같습니다. [1, 100]. Defaults to 1.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.blur(image=image, ksize=3)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "blur",
            "ksize_limit": List[int],  # ksize 최소 최대 범위
        }
        ```
    """
    return blur(image=image, ksize=ksize), None
gaussian_blur
gaussian_blur(image: ndarray, ksize: int = 1) -> Tuple[ndarray, None]

image에 gaussian blur를 적용합니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • ksize (int, default: 1 ) –

    blur kernel의 크기 입니다. 유효 범위는 다음과 같습니다. [1, 100]. Defaults to 1.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.gaussian_blur(image=image, ksize=3)
Trainer Config
{
    "_target_": "gaussian_blur",
    "ksize_limit": List[int],  # ksize 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def gaussian_blur(image: np.ndarray, ksize: int = 1) -> Tuple[np.ndarray, None]:
    """image에 gaussian blur를 적용합니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        ksize (int, optional): blur kernel의 크기 입니다. 유효 범위는 다음과 같습니다. [1, 100]. Defaults to 1.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.gaussian_blur(image=image, ksize=3)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "gaussian_blur",
            "ksize_limit": List[int],  # ksize 최소 최대 범위
        }
        ```
    """
    return gaussian_blur(image=image, ksize=ksize, sigma=0.0), None
adjust_brightness
adjust_brightness(image: ndarray, brightness: float = 0.0) -> Tuple[ndarray, None]

image의 밝기 (brightness)를 변경합니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • brightness (float, default: 0.0 ) –

    밝기를 변화 강도입니다. 유효 범위는 다음과 같습니다. [-1.00, 1.00]. Defaults to 0.0.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.adjust_brightness(image=image, brightness=0.3)
Trainer Config
{
    "_target_": "adjust_brightness",
    "brightness_limit": List[float],  # brightness 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def adjust_brightness(image: np.ndarray, brightness: float = 0.0) -> Tuple[np.ndarray, None]:
    """image의 밝기 (brightness)를 변경합니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        brightness (float, optional): 밝기를 변화 강도입니다. 유효 범위는 다음과 같습니다. [-1.00, 1.00]. Defaults to 0.0.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.adjust_brightness(image=image, brightness=0.3)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "adjust_brightness",
            "brightness_limit": List[float],  # brightness 최소 최대 범위
        }
        ```
    """
    return (
        adjust_brightness(image=image, brightness=brightness, brightness_by_max=True),
        None,
    )
adjust_contrast
adjust_contrast(image: ndarray, contrast: float = 0.0) -> Tuple[ndarray, None]

image의 대비 (contrast)를 변경합니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • contrast (float, default: 0.0 ) –

    대비 변화 강도입니다. 유효 범위는 다음과 같습니다. [-1.00, 1.00]. Defaults to 0.0.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.adjust_contrast(image=image, contrast=0.7)
Trainer Config
{
    "_target_": "adjust_contrast",
    "contrast_limit": List[float],  # contrast 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def adjust_contrast(image: np.ndarray, contrast: float = 0.0) -> Tuple[np.ndarray, None]:
    """image의 대비 (contrast)를 변경합니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        contrast (float, optional): 대비 변화 강도입니다. 유효 범위는 다음과 같습니다. [-1.00, 1.00]. Defaults to 0.0.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.adjust_contrast(image=image, contrast=0.7)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "adjust_contrast",
            "contrast_limit": List[float],  # contrast 최소 최대 범위
        }
        ```
    """
    return adjust_contrast(image=image, contrast=contrast), None
adjust_hue
adjust_hue(image: ndarray, hue: float = 0.0) -> Tuple[ndarray, None]

image의 색조 (hue)를 변경합니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • hue (float, default: 0.0 ) –

    색조 변화 강도입니다. 유효 범위는 다음과 같습니다. [-1.00, 1.00]. Defaults to 0.0.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.adjust_hue(image=image, hue=0.7)
Trainer Config
{
    "_target_": "adjust_hue",
    "hue_limit": List[float],  # hue 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def adjust_hue(image: np.ndarray, hue: float = 0.0) -> Tuple[np.ndarray, None]:
    """image의 색조 (hue)를 변경합니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        hue (float, optional): 색조 변화 강도입니다. 유효 범위는 다음과 같습니다. [-1.00, 1.00]. Defaults to 0.0.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.adjust_hue(image=image, hue=0.7)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "adjust_hue",
            "hue_limit": List[float],  # hue 최소 최대 범위
        }
        ```
    """
    return adjust_hue(image=image, hue=hue), None
adjust_saturation
adjust_saturation(image: ndarray, saturation: float = 0.0) -> Tuple[ndarray, None]

image의 채도 (saturation)를 변경합니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • saturation (float, default: 0.0 ) –

    채도 변화 강도입니다. 유효 범위는 다음과 같습니다. [-1.00, 1.00]. Defaults to 0.0.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.adjust_saturation(image=image, saturation=0.7)
Trainer Config
{
    "_target_": "adjust_saturation",
    "saturation_limit": List[float],  # saturation 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def adjust_saturation(image: np.ndarray, saturation: float = 0.0) -> Tuple[np.ndarray, None]:
    """image의 채도 (saturation)를 변경합니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        saturation (float, optional): 채도 변화 강도입니다. 유효 범위는 다음과 같습니다. [-1.00, 1.00]. Defaults to 0.0.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.adjust_saturation(image=image, saturation=0.7)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "adjust_saturation",
            "saturation_limit": List[float],  # saturation 최소 최대 범위
        }
        ```
    """
    return adjust_saturation(image=image, saturation=saturation), None
adjust_gamma
adjust_gamma(image: ndarray, gamma: float = 0.0) -> Tuple[ndarray, None]

image의 gamma를 조절하여 밝기를 변화시킵니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • gamma (float, default: 0.0 ) –

    조절할 gamma value 입니다. 유효 범위는 다음과 같습니다. [-1.0, 1.0]. Defaults to 0.0.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.adjust_gamma(image=image, gamma=0.5)
Trainer Config
{
    "_target_": "adjust_gamma",
    "gamma_limit": List[float],  # gamma 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def adjust_gamma(image: np.ndarray, gamma: float = 0.0) -> Tuple[np.ndarray, None]:
    """image의 gamma를 조절하여 밝기를 변화시킵니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        gamma (float, optional): 조절할 gamma value 입니다. 유효 범위는 다음과 같습니다. [-1.0, 1.0]. Defaults to 0.0.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.adjust_gamma(image=image, gamma=0.5)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "adjust_gamma",
            "gamma_limit": List[float],  # gamma 최소 최대 범위
        }
        ```
    """
    return adjust_gamma(image=image, gamma=gamma), None
adjust_brightness_contrast
adjust_brightness_contrast(image: ndarray, brightness: float = 0.0, contrast: float = 0.0) -> Tuple[ndarray, None]

image의 밝기 (brightness), 대비 (contrast)를 변경합니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • brightness (float, default: 0.0 ) –

    밝기를 담당하는 요소입니다. 유효 범위는 다음과 같습니다. [-1.00, 1.00]. Defaults to 0.0.

  • contrast (float, default: 0.0 ) –

    대비를 담당하는 요소입니다. 유효 범위는 다음과 같습니다. [-1.00, 1.00]. Defaults to 0.0.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.adjust_brightness_contrast(image=image, brightness=0.3, contrast=0.7)
Trainer Config
{
    "_target_": "adjust_brightness_contrast",
    "brightness_limit": List[float],  # brightness 최소 최대 범위
    "contrast_limit": List[float],  # contrast 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def adjust_brightness_contrast(
    image: np.ndarray, brightness: float = 0.0, contrast: float = 0.0
) -> Tuple[np.ndarray, None]:
    """image의 밝기 (brightness), 대비 (contrast)를 변경합니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        brightness (float, optional): 밝기를 담당하는 요소입니다. 유효 범위는 다음과 같습니다. [-1.00, 1.00]. Defaults to 0.0.
        contrast (float, optional): 대비를 담당하는 요소입니다. 유효 범위는 다음과 같습니다. [-1.00, 1.00]. Defaults to 0.0.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.adjust_brightness_contrast(image=image, brightness=0.3, contrast=0.7)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "adjust_brightness_contrast",
            "brightness_limit": List[float],  # brightness 최소 최대 범위
            "contrast_limit": List[float],  # contrast 최소 최대 범위
        }
        ```
    """
    return (
        adjust_brightness_contrast(
            image=image, brightness=brightness, contrast=contrast, brightness_by_max=True
        ),
        None,
    )
iso_noise
iso_noise(image: ndarray, color_shift: float = 0.0, intensity: float = 0.0) -> Tuple[ndarray, None]

Apply camera sensor noise.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • color_shift (float, default: 0.0 ) –

    variance range for color hue change. Measured as a fraction of 360 degree Hue angle in HLS colorspace. 유효 범위는 다음과 같습니다. [0.00, 1.00]. Defaults to 0.0.

  • intensity (float, default: 0.0 ) –

    Multiplicative factor that control strength of color and luminace noise. 유효 범위는 다음과 같습니다. [0.00, 2.00]. Defaults to 0.0.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.iso_noise(image=image, color_shift=0.2, intensity=0.2)
Trainer Config
{
    "_target_": "iso_noise",
    "color_shift_limit": List[float],  # color_shift 최소 최대 범위
    "intensity_limit": List[float],  # intensity 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def iso_noise(
    image: np.ndarray, color_shift: float = 0.0, intensity: float = 0.0
) -> Tuple[np.ndarray, None]:
    """Apply camera sensor noise.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        color_shift (float, optional): variance range for color hue change. Measured as a fraction of 360 degree Hue angle in HLS colorspace.
                                       유효 범위는 다음과 같습니다. [0.00, 1.00]. Defaults to 0.0.
        intensity (float, optional): Multiplicative factor that control strength of color and luminace noise.
                                     유효 범위는 다음과 같습니다. [0.00, 2.00]. Defaults to 0.0.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.iso_noise(image=image, color_shift=0.2, intensity=0.2)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "iso_noise",
            "color_shift_limit": List[float],  # color_shift 최소 최대 범위
            "intensity_limit": List[float],  # intensity 최소 최대 범위
        }
        ```
    """
    return (
        iso_noise(image=image, color_shift=color_shift, intensity=intensity, random_state=0),
        None,
    )
ratio_jitter
ratio_jitter(image: ndarray, proportion: Optional[int] = 0, fixed_aug_params: Optional[Dict] = None) -> Tuple[ndarray, Dict]

image에 random하게 padding과 crop을 한 뒤, 원래 size로 resize 하는 과정을 통해 image의 가로 세로 비율을 변경합니다. NOTE: 프리뷰에서는 네변에 각각 [0, proportion] 범위에서 crop 혹은 padding한 예시를 보여줍니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • proportion (int, default: 0 ) –

    padding 혹은 crop을 할 비율(단위: 백분율)입니다. 유효 범위는 다음과 같습니다. [0, 50]. Defaults to 0. (fixed_aug_params=None일 때만 작동합니다.)

  • fixed_aug_params (Dict, default: None ) –

    각 변에 대해서 고정된 crop 혹은 padding을 직접 정해주고자 할 때 사용합니다.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • Dict ( Dict ) –

    각 변에 적용된 proportioin 비율(-50 ~ 50 사이의 실수, 양수이면 crop, 음수이면 pad)들입니다.

    {
        "proportion_left": float,
        "proportion_right": float,
        "proportion_top": float,
        "proportion_bottom": float,
    }
    

각 변에 대해 crop 혹은 padding할 비율을 직접 설정
error, augmented_image = ImageProcessor.ratio_jitter(
    image=image,
    proportion=None,
    fixed_aug_params={
        "proportion_left": 2.0,
        "proportion_right": 2.0,
        "proportion_top": 2.0,
        "proportion_bottom": 2.0,
    }
)
각 변에 [-proportion, proportion] 범위에서 랜덤하게 crop 혹은 padding 적용
error, augmented_image = ImageProcessor.ratio_jitter(image=image, proportion=2)
Trainer Config
{
    "_target_": "ratio_jitter",
    "proportion_limit": int,  # proportion 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def ratio_jitter(
    image: np.ndarray,
    proportion: Optional[int] = 0,
    fixed_aug_params: Optional[Dict] = None,
) -> Tuple[np.ndarray, Dict]:
    """image에 random하게 padding과 crop을 한 뒤, 원래 size로 resize 하는 과정을 통해 image의 가로 세로 비율을 변경합니다.
    NOTE: 프리뷰에서는 네변에 각각 [0, proportion] 범위에서 crop 혹은 padding한 예시를 보여줍니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        proportion (int, optional): padding 혹은 crop을 할 비율(단위: 백분율)입니다. 유효 범위는 다음과 같습니다. [0, 50]. Defaults to 0. (`fixed_aug_params=None`일 때만 작동합니다.)
        fixed_aug_params (Dict, optional): 각 변에 대해서 고정된 crop 혹은 padding을 직접 정해주고자 할 때 사용합니다.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        Dict: 각 변에 적용된 proportioin 비율(-50 ~ 50 사이의 실수, 양수이면 crop, 음수이면 pad)들입니다.
            ```python
            {
                "proportion_left": float,
                "proportion_right": float,
                "proportion_top": float,
                "proportion_bottom": float,
            }
            ```

    Example1: 각 변에 대해 crop 혹은 padding할 비율을 직접 설정
        ```python
        error, augmented_image = ImageProcessor.ratio_jitter(
            image=image,
            proportion=None,
            fixed_aug_params={
                "proportion_left": 2.0,
                "proportion_right": 2.0,
                "proportion_top": 2.0,
                "proportion_bottom": 2.0,
            }
        )
        ```

    Example2: 각 변에 [-proportion, proportion] 범위에서 랜덤하게 crop 혹은 padding 적용
        ```python
        error, augmented_image = ImageProcessor.ratio_jitter(image=image, proportion=2)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "ratio_jitter",
            "proportion_limit": int,  # proportion 최대 범위
        }
        ```
    """
    aug_param_keys = ["proportion_left", "proportion_right", "proportion_top", "proportion_bottom"]
    if fixed_aug_params is None:
        check_value(proportion, 0, 50)
        proportion_limit = [-proportion, proportion]
        fixed_aug_params = {k: random.uniform(*proportion_limit) for k in aug_param_keys}
    else:
        for k in aug_param_keys:
            check_value(fixed_aug_params[k], -50, 50)

    return (
        ratio_jitter(
            image=image,
            proportion_left=fixed_aug_params["proportion_left"] / 100,
            proportion_right=fixed_aug_params["proportion_right"] / 100,
            proportion_top=fixed_aug_params["proportion_top"] / 100,
            proportion_bottom=fixed_aug_params["proportion_bottom"] / 100,
            resampling="bilinear",
            border_mode=cv2.BORDER_CONSTANT,
            value=0,
        ),
        fixed_aug_params,
    )
zoom
zoom(image: ndarray, ratio: float = 1.0) -> Tuple[ndarray, None]

image에 zoom in/out 효과를 줍니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • ratio (float, default: 1.0 ) –

    zoom in/out 할 비율입니다. 1보다 크면 zoom in을 1보다 작으면 zoom out을 합니다. 유효 범위는 다음과 같습니다. [0.01, 100.00]. Defaults to 1.0.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.zoom(image=image, ratio=2.0)
Trainer Config
{
    "_target_": "zoom",
    "ratio_limit": List[float],  # ratio 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def zoom(image: np.ndarray, ratio: float = 1.0) -> Tuple[np.ndarray, None]:
    """image에 zoom in/out 효과를 줍니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        ratio (float, optional): zoom in/out 할 비율입니다. 1보다 크면 zoom in을 1보다 작으면 zoom out을 합니다.
                                 유효 범위는 다음과 같습니다. [0.01, 100.00]. Defaults to 1.0.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.zoom(image=image, ratio=2.0)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "zoom",
            "ratio_limit": List[float],  # ratio 최소 최대 범위
        }
        ```
    """
    return (
        zoom(
            image=image,
            ratio=ratio,
            h_start=0,
            w_start=0,
            resampling="bilinear",
            border_mode=cv2.BORDER_CONSTANT,
            value=0,
        ),
        None,
    )
random_resized_crop_and_pad
random_resized_crop_and_pad(image: ndarray, scale: float = 1.0, aspect_ratio: float = 1.0, height: Optional[int] = None, width: Optional[int] = None) -> Tuple[ndarray, None]

원본 image의 scale 비율의 면적을 가지면서 가로 세로 비가 aspect_ratio인 image를 random하게 crop 한 뒤, (crop image가 원본 image 보다 커지는 경우 padding을 통해 해결합니다.) 크기가 (height, width)가 되도록 resize를 합니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • scale (float, default: 1.0 ) –

    crop할 image의 면적을 나타내는 값입니다. 실제 면적은 [원본 이미지의 면적 * scale] 입니다. 유효 범위는 다음과 같습니다. [0.01, 1.00]. Defaults to 1.0.

  • aspect_ratio (float, default: 1.0 ) –

    crop할 image의 가로 세로 비를 나타내는 값입니다. 유효 범위는 다음과 같습니다. [0.10, 10.00]. Defaults to 1.0.

  • height (int, default: None ) –

    crop image를 resize할 height 입니다. None인 경우 입력 이미지의 원본 height를 사용합니다.

  • width (int, default: None ) –

    crop image를 resize할 width 입니다. None인 경우 입력 이미지의 원본 width를 사용합니다.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.random_resized_crop_and_pad(image=image,
                                                                    scale=0.5,
                                                                    aspect_ratio=2,
                                                                    height=256,
                                                                    width=256)
Trainer Config
{
    "_target_": "random_resized_crop_and_pad",
    "scale_limit": List[float],  # scale 최소 최대 범위
    "aspect_ratio_limit": List[float],  # aspect_ratio 최소 최대 범위
    "height": Optional[int],  # crop 후 resize할 이미지 height (None인 경우 원본 height 사용)
    "width": Optional[int],  # crop 후 resize할 이미지 width (None인 경우 원본 width 사용)
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def random_resized_crop_and_pad(
    image: np.ndarray,
    scale: float = 1.0,
    aspect_ratio: float = 1.0,
    height: Optional[int] = None,
    width: Optional[int] = None,
) -> Tuple[np.ndarray, None]:
    """원본 image의 scale 비율의 면적을 가지면서 가로 세로 비가 aspect_ratio인 image를 random하게 crop 한 뒤,
       (crop image가 원본 image 보다 커지는 경우 padding을 통해 해결합니다.)
       크기가 (height, width)가 되도록 resize를 합니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        scale (float, optional): crop할 image의 면적을 나타내는 값입니다. 실제 면적은 [원본 이미지의 면적 * scale] 입니다.
                                 유효 범위는 다음과 같습니다. [0.01, 1.00]. Defaults to 1.0.
        aspect_ratio (float, optional): crop할 image의 가로 세로 비를 나타내는 값입니다.
                                        유효 범위는 다음과 같습니다. [0.10, 10.00]. Defaults to 1.0.
        height (int): crop image를 resize할 height 입니다. None인 경우 입력 이미지의 원본 height를 사용합니다.
        width (int): crop image를 resize할 width 입니다. None인 경우 입력 이미지의 원본 width를 사용합니다.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.random_resized_crop_and_pad(image=image,
                                                                            scale=0.5,
                                                                            aspect_ratio=2,
                                                                            height=256,
                                                                            width=256)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "random_resized_crop_and_pad",
            "scale_limit": List[float],  # scale 최소 최대 범위
            "aspect_ratio_limit": List[float],  # aspect_ratio 최소 최대 범위
            "height": Optional[int],  # crop 후 resize할 이미지 height (None인 경우 원본 height 사용)
            "width": Optional[int],  # crop 후 resize할 이미지 width (None인 경우 원본 width 사용)
        }
        ```
    """
    return (
        random_resized_crop_and_pad(
            image=image,
            scale=scale,
            aspect_ratio=aspect_ratio,
            h_start=0,
            w_start=0,
            height=height,
            width=width,
            resampling="bilinear",
            border_mode=cv2.BORDER_CONSTANT,
            value=0,
        ),
        None,
    )
light_reflect
light_reflect(image: ndarray, radius: float = 0.0) -> Tuple[ndarray, None]

image에 원형 빛을 비춘 것 같은 효과를 줍니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • radius (float, default: 0.0 ) –

    원형 빛의 반지름 크기 입니다. 유효 범위는 다음과 같습니다. [0.00, 1.00]. Defaults to 0.0.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • NoneType ( None ) –

    None

Example
error, augmented_image = ImageProcessor.light_reflect(image=image, radius=0.30)
Trainer Config
{
    "_target_": "light_reflect",
    "radius_limit": List[float],  # radius 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def light_reflect(image: np.ndarray, radius: float = 0.0) -> Tuple[np.ndarray, None]:
    """image에 원형 빛을 비춘 것 같은 효과를 줍니다.

    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        radius (float, optional): 원형 빛의 반지름 크기 입니다. 유효 범위는 다음과 같습니다. [0.00, 1.00]. Defaults to 0.0.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        NoneType: None

    Example:
        ```python
        error, augmented_image = ImageProcessor.light_reflect(image=image, radius=0.30)
        ```

    Trainer Config:
        ```python
        {
            "_target_": "light_reflect",
            "radius_limit": List[float],  # radius 최소 최대 범위
        }
        ```
    """
    return (
        light_reflect(image=image, xc=0.5, yc=0.5, x_radius=radius, y_radius=radius, angle=0),
        None,
    )
perspective_transform
perspective_transform(image: ndarray, intensity: Optional[int] = 0, fixed_aug_params: Optional[Dict] = None) -> Tuple[ndarray, Dict]

이미지를 투영 변환(Perspective Transform)합니다. image를 다른 각도(시점)에서 바라본 형태로 변환합니다. Perspective Transform 을 위한 네개의 도착점의 좌표 (offset)은 다음과 같이 샘플링됩니다. offset_top_left: 원본 이미지의 좌측 상단 모서리를 얼마만큼 중심부로 이동시킬 지에 대한 실수 값이 들어있습니다. 즉, Perspective Transform 의 좌측 상단점의 도착점은 아래와 같이 계산할 수 있습니다.

```
x` = 0 + width * offset_top_left[0]
y` = 0 + height * offset_top_left[1]
point_dst = (x`, y`)
```
offset_bottom_right

offset_top_left 와 동일하되 우측 하단 점을 나타냅니다. Perspective Transform 의 우측 하단점의 도착점은 아래와 같이 계산할 수 있습니다.

x` = width - width * offset_bottom_right[0]
y` = height - height * offset_bottom_right[1]
point_dst = (x`, y`)

offset_top_right: offset_top_left 와 동일하되 우측 상단 점을 나타냅니다. offset_bottom_left: offset_top_left 와 동일하되 좌측 하단 점을 나타냅니다.

Parameters:

  • image (ndarray) –

    augmentation을 적용할 image 입니다.

    data type: uint8
    channel: H x W / H x W x 1  - Gray
             H x W x 3 - RGB
             H x W x 4 - RGBA
    

  • intensity (int, default: 0 ) –

    perspective transform 을 적용 강도(단위: 백분율)입니다. intensity 범위 내에서 랜덤한 값으로 샘플된 네개의 offset 을 이용해 perspective transform 을 수행합니다. 유효 범위는 [0, 49] 로, 각 도착지 점들은 이미지의 중간 선을 지나칠 수 없습니다. 이를 통해 이미지가 반전되는 정도의 왜곡을 방지합니다. Defaults to 0. (fixed_aug_params=None일 때만 작동합니다.)

  • fixed_aug_params (Dict, default: None ) –

    각 꼭짓점에 대해서 offset 비율을 직접 정해주고자 할 때 사용합니다.

Returns:

  • ndarray

    np.ndarray: augmentation이 적용된 image 입니다.

  • Dict ( Dict ) –

    각 꼭짓점에 적용된 offset들입니다.

    {
        "offset_top_left": Tuple[float, float],
        "offset_top_right": Tuple[float, float],
        "offset_bottom_right": Tuple[float, float],
        "offset_bottom_left": Tuple[float, float],
    }
    

각 꼭짓점의 offset 비율을 직접 설정
error, augmented_image = ImageProcessor.perspective_transform(
    image=image,
    fixed_aug_params={
        "offset_top_left": (25.0, 30.0),
        "offset_top_right": (10.0, 40.0),
        "offset_bottom_right": (0.0, 20.0),
        "offset_bottom_left": (20.0, 35.0),
    }
)
각 꼭짓점에 [0, intensity] 범위에서 랜덤하게 offset 비율을 적용
error, augmented_image = ImageProcessor.perspective_transform(
    image=image,
    intensity=10,
)
Trainer Config
{
    "_target_": "perspective_transform",
    "intensity_limit": List[float],  # intensity 최소 최대 범위
}
Source code in SaigeToolkit/data/transform/augmentation/api.py
def perspective_transform(
    image: np.ndarray,
    intensity: Optional[int] = 0,
    fixed_aug_params: Optional[Dict] = None,
) -> Tuple[np.ndarray, Dict]:
    """이미지를 투영 변환(Perspective Transform)합니다. image를 다른 각도(시점)에서 바라본 형태로 변환합니다.
    Perspective Transform 을 위한 네개의 도착점의 좌표 (offset)은 다음과 같이 샘플링됩니다.
    offset_top_left:
        원본 이미지의 좌측 상단 모서리를 얼마만큼 중심부로 이동시킬 지에 대한 실수 값이 들어있습니다.
        즉, Perspective Transform 의 좌측 상단점의 도착점은 아래와 같이 계산할 수 있습니다.

        ```
        x` = 0 + width * offset_top_left[0]
        y` = 0 + height * offset_top_left[1]
        point_dst = (x`, y`)
        ```

    offset_bottom_right:
        `offset_top_left` 와 동일하되 우측 하단 점을 나타냅니다.
        Perspective Transform 의 우측 하단점의 도착점은 아래와 같이 계산할 수 있습니다.

        ```
        x` = width - width * offset_bottom_right[0]
        y` = height - height * offset_bottom_right[1]
        point_dst = (x`, y`)
        ```

    offset_top_right: `offset_top_left` 와 동일하되 우측 상단 점을 나타냅니다.
    offset_bottom_left: `offset_top_left` 와 동일하되 좌측 하단 점을 나타냅니다.


    Args:
        image (np.ndarray): augmentation을 적용할 image 입니다.
            ```
            data type: uint8
            channel: H x W / H x W x 1  - Gray
                     H x W x 3 - RGB
                     H x W x 4 - RGBA
            ```
        intensity (int, optional): perspective transform 을 적용 강도(단위: 백분율)입니다.
            intensity 범위 내에서 랜덤한 값으로 샘플된 네개의 offset 을 이용해 perspective transform 을 수행합니다.
            유효 범위는 [0, 49] 로, 각 도착지 점들은 이미지의 중간 선을 지나칠 수 없습니다.
            이를 통해 이미지가 반전되는 정도의 왜곡을 방지합니다. Defaults to 0. (`fixed_aug_params=None`일 때만 작동합니다.)
        fixed_aug_params (Dict, optional): 각 꼭짓점에 대해서 offset 비율을 직접 정해주고자 할 때 사용합니다.

    Returns:
        np.ndarray: augmentation이 적용된 image 입니다.
        Dict: 각 꼭짓점에 적용된 offset들입니다.
            ```python
            {
                "offset_top_left": Tuple[float, float],
                "offset_top_right": Tuple[float, float],
                "offset_bottom_right": Tuple[float, float],
                "offset_bottom_left": Tuple[float, float],
            }
            ```

    Example1:  각 꼭짓점의 offset 비율을 직접 설정
        ```python
        error, augmented_image = ImageProcessor.perspective_transform(
            image=image,
            fixed_aug_params={
                "offset_top_left": (25.0, 30.0),
                "offset_top_right": (10.0, 40.0),
                "offset_bottom_right": (0.0, 20.0),
                "offset_bottom_left": (20.0, 35.0),
            }
        )
        ```

    Example2:  각 꼭짓점에 [0, intensity] 범위에서 랜덤하게 offset 비율을 적용
        ```python
        error, augmented_image = ImageProcessor.perspective_transform(
            image=image,
            intensity=10,
        )
        ```

    Trainer Config:
        ```python
        {
            "_target_": "perspective_transform",
            "intensity_limit": List[float],  # intensity 최소 최대 범위
        }
        ```
    """
    aug_param_keys = [
        "offset_top_left",
        "offset_top_right",
        "offset_bottom_right",
        "offset_bottom_left",
    ]
    if fixed_aug_params is None:
        check_value(intensity, 0, 49)
        fixed_aug_params = {
            k: tuple((random.uniform(0, intensity), random.uniform(0, intensity)))
            for k in aug_param_keys
        }
    else:
        for k in aug_param_keys:
            offset_x, offset_y = fixed_aug_params[k]
            check_value(offset_x, 0, 49)
            check_value(offset_y, 0, 49)

    return (
        perspective_transform(
            image=image,
            **fixed_aug_params,
        ),
        fixed_aug_params,
    )

augment_function

INTERPOLATE_METHOD_CV2 module-attribute
INTERPOLATE_METHOD_CV2 = {'nearest': INTER_NEAREST, 'linear': INTER_LINEAR, 'cubic': INTER_CUBIC}
vertical_flip
vertical_flip(image: Union[ImageType, MaskType]) -> Union[ImageType, MaskType]
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def vertical_flip(image: Union[ImageType, MaskType]) -> Union[ImageType, MaskType]:
    return AF.vflip(image)
horizontal_flip
horizontal_flip(image: Union[ImageType, MaskType]) -> Union[ImageType, MaskType]
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def horizontal_flip(image: Union[ImageType, MaskType]) -> Union[ImageType, MaskType]:
    if image.ndim == 3 and image.shape[2] > 1 and image.dtype == np.uint8:
        # Opencv is faster than numpy only in case of
        # non-gray scale 8bits images
        return AF.hflip_cv2(image)

    return AF.hflip(image)
rotate
rotate(image: Union[ImageType, MaskType], angle: float = 0, interpolation: int = cv2.INTER_LINEAR, border_mode: int = cv2.BORDER_REFLECT_101, value: Union[int, float, List[int], List[float]] = 0, crop_border: bool = False) -> Union[ImageType, MaskType]
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def rotate(
    image: Union[ImageType, MaskType],
    angle: float = 0,
    interpolation: int = cv2.INTER_LINEAR,
    border_mode: int = cv2.BORDER_REFLECT_101,
    value: Union[int, float, List[int], List[float]] = 0,
    crop_border: bool = False,
) -> Union[ImageType, MaskType]:
    def _rotated_rect_with_max_area(h, w, angle):
        """
        Given a rectangle of size wxh that has been rotated by 'angle' (in
        degrees), computes the width and height of the largest possible
        axis-aligned rectangle (maximal area) within the rotated rectangle.

        Code from: https://stackoverflow.com/questions/16702966/rotate-image-and-crop-out-black-borders
        """

        angle = math.radians(angle)
        width_is_longer = w >= h
        side_long, side_short = (w, h) if width_is_longer else (h, w)

        # since the solutions for angle, -angle and 180-angle are all the same,
        # it is sufficient to look at the first quadrant and the absolute values of sin,cos:
        sin_a, cos_a = abs(math.sin(angle)), abs(math.cos(angle))
        if side_short <= 2.0 * sin_a * cos_a * side_long or abs(sin_a - cos_a) < 1e-10:
            # half constrained case: two crop corners touch the longer side,
            # the other two corners are on the mid-line parallel to the longer line
            x = 0.5 * side_short
            wr, hr = (x / sin_a, x / cos_a) if width_is_longer else (x / cos_a, x / sin_a)
        else:
            # fully constrained case: crop touches all 4 sides
            cos_2a = cos_a * cos_a - sin_a * sin_a
            wr, hr = (w * cos_a - h * sin_a) / cos_2a, (h * cos_a - w * sin_a) / cos_2a

        return dict(
            x_min=max(0, int(w / 2 - wr / 2)),
            x_max=min(w, int(w / 2 + wr / 2)),
            y_min=max(0, int(h / 2 - hr / 2)),
            y_max=min(h, int(h / 2 + hr / 2)),
        )

    check_value(angle, -360.0, 360.0)

    img_out = AFGeometric.rotate(image, angle, interpolation, border_mode, value)
    if crop_border:
        h, w = image.shape[:2]
        crop_bbox_dict = _rotated_rect_with_max_area(h, w, angle)
        x_min = crop_bbox_dict["x_min"]
        y_min = crop_bbox_dict["y_min"]
        x_max = crop_bbox_dict["x_max"]
        y_max = crop_bbox_dict["y_max"]
        img_out = AFCrops.crop(img_out, x_min, y_min, x_max, y_max)
    return img_out
random_rotate90
random_rotate90(image: Union[ImageType, MaskType], factor: int = 0) -> Union[ImageType, MaskType]
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def random_rotate90(
    image: Union[ImageType, MaskType],
    factor: int = 0,
) -> Union[ImageType, MaskType]:
    check_value(factor, 0, 3)
    return AF.rot90(img=image, factor=factor)
color_jitter
color_jitter(image: ImageType, brightness: float = 1.0, contrast: float = 1.0, saturation: float = 1.0, hue: float = 0, order: List[int] = [0, 1, 2, 3]) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_rgba
def color_jitter(
    image: ImageType,
    brightness: float = 1.0,
    contrast: float = 1.0,
    saturation: float = 1.0,
    hue: float = 0,
    order: List[int] = [0, 1, 2, 3],
) -> ImageType:
    if not AF.is_rgb_image(image) and not AF.is_grayscale_image(image):
        raise TypeError("ColorJitter transformation expects 1-channel or 3-channel images.")

    check_value(brightness, 0.01, 10.00)
    check_value(contrast, 0.01, 10.00)
    check_value(saturation, 0.01, 10.00)
    check_value(hue, -0.50, 0.50)

    transforms = [
        AF.adjust_brightness_torchvision,
        AF.adjust_contrast_torchvision,
        AF.adjust_saturation_torchvision,
        AF.adjust_hue_torchvision,
    ]
    params = [brightness, contrast, saturation, hue]

    for i in order:
        image = transforms[i](image, params[i])
    return image
blur
blur(image: ImageType, ksize: int = 3) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def blur(image: ImageType, ksize: int = 3) -> ImageType:
    check_value(ksize, 1, 100)

    return AF.blur(image, ksize)
gaussian_blur
gaussian_blur(image: ImageType, ksize: int = 3, sigma: float = 0.0) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def gaussian_blur(image: ImageType, ksize: int = 3, sigma: float = 0.0) -> ImageType:
    check_value(ksize, 1, 100)
    check_value(sigma, 0.00, 100.00)

    if ksize % 2 != 1:
        ksize = ksize + 1

    return AF.gaussian_blur(image, ksize, sigma)
adjust_gamma
adjust_gamma(image: ImageType, gamma: float = 0.0) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def adjust_gamma(image: ImageType, gamma: float = 0.0) -> ImageType:
    check_value(gamma, -1.0, 1.0)

    gamma = gamma + 1

    return AF.gamma_transform(image, gamma=gamma)
adjust_brightness_contrast
adjust_brightness_contrast(image: ImageType, brightness: float = 0.0, contrast: float = 0.0, brightness_by_max: bool = True) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def adjust_brightness_contrast(
    image: ImageType, brightness: float = 0.0, contrast: float = 0.0, brightness_by_max: bool = True
) -> ImageType:
    check_value(brightness, -1.00, 1.00)
    check_value(contrast, -1.00, 1.00)

    alpha = 1.0 + contrast
    beta = 0.0 + brightness

    return AF.brightness_contrast_adjust(image, alpha, beta, brightness_by_max)
adjust_brightness
adjust_brightness(image: ImageType, brightness: float = 0.0, brightness_by_max: bool = True) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def adjust_brightness(
    image: ImageType, brightness: float = 0.0, brightness_by_max: bool = True
) -> ImageType:
    check_value(brightness, -1.00, 1.00)

    alpha = 1.0
    beta = 0.0 + brightness

    return AF.brightness_contrast_adjust(image, alpha, beta, brightness_by_max)
adjust_contrast
adjust_contrast(image: ImageType, contrast: float = 0.0) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def adjust_contrast(image: ImageType, contrast: float = 0.0) -> ImageType:
    check_value(contrast, -1.00, 1.00)

    alpha = 1.0 + contrast
    beta = 0.0

    return AF.brightness_contrast_adjust(image, alpha, beta)
adjust_hue
adjust_hue(image: ImageType, hue: float = 0.0) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_gray
@support_rgba
def adjust_hue(image: ImageType, hue: float = 0.0) -> ImageType:
    check_value(hue, -1.00, 1.00)

    hue_shift = int(hue * 180)
    sat_shift = 0
    val_shift = 0

    return AF.shift_hsv(image, hue_shift, sat_shift, val_shift)
adjust_saturation
adjust_saturation(image: ImageType, saturation: float = 0.0) -> ImageType

adjust_saturation

Parameters:

  • image (ImageType) –

    입력 이미지

  • saturation (float, default: 0.0 ) –

    변형 강도, [-1.0, 1.0] 범위, Defaults to 0.0.

Returns:

  • ImageType ( ImageType ) –

    결과 이미지

Note

Ablumentation의 AF.shift_hsv()와 다른 알고리즘을 사용합니다. AF.shift_hsv()의 경우 색이 없는 픽셀을 붉은 색으로 변형합니다. 이 함수의 경우 색이 없는 픽셀은 변형하지 않습니다.

Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_gray
@support_rgba
def adjust_saturation(image: ImageType, saturation: float = 0.0) -> ImageType:
    """adjust_saturation

    Args:
        image (ImageType): 입력 이미지
        saturation (float, optional): 변형 강도, [-1.0, 1.0] 범위, Defaults to 0.0.

    Returns:
        ImageType: 결과 이미지

    Note:
        Ablumentation의 AF.shift_hsv()와 다른 알고리즘을 사용합니다.
        AF.shift_hsv()의 경우 색이 없는 픽셀을 붉은 색으로 변형합니다.
        이 함수의 경우 색이 없는 픽셀은 변형하지 않습니다.

    """
    check_value(saturation, -1.00, 1.00)
    saturation = (saturation + 1) ** 2
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    result = image * saturation + gray[..., None] * (1 - saturation)

    dtype = image.dtype
    if dtype == np.uint8:
        result = np.clip(result, 0, 255)
    elif dtype == np.uint16:
        result = np.clip(result, 0, 65535)
    result = result.astype(dtype)

    return result
iso_noise
iso_noise(image: ImageType, color_shift: float = 0.05, intensity: float = 0.5, random_state: Optional[int] = None) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_gray
@support_rgba
def iso_noise(
    image: ImageType,
    color_shift: float = 0.05,
    intensity: float = 0.50,
    random_state: Optional[int] = None,
) -> ImageType:
    check_value(color_shift, 0.00, 1.00)
    check_value(intensity, 0.00, 2.00)

    return AF.iso_noise(image, color_shift, intensity, np.random.RandomState(random_state))
image_compression
image_compression(image: ImageType, quality: int = 100, image_type: str = '.jpeg') -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_gray
@support_rgba
def image_compression(
    image: ImageType,
    quality: int = 100,
    image_type: str = ".jpeg",
) -> ImageType:
    return AF.image_compression(image, quality, image_type)
sharpen
sharpen(image: ImageType, sharpening_matrix: ndarray) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_gray
@support_rgba
def sharpen(
    image: ImageType,
    sharpening_matrix: np.ndarray,
) -> ImageType:
    return AF.convolve(image, sharpening_matrix)
multiplicative_noise
multiplicative_noise(image: ImageType, multiplier: ndarray) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_gray
@support_rgba
def multiplicative_noise(
    image: ImageType,
    multiplier: np.ndarray,
) -> ImageType:
    return AF.multiply(image, multiplier)
ratio_jitter
ratio_jitter(image: ImageType, proportion_left: float = 0.0, proportion_right: float = 0.0, proportion_top: float = 0.0, proportion_bottom: float = 0.0, resampling: str = 'bilinear', border_mode: int = cv2.BORDER_CONSTANT, value: Union[int, float, List[int], List[float]] = 0) -> Union[ImageType, MaskType]
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def ratio_jitter(
    image: ImageType,
    proportion_left: float = 0.0,
    proportion_right: float = 0.0,
    proportion_top: float = 0.0,
    proportion_bottom: float = 0.0,
    resampling: str = "bilinear",
    border_mode: int = cv2.BORDER_CONSTANT,
    value: Union[int, float, List[int], List[float]] = 0,
) -> Union[ImageType, MaskType]:
    check_value(proportion_left, -0.50, 0.50)
    check_value(proportion_right, -0.50, 0.50)
    check_value(proportion_top, -0.50, 0.50)
    check_value(proportion_bottom, -0.50, 0.50)

    h_original, w_original = image.shape[:2]

    left = int(w_original * proportion_left)
    right = int(w_original * proportion_right)
    top = int(h_original * proportion_top)
    bottom = int(h_original * proportion_bottom)

    # crop
    crop_left = left if left > 0 else 0
    crop_right = right if right > 0 else 0
    crop_top = top if top > 0 else 0
    crop_bottom = bottom if bottom > 0 else 0

    crop_w = max(w_original - crop_left - crop_right, 1)
    crop_h = max(h_original - crop_top - crop_bottom, 1)

    image = image[crop_top : crop_top + crop_h, crop_left : crop_left + crop_w]

    # padding
    pad_left = 0 if left > 0 else -left
    pad_right = 0 if right > 0 else -right
    pad_top = 0 if top > 0 else -top
    pad_bottom = 0 if bottom > 0 else -bottom

    image = cv2.copyMakeBorder(
        image,
        top=pad_top,
        bottom=pad_bottom,
        left=pad_left,
        right=pad_right,
        borderType=border_mode,
        value=value,
    )

    # resize
    target_size = (w_original, h_original)
    image = resize_image(image, target_size, resampling)

    return image
_zoom_in
_zoom_in(image: ImageType, ratio: float = 1.0, h_start: float = 0.0, w_start: float = 0.0, resampling: str = 'bilinear') -> Union[ImageType, MaskType]
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def _zoom_in(
    image: ImageType,
    ratio: float = 1.0,
    h_start: float = 0.0,
    w_start: float = 0.0,
    resampling: str = "bilinear",
) -> Union[ImageType, MaskType]:
    check_value(ratio, 1.00, 100.00)
    check_value(w_start, 0.00, 1.00)
    check_value(h_start, 0.00, 1.00)

    # zoom in/out
    h_original, w_original = image.shape[:2]
    target_size = (int(w_original * ratio), int(h_original * ratio))
    image = resize_image(image, target_size, resampling)

    # crop
    image = AFCrops.random_crop(
        img=image,
        crop_height=h_original,
        crop_width=w_original,
        h_start=min(h_start, 1.0 - 1e-5),
        w_start=min(w_start, 1.0 - 1e-5),
    )

    return image
_zoom_out
_zoom_out(image: ImageType, ratio: float = 1.0, h_start: float = 0.0, w_start: float = 0.0, resampling: str = 'bilinear', border_mode: int = cv2.BORDER_CONSTANT, value: Union[int, float, List[int], List[float]] = 0) -> Union[ImageType, MaskType]
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def _zoom_out(
    image: ImageType,
    ratio: float = 1.0,
    h_start: float = 0.0,
    w_start: float = 0.0,
    resampling: str = "bilinear",
    border_mode: int = cv2.BORDER_CONSTANT,
    value: Union[int, float, List[int], List[float]] = 0,
) -> Union[ImageType, MaskType]:
    check_value(ratio, 0.01, 1.00)

    # zoom in/out
    h_original, w_original = image.shape[:2]
    target_size = (max(int(w_original * ratio), 1), max(int(h_original * ratio), 1))
    image = resize_image(image, target_size, resampling)

    # pad
    h_current, w_current = image.shape[:2]
    x1, y1, x2, y2 = AFCrops.get_random_crop_coords(
        h_original, w_original, h_current, w_current, h_start, w_start
    )
    pad_top = y1
    pad_bottom = h_original - y2
    pad_left = x1
    pad_right = w_original - x2

    image = cv2.copyMakeBorder(
        image,
        top=pad_top,
        bottom=pad_bottom,
        left=pad_left,
        right=pad_right,
        borderType=border_mode,
        value=value,
    )

    return image
zoom
zoom(image: ImageType, ratio: float = 1.0, h_start: float = 0.0, w_start: float = 0.0, resampling: str = 'bilinear', border_mode: int = cv2.BORDER_CONSTANT, value: Union[int, float, List[int], List[float]] = 0) -> Union[ImageType, MaskType]
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def zoom(
    image: ImageType,
    ratio: float = 1.0,
    h_start: float = 0.0,
    w_start: float = 0.0,
    resampling: str = "bilinear",
    border_mode: int = cv2.BORDER_CONSTANT,
    value: Union[int, float, List[int], List[float]] = 0,
) -> Union[ImageType, MaskType]:
    check_value(ratio, 0.01, 100.00)
    check_value(h_start, 0.00, 1.00)
    check_value(w_start, 0.00, 1.00)

    if ratio < 1.0:
        output_image = _zoom_out(
            image=image,
            ratio=ratio,
            h_start=h_start,
            w_start=w_start,
            resampling=resampling,
            border_mode=border_mode,
            value=value,
        )
    elif ratio == 1.0:
        output_image = image
    elif ratio > 1.0:
        output_image = _zoom_in(
            image=image,
            ratio=ratio,
            h_start=h_start,
            w_start=w_start,
            resampling=resampling,
        )
    else:
        raise NotImplementedError

    return output_image
random_resized_crop
random_resized_crop(image: ImageType, h_scale: float = 1.0, w_scale: float = 1.0, h_start: float = 0.0, w_start: float = 0.0, resampling: str = 'bilinear') -> Union[ImageType, MaskType]

Crop the given image to given scale and then resize it to original size.

Parameters:

  • image (ImageType) –

    original image

  • h_scale (float, default: 1.0 ) –

    crop height scale. Defaults to 1.0.

  • w_scale (float, default: 1.0 ) –

    crop width scale. Defaults to 1.0.

  • h_start (float, default: 0.0 ) –

    crop height start ratio. Defaults to 0.0.

  • w_start (float, default: 0.0 ) –

    crop width start ratio. Defaults to 0.0.

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

    interpolation method. Defaults to "bilinear".

Returns:

Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def random_resized_crop(
    image: ImageType,
    h_scale: float = 1.0,
    w_scale: float = 1.0,
    h_start: float = 0.0,
    w_start: float = 0.0,
    resampling: str = "bilinear",
) -> Union[ImageType, MaskType]:
    """Crop the given image to given scale and then resize it to original size.

    Args:
        image (ImageType): original image
        h_scale (float, optional): crop height scale. Defaults to 1.0.
        w_scale (float, optional): crop width scale. Defaults to 1.0.
        h_start (float, optional): crop height start ratio. Defaults to 0.0.
        w_start (float, optional): crop width start ratio. Defaults to 0.0.
        resampling (str, optional): interpolation method. Defaults to "bilinear".

    Returns:
        Union[ImageType, MaskType]: augmented data
    """
    check_value(h_scale, 0.01, 1.00)
    check_value(w_scale, 0.01, 1.00)
    check_value(h_start, 0.00, 1.00)
    check_value(w_start, 0.00, 1.00)

    h_original, w_original = image.shape[:2]
    target_height = int(round(h_original * h_scale))
    target_width = int(round(w_original * w_scale))

    # crop
    image = AFCrops.random_crop(
        img=image,
        crop_height=target_height,
        crop_width=target_width,
        h_start=min(h_start, 1.0 - 1e-5),
        w_start=min(w_start, 1.0 - 1e-5),
    )

    # resize
    target_size = (w_original, h_original)
    image = resize_image(image, target_size, resampling)

    return image
random_resized_crop_and_pad
random_resized_crop_and_pad(image: ImageType, scale: float = 1.0, aspect_ratio: float = 1.0, h_start: float = 0.0, w_start: float = 0.0, height: Optional[int] = None, width: Optional[int] = None, resampling: str = 'bilinear', border_mode: int = cv2.BORDER_CONSTANT, value: Union[int, float, List[int], List[float]] = 0) -> Union[ImageType, MaskType]
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def random_resized_crop_and_pad(
    image: ImageType,
    scale: float = 1.0,
    aspect_ratio: float = 1.0,
    h_start: float = 0.0,
    w_start: float = 0.0,
    height: Optional[int] = None,
    width: Optional[int] = None,
    resampling: str = "bilinear",
    border_mode: int = cv2.BORDER_CONSTANT,
    value: Union[int, float, List[int], List[float]] = 0,
) -> Union[ImageType, MaskType]:
    check_value(scale, 0.01, 1.00)
    check_value(aspect_ratio, 0.10, 10.00)
    check_value(h_start, 0.00, 1.00)
    check_value(w_start, 0.00, 1.00)

    h_original, w_original = image.shape[:2]
    area = h_original * w_original
    target_area = scale * area

    crop_height = int(round(math.sqrt(target_area / aspect_ratio)))
    crop_width = int(round(math.sqrt(target_area * aspect_ratio)))

    # pad
    pad_top = max(crop_height - h_original, 0)
    pad_bottom = max(crop_height - h_original, 0)
    pad_left = max(crop_width - w_original, 0)
    pad_right = max(crop_width - w_original, 0)

    image = cv2.copyMakeBorder(
        image,
        top=pad_top,
        bottom=pad_bottom,
        left=pad_left,
        right=pad_right,
        borderType=border_mode,
        value=value,
    )

    # crop
    image = AFCrops.random_crop(
        img=image,
        crop_height=crop_height,
        crop_width=crop_width,
        h_start=min(h_start, 1.0 - 1e-5),
        w_start=min(w_start, 1.0 - 1e-5),
    )

    # resize
    target_size = (width or w_original, height or h_original)
    image = resize_image(image, target_size, resampling)

    return image
light_reflect
light_reflect(image: ImageType, xc: float, yc: float, x_radius: float, y_radius: float, angle: float) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_gray
def light_reflect(
    image: ImageType, xc: float, yc: float, x_radius: float, y_radius: float, angle: float
) -> ImageType:
    check_value(xc, 0.00, 1.00)
    check_value(yc, 0.00, 1.00)
    check_value(x_radius, 0.00, 1.00)
    check_value(y_radius, 0.00, 1.00)
    check_value(angle, 0.00, 360.00)

    if x_radius == 0 or y_radius == 0:
        return image

    image = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)

    h_original, w_original = image.shape[:2]

    xc = ratio_to_value(xc, 0, w_original)
    yc = ratio_to_value(yc, 0, h_original)
    x_radius = ratio_to_value(x_radius, 0, w_original)
    y_radius = ratio_to_value(y_radius, 0, h_original)
    angle = math.radians(angle)

    c, r = np.meshgrid(np.arange(w_original), np.arange(h_original))
    x_rot = (c - xc) * math.cos(angle) + (r - yc) * math.sin(angle)
    y_rot = -(c - xc) * math.sin(angle) + (r - yc) * math.cos(angle)
    d = x_rot * x_rot / x_radius / x_radius + y_rot * y_rot / y_radius / y_radius
    k = 2 - np.power(d, 0.5)
    k[d >= 1] = 1

    image[:, :, 2] = np.clip(image[:, :, 2] * k, 0, 255).astype(np.uint8)

    image = cv2.cvtColor(image, cv2.COLOR_HSV2RGB)

    return image
perspective_transform
perspective_transform(image: ImageType, offset_top_left: OffsetType, offset_top_right: OffsetType, offset_bottom_right: OffsetType, offset_bottom_left: OffsetType, interpolate_method: str = 'nearest')
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def perspective_transform(
    image: ImageType,
    offset_top_left: OffsetType,
    offset_top_right: OffsetType,
    offset_bottom_right: OffsetType,
    offset_bottom_left: OffsetType,
    interpolate_method: str = "nearest",
):
    height, width = image.shape[:2]

    for point_offset in [
        offset_top_left,
        offset_top_right,
        offset_bottom_right,
        offset_bottom_left,
    ]:
        offset_x, offset_y = point_offset

        check_value(offset_x, 0, 49)
        check_value(offset_y, 0, 49)

    transform_matrix = calculate_transform_matrix(
        width=width,
        height=height,
        offset_top_left=offset_top_left,
        offset_top_right=offset_top_right,
        offset_bottom_right=offset_bottom_right,
        offset_bottom_left=offset_bottom_left,
    )
    image = cv2.warpPerspective(
        image, transform_matrix, (width, height), flags=INTERPOLATE_METHOD_CV2[interpolate_method]
    )

    return image
calculate_transform_matrix
calculate_transform_matrix(width: int, height: int, offset_top_left: OffsetType, offset_top_right: OffsetType, offset_bottom_right: OffsetType, offset_bottom_left: OffsetType) -> ndarray

Calculates the perspective transformation matrix using the given width, height, and corner points of a rectangle.

Parameters:

  • width (int) –

    Width of the original image.

  • height (int) –

    Height of the original image.

  • offset_top_left (OffsetType) –

    The offset ratio of the top-left corner point. Represented by the coordinates (x, y) and has a range of [0, 49]. Calculate xand y according to the procedure below.

    x` = width * offset_top_left[0]
    y` = height * offset_top_left[1]
    
  • offset_bottom_right (OffsetType) –

    The offset ratio of the bottom-right corner point. Calculate xand y according to the procedure below.

    x` = width - width * offset_bottom_right[0]
    y` = hegiht - height * offset_bottom_right[1]
    
  • offset_top_right (OffsetType) –

    The offset ratio of the top-left corner point.

  • offset_bottom_left (OffsetType) –

    The offset ratio of the bottom-left corner point.

Returns:

  • ndarray

    np.ndarray: The 4x3 perspective transform matrix.

Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def calculate_transform_matrix(
    width: int,
    height: int,
    offset_top_left: OffsetType,
    offset_top_right: OffsetType,
    offset_bottom_right: OffsetType,
    offset_bottom_left: OffsetType,
) -> np.ndarray:
    """
    Calculates the perspective transformation matrix
    using the given width, height, and corner points of a rectangle.

    Parameters:
        width (int): Width of the original image.
        height (int): Height of the original image.
        offset_top_left (OffsetType):
            The offset ratio of the top-left corner point.
            Represented by the coordinates (x, y) and has a range of [0, 49].
            Calculate x` and y` according to the procedure below.

            ```
            x` = width * offset_top_left[0]
            y` = height * offset_top_left[1]
            ```

        offset_bottom_right (OffsetType):
            The offset ratio of the bottom-right corner point.
            Calculate x` and y` according to the procedure below.

            ```
            x` = width - width * offset_bottom_right[0]
            y` = hegiht - height * offset_bottom_right[1]
            ```

        offset_top_right (OffsetType): The offset ratio of the top-left corner point.
        offset_bottom_left (OffsetType): The offset ratio of the bottom-left corner point.

    Returns:
        np.ndarray: The 4x3 perspective transform matrix.

    """
    points_from = np.float32([[0, 0], [width, 0], [width, height], [0, height]])

    point_top_left: OffsetType = (
        (offset_top_left[0] / 100) * width,
        (offset_top_left[1] / 100) * height,
    )
    point_top_right: OffsetType = (
        width - (offset_top_right[0] / 100) * width,
        (offset_top_right[1] / 100) * height,
    )
    point_bottom_right: OffsetType = (
        width - (offset_bottom_right[0] / 100) * width,
        height - (offset_bottom_right[1] / 100) * height,
    )
    point_bottom_left: OffsetType = (
        (offset_bottom_left[0] / 100) * width,
        height - (offset_bottom_left[1] / 100) * height,
    )
    points_to = np.float32(
        [
            point_top_left,
            point_top_right,
            point_bottom_right,
            point_bottom_left,
        ]
    )
    transform_matrix = cv2.getPerspectiveTransform(points_from, points_to)

    return transform_matrix
erase
erase(image: ImageType, x: int, y: int, w: int, h: int, v: ndarray)
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_gray
@support_rgba
def erase(
    image: ImageType,
    x: int,
    y: int,
    w: int,
    h: int,
    v: np.ndarray,
):
    image[y : y + h, x : x + w, ...] = v
    return image.astype("uint8")
grayscale
grayscale(image: ImageType) -> ndarray
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_gray
@support_rgba
def grayscale(
    image: ImageType,
) -> np.ndarray:
    image = AF.to_gray(image)
    return image.astype("uint8")

augment_transform

Implement ImageTransform classes for image augmentation.

ImageTransform
ImageTransform(prob: float = 0.5)

Bases: BaseTransform

Image와 라벨에 적용되는 Transform 입니다.

Source code in SaigeToolkit/data/transform/augmentation/base_transform.py
def __init__(self, prob: float = 0.5) -> None:
    self.prob = prob
    self._additional_targets = {}
targets property
targets: Dict[str, Callable]
apply_to_image
apply_to_image(image: ImageType, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, **params) -> ImageType:
    return image
apply_to_mask
apply_to_mask(mask: MaskType, **params) -> MaskType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_mask(self, mask: MaskType, **params) -> MaskType:
    return mask
apply_to_bboxes
apply_to_bboxes(bboxes: BBoxesType, **params) -> BBoxesType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_bboxes(self, bboxes: BBoxesType, **params) -> BBoxesType:
    return bboxes
apply_to_polygons
apply_to_polygons(polygons: PolygonType, **params) -> PolygonType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_polygons(self, polygons: PolygonType, **params) -> PolygonType:
    return polygons
ImageSizeParams
data_for_params property
data_for_params: List[str]
get_params_from_data
get_params_from_data(data_for_params: ParamsType) -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_params_from_data(self, data_for_params: ParamsType) -> ParamsType:
    img = data_for_params["image"]
    if not isinstance(img, list):
        return {"image_size": read_image_size(img)}
    else:
        return {"image_size": read_image_size(img[0])}
ToNumpy
ToNumpy(prob: float = 0.5)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/base_transform.py
def __init__(self, prob: float = 0.5) -> None:
    self.prob = prob
    self._additional_targets = {}
apply_to_image
apply_to_image(image: ImageType, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, **params) -> ImageType:
    return to_numpy(image=image)
apply_to_mask
apply_to_mask(mask: MaskType, **params) -> MaskType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_mask(self, mask: MaskType, **params) -> MaskType:
    return to_numpy(image=mask)
ToPil
ToPil(prob: float = 0.5)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/base_transform.py
def __init__(self, prob: float = 0.5) -> None:
    self.prob = prob
    self._additional_targets = {}
apply_to_image
apply_to_image(image: ImageType, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, **params) -> ImageType:
    return to_pil(image=image)
apply_to_mask
apply_to_mask(mask: MaskType, **params) -> MaskType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_mask(self, mask: MaskType, **params) -> MaskType:
    return to_pil(image=mask)
VerticalFlip
VerticalFlip(prob: float = 0.5)

Bases: ImageSizeParams, ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/base_transform.py
def __init__(self, prob: float = 0.5) -> None:
    self.prob = prob
    self._additional_targets = {}
apply_to_image
apply_to_image(image: ImageType, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, **params) -> ImageType:
    return vertical_flip(image=image)
apply_to_mask
apply_to_mask(mask: MaskType, **params) -> MaskType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_mask(self, mask: MaskType, **params) -> MaskType:
    return vertical_flip(image=mask)
apply_to_bboxes
apply_to_bboxes(bboxes: BBoxesType, image_size: Tuple[int], **params) -> BBoxesType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_bboxes(self, bboxes: BBoxesType, image_size: Tuple[int], **params) -> BBoxesType:
    return box_function.vflip_box(bboxes=bboxes, image_size=image_size)
apply_to_polygons
apply_to_polygons(polygons: PolygonType, image_size: Tuple[int], **params) -> PolygonType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_polygons(self, polygons: PolygonType, image_size: Tuple[int], **params) -> PolygonType:
    return polygon_function.vertical_flip(polygons=polygons, image_size=image_size)
HorizontalFlip
HorizontalFlip(prob: float = 0.5)

Bases: ImageSizeParams, ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/base_transform.py
def __init__(self, prob: float = 0.5) -> None:
    self.prob = prob
    self._additional_targets = {}
apply_to_image
apply_to_image(image: ImageType, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, **params) -> ImageType:
    return horizontal_flip(image=image)
apply_to_mask
apply_to_mask(mask: MaskType, **params) -> MaskType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_mask(self, mask: MaskType, **params) -> MaskType:
    return horizontal_flip(image=mask)
apply_to_bboxes
apply_to_bboxes(bboxes: BBoxesType, image_size: Tuple[int], **params) -> BBoxesType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_bboxes(self, bboxes: BBoxesType, image_size: Tuple[int], **params) -> BBoxesType:
    return box_function.hflip_box(bboxes=bboxes, image_size=image_size)
apply_to_polygons
apply_to_polygons(polygons: PolygonType, image_size: Tuple[int], **params) -> PolygonType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_polygons(self, polygons: PolygonType, image_size: Tuple[int], **params) -> PolygonType:
    return polygon_function.horizontal_flip(polygons=polygons, image_size=image_size)
Rotate
Rotate(angle_limit: Optional[List[float]] = None, interpolation: int = cv2.INTER_LINEAR, border_mode: int = cv2.BORDER_CONSTANT, value: Union[int, float, List[int], List[float]] = 0, mask_value: Union[int, float] = 0, crop_border: bool = False, **kwargs)

Bases: ImageSizeParams, ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    angle_limit: Optional[List[float]] = None,
    interpolation: int = cv2.INTER_LINEAR,
    border_mode: int = cv2.BORDER_CONSTANT,
    value: Union[int, float, List[int], List[float]] = 0,
    mask_value: Union[int, float] = 0,
    crop_border: bool = False,
    **kwargs,
) -> None:
    super().__init__(**kwargs)

    if angle_limit is None:
        angle_limit = [-30.0, 30.0]

    check_range(angle_limit, -360.0, 360.0)

    self.angle_limit = angle_limit
    self.interpolation = interpolation
    self.border_mode = border_mode
    self.value = value
    self.mask_value = mask_value
    self.crop_border = crop_border
angle_limit instance-attribute
angle_limit = angle_limit
interpolation instance-attribute
interpolation = interpolation
border_mode instance-attribute
border_mode = border_mode
value instance-attribute
value = value
mask_value instance-attribute
mask_value = mask_value
crop_border instance-attribute
crop_border = crop_border
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {"angle": random.uniform(*self.angle_limit)}
apply_to_image
apply_to_image(image: ImageType, angle: float = 0, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, angle: float = 0, **params) -> ImageType:
    return rotate(
        image=image,
        angle=angle,
        interpolation=self.interpolation,
        border_mode=self.border_mode,
        value=self.value,
        crop_border=self.crop_border,
    )
apply_to_mask
apply_to_mask(mask: MaskType, angle: float = 0, **params) -> MaskType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_mask(self, mask: MaskType, angle: float = 0, **params) -> MaskType:
    return rotate(
        image=mask,
        angle=angle,
        interpolation=cv2.INTER_NEAREST,
        border_mode=self.border_mode,
        value=self.mask_value,
        crop_border=self.crop_border,
    )
apply_to_bboxes
apply_to_bboxes(bboxes: BBoxesType, image_size: Tuple[int], angle: float = 0, **params) -> BBoxesType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_bboxes(
    self,
    bboxes: BBoxesType,
    image_size: Tuple[int],
    angle: float = 0,
    **params,
) -> BBoxesType:
    return box_function.rotate(bboxes=bboxes, angle=angle, image_size=image_size)
apply_to_polygons
apply_to_polygons(polygons: PolygonType, angle: float, image_size: Tuple[int], **params) -> PolygonType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_polygons(
    self,
    polygons: PolygonType,
    angle: float,
    image_size: Tuple[int],
    **params,
) -> PolygonType:
    return polygon_function.rotate(polygons=polygons, angle=angle, image_size=image_size)
RandomRotate90
RandomRotate90(**kwargs)

Bases: ImageSizeParams, ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(self, **kwargs) -> None:
    super().__init__(**kwargs)
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {"factor": random.randint(0, 3)}
apply_to_image
apply_to_image(image: ImageType, factor: int = 0, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, factor: int = 0, **params) -> ImageType:
    return random_rotate90(
        image=image,
        factor=factor,
    )
apply_to_mask
apply_to_mask(mask: MaskType, factor: int = 0, **params) -> MaskType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_mask(self, mask: MaskType, factor: int = 0, **params) -> MaskType:
    return random_rotate90(
        image=mask,
        factor=factor,
    )
apply_to_bboxes
apply_to_bboxes(bboxes: BBoxesType, image_size: Tuple[int], factor: int = 0, **params) -> BBoxesType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_bboxes(
    self,
    bboxes: BBoxesType,
    image_size: Tuple[int],
    factor: int = 0,
    **params,
) -> BBoxesType:
    return box_function.rotate90(bboxes=bboxes, factor=factor, image_size=image_size)
apply_to_polygons
apply_to_polygons(polygons: PolygonType, image_size: Tuple[int], factor: int = 0, **params) -> PolygonType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_polygons(
    self,
    polygons: PolygonType,
    image_size: Tuple[int],
    factor: int = 0,
    **params,
) -> PolygonType:
    return polygon_function.rotate90(polygons=polygons, factor=factor, image_size=image_size)
ColorJitter
ColorJitter(brightness_limit: Optional[List[float]] = None, contrast_limit: Optional[List[float]] = None, saturation_limit: Optional[List[float]] = None, hue_limit: Optional[List[float]] = None, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    brightness_limit: Optional[List[float]] = None,
    contrast_limit: Optional[List[float]] = None,
    saturation_limit: Optional[List[float]] = None,
    hue_limit: Optional[List[float]] = None,
    **kwargs,
) -> None:
    super().__init__(**kwargs)

    if brightness_limit is None:
        brightness_limit = [0.8, 1.2]
    if contrast_limit is None:
        contrast_limit = [0.8, 1.2]
    if saturation_limit is None:
        saturation_limit = [0.8, 1.2]
    if hue_limit is None:
        hue_limit = [-0.2, 0.2]

    check_range(brightness_limit, 0.01, 10.00)
    check_range(contrast_limit, 0.01, 10.00)
    check_range(saturation_limit, 0.01, 10.00)
    check_range(hue_limit, -0.50, 0.50)

    self.brightness_limit = brightness_limit
    self.contrast_limit = contrast_limit
    self.saturation_limit = saturation_limit
    self.hue_limit = hue_limit
brightness_limit instance-attribute
brightness_limit = brightness_limit
contrast_limit instance-attribute
contrast_limit = contrast_limit
saturation_limit instance-attribute
saturation_limit = saturation_limit
hue_limit instance-attribute
hue_limit = hue_limit
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    order = [0, 1, 2, 3]
    random.shuffle(order)

    return {
        "brightness": random.uniform(*self.brightness_limit),
        "contrast": random.uniform(*self.contrast_limit),
        "saturation": random.uniform(*self.saturation_limit),
        "hue": random.uniform(*self.hue_limit),
        "order": order,
    }
apply_to_image
apply_to_image(image: ImageType, brightness: float = 1.0, contrast: float = 1.0, saturation: float = 1.0, hue: float = 0, order: List[int] = [0, 1, 2, 3], **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self,
    image: ImageType,
    brightness: float = 1.0,
    contrast: float = 1.0,
    saturation: float = 1.0,
    hue: float = 0,
    order: List[int] = [0, 1, 2, 3],
    **params,
) -> ImageType:
    return color_jitter(
        image=image,
        brightness=brightness,
        contrast=contrast,
        saturation=saturation,
        hue=hue,
        order=order,
    )
Blur
Blur(ksize_limit: Optional[List[int]] = None, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(self, ksize_limit: Optional[List[int]] = None, **kwargs) -> None:
    super().__init__(**kwargs)

    if ksize_limit is None:
        ksize_limit = [3, 7]

    check_range(ksize_limit, 1, 100)

    self.ksize_limit = ksize_limit
ksize_limit instance-attribute
ksize_limit = ksize_limit
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {"ksize": random.randint(*self.ksize_limit)}
apply_to_image
apply_to_image(image: ImageType, ksize: int = 3, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, ksize: int = 3, **params) -> ImageType:
    return blur(image=image, ksize=ksize)
GaussianBlur
GaussianBlur(ksize_limit: Optional[List[int]] = None, sigma_limit: Optional[List[float]] = None, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    ksize_limit: Optional[List[int]] = None,
    sigma_limit: Optional[List[float]] = None,
    **kwargs,
) -> None:
    super().__init__(**kwargs)

    if ksize_limit is None:
        ksize_limit = [3, 7]
    if sigma_limit is None:
        sigma_limit = [0.0, 0.0]

    check_range(ksize_limit, 1, 100)
    check_range(sigma_limit, 0.00, 100.00)

    self.ksize_limit = ksize_limit
    self.sigma_limit = sigma_limit
ksize_limit instance-attribute
ksize_limit = ksize_limit
sigma_limit instance-attribute
sigma_limit = sigma_limit
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {
        "ksize": random.randint(*self.ksize_limit),
        "sigma": random.uniform(*self.sigma_limit),
    }
apply_to_image
apply_to_image(image: ImageType, ksize: int = 3, sigma: float = 0.0, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self, image: ImageType, ksize: int = 3, sigma: float = 0.0, **params
) -> ImageType:
    return gaussian_blur(image=image, ksize=ksize, sigma=sigma)
AdjustBrightness
AdjustBrightness(brightness_limit: Optional[List[float]] = None, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(self, brightness_limit: Optional[List[float]] = None, **kwargs) -> None:
    super().__init__(**kwargs)

    if brightness_limit is None:
        brightness_limit = [-0.2, 0.2]

    check_range(brightness_limit, -1.0, 1.0)

    self.brightness_limit = brightness_limit
brightness_limit instance-attribute
brightness_limit = brightness_limit
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {"brightness": random.uniform(*self.brightness_limit)}
apply_to_image
apply_to_image(image: ImageType, brightness: float = 0.0, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, brightness: float = 0.0, **params) -> ImageType:
    return adjust_brightness(image=image, brightness=brightness)
AdjustContrast
AdjustContrast(contrast_limit: Optional[List[float]] = None, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(self, contrast_limit: Optional[List[float]] = None, **kwargs) -> None:
    super().__init__(**kwargs)

    if contrast_limit is None:
        contrast_limit = [-0.2, 0.2]

    check_range(contrast_limit, -1.0, 1.0)

    self.contrast_limit = contrast_limit
contrast_limit instance-attribute
contrast_limit = contrast_limit
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {"contrast": random.uniform(*self.contrast_limit)}
apply_to_image
apply_to_image(image: ImageType, contrast: float = 0.0, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, contrast: float = 0.0, **params) -> ImageType:
    return adjust_contrast(image=image, contrast=contrast)
AdjustHue
AdjustHue(hue_limit: Optional[List[float]] = None, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(self, hue_limit: Optional[List[float]] = None, **kwargs) -> None:
    super().__init__(**kwargs)

    if hue_limit is None:
        hue_limit = [-0.2, 0.2]

    check_range(hue_limit, -1.0, 1.0)

    self.hue_limit = hue_limit
hue_limit instance-attribute
hue_limit = hue_limit
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {"hue": random.uniform(*self.hue_limit)}
apply_to_image
apply_to_image(image: ImageType, hue: float = 0.0, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, hue: float = 0.0, **params) -> ImageType:
    return adjust_hue(image=image, hue=hue)
AdjustSaturation
AdjustSaturation(saturation_limit: Optional[List[float]] = None, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(self, saturation_limit: Optional[List[float]] = None, **kwargs) -> None:
    super().__init__(**kwargs)

    if saturation_limit is None:
        saturation_limit = [-0.2, 0.2]

    check_range(saturation_limit, -1.0, 1.0)

    self.saturation_limit = saturation_limit
saturation_limit instance-attribute
saturation_limit = saturation_limit
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {"saturation": random.uniform(*self.saturation_limit)}
apply_to_image
apply_to_image(image: ImageType, saturation: float = 0.0, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, saturation: float = 0.0, **params) -> ImageType:
    return adjust_saturation(image=image, saturation=saturation)
AdjustGamma
AdjustGamma(gamma_limit: Optional[List[float]] = None, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(self, gamma_limit: Optional[List[float]] = None, **kwargs) -> None:
    super().__init__(**kwargs)

    if gamma_limit is None:
        gamma_limit = [-0.2, 0.2]

    check_range(gamma_limit, -1.0, 1.0)

    self.gamma_limit = gamma_limit
gamma_limit instance-attribute
gamma_limit = gamma_limit
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {"gamma": random.uniform(*self.gamma_limit)}
apply_to_image
apply_to_image(image: ImageType, gamma: float = 0.0, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, gamma: float = 0.0, **params) -> ImageType:
    return adjust_gamma(image=image, gamma=gamma)
AdjustBrightnessContrast
AdjustBrightnessContrast(brightness_limit: Optional[List[float]] = None, contrast_limit: Optional[List[float]] = None, brightness_by_max: bool = True, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    brightness_limit: Optional[List[float]] = None,
    contrast_limit: Optional[List[float]] = None,
    brightness_by_max: bool = True,
    **kwargs,
) -> None:
    super().__init__(**kwargs)

    if brightness_limit is None:
        brightness_limit = [-0.2, 0.2]
    if contrast_limit is None:
        contrast_limit = [-0.2, 0.2]

    check_range(brightness_limit, -1.00, 1.00)
    check_range(contrast_limit, -1.00, 1.00)

    self.brightness_limit = brightness_limit
    self.contrast_limit = contrast_limit
    self.brightness_by_max = brightness_by_max
brightness_limit instance-attribute
brightness_limit = brightness_limit
contrast_limit instance-attribute
contrast_limit = contrast_limit
brightness_by_max instance-attribute
brightness_by_max = brightness_by_max
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {
        "brightness": random.uniform(*self.brightness_limit),
        "contrast": random.uniform(*self.contrast_limit),
    }
apply_to_image
apply_to_image(image: ImageType, brightness: float = 0.0, contrast: float = 0.0, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self, image: ImageType, brightness: float = 0.0, contrast: float = 0.0, **params
) -> ImageType:
    return adjust_brightness_contrast(
        image=image,
        brightness=brightness,
        contrast=contrast,
        brightness_by_max=self.brightness_by_max,
    )
IsoNoise
IsoNoise(color_shift_limit: Optional[List[float]] = None, intensity_limit: Optional[List[float]] = None, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    color_shift_limit: Optional[List[float]] = None,
    intensity_limit: Optional[List[float]] = None,
    **kwargs,
) -> None:
    super().__init__(**kwargs)

    if color_shift_limit is None:
        color_shift_limit = [0.01, 0.05]
    if intensity_limit is None:
        intensity_limit = [0.1, 0.5]

    check_range(color_shift_limit, 0.00, 1.00)
    check_range(intensity_limit, 0.00, 2.00)

    self.color_shift_limit = color_shift_limit
    self.intensity_limit = intensity_limit
color_shift_limit instance-attribute
color_shift_limit = color_shift_limit
intensity_limit instance-attribute
intensity_limit = intensity_limit
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {
        "color_shift": random.uniform(*self.color_shift_limit),
        "intensity": random.uniform(*self.intensity_limit),
        "random_state": random.randint(0, 65536),
    }
apply_to_image
apply_to_image(image: ImageType, color_shift: float = 0.05, intensity: float = 0.5, random_state: Optional[int] = None, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self,
    image: ImageType,
    color_shift: float = 0.05,
    intensity: float = 0.50,
    random_state: Optional[int] = None,
    **params,
) -> ImageType:
    return iso_noise(
        image=image, color_shift=color_shift, intensity=intensity, random_state=random_state
    )
JpegCompression
JpegCompression(quality_limit: Optional[List[int]] = None, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    quality_limit: Optional[List[int]] = None,
    **kwargs,
) -> None:
    super().__init__(**kwargs)

    if quality_limit is None:
        quality_limit = [5, 30]

    check_range(quality_limit, 0, 100)

    self.quality_limit = quality_limit
quality_limit instance-attribute
quality_limit = quality_limit
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {"quality": random.randint(*self.quality_limit)}
apply_to_image
apply_to_image(image: ImageType, quality: int = 100) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self,
    image: ImageType,
    quality: int = 100,
) -> ImageType:
    return image_compression(image=image, quality=quality, image_type=".jpeg")
Sharpen
Sharpen(alpha_limit: Optional[List[int]] = None, lightness_limit: Optional[List[int]] = None, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    alpha_limit: Optional[List[int]] = None,
    lightness_limit: Optional[List[int]] = None,
    **kwargs,
) -> None:
    super().__init__(**kwargs)

    if alpha_limit is None:
        alpha_limit = [0.20, 0.50]

    if lightness_limit is None:
        lightness_limit = [0.80, 1.20]

    check_range(alpha_limit, 0.00, 1.00)
    check_range(lightness_limit, 0.00, 10.00)

    self.alpha_limit = alpha_limit
    self.lightness_limit = lightness_limit
alpha_limit instance-attribute
alpha_limit = alpha_limit
lightness_limit instance-attribute
lightness_limit = lightness_limit
_generate_sharpening_matrix
_generate_sharpening_matrix(alpha: float, lightness: float) -> ndarray
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def _generate_sharpening_matrix(self, alpha: float, lightness: float) -> np.ndarray:
    matrix_nochange = np.array([[0, 0, 0], [0, 1, 0], [0, 0, 0]], dtype=np.float32)
    matrix_effect = np.array(
        [[-1, -1, -1], [-1, 8 + lightness, -1], [-1, -1, -1]],
        dtype=np.float32,
    )
    matrix = (1 - alpha) * matrix_nochange + alpha * matrix_effect
    return matrix
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    sharpening_matrix = self._generate_sharpening_matrix(
        random.uniform(*self.alpha_limit), random.uniform(*self.lightness_limit)
    )
    return {"sharpening_matrix": sharpening_matrix}
apply_to_image
apply_to_image(image: ImageType, sharpening_matrix: ndarray) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self,
    image: ImageType,
    sharpening_matrix: np.ndarray,
) -> ImageType:
    return sharpen(image=image, sharpening_matrix=sharpening_matrix)
MultiplicativeNoise
MultiplicativeNoise(multiplier: Optional[List[float]] = None, per_channel: bool = True, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    multiplier: Optional[List[float]] = None,
    per_channel: bool = True,
    **kwargs,
) -> None:
    super().__init__(**kwargs)

    if multiplier is None:
        multiplier = [0.80, 1.20]

    check_range(multiplier, 0.00, 5.00)

    self.multiplier = multiplier
    self.per_channel = per_channel
multiplier instance-attribute
multiplier = multiplier
per_channel instance-attribute
per_channel = per_channel
_generate_multiplier
_generate_multiplier(image: ImageType, multiplier: ndarray, random_seed: int) -> ndarray
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def _generate_multiplier(
    self, image: ImageType, multiplier: np.ndarray, random_seed: int
) -> np.ndarray:
    if multiplier[0] == multiplier[1]:
        return np.array([multiplier[0]])

    height, width = image.shape[:2]
    num_channels = 3

    result_multiplier = np.random.RandomState(seed=random_seed).uniform(
        multiplier[0], multiplier[1], [height, width, num_channels]
    )
    return result_multiplier
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    def _shrink_range(low: int, high: int) -> List[int]:
        lower_bound = random.uniform(low, high)
        upper_bound = random.uniform(lower_bound, high)
        return [lower_bound, upper_bound]

    return {
        "multiplier": np.array(_shrink_range(*self.multiplier)),
        "random_seed": random.randint(0, 65536),
    }
apply_to_image
apply_to_image(image: ImageType, multiplier: ndarray, random_seed: int) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, multiplier: np.ndarray, random_seed: int) -> ImageType:
    _multiplier = self._generate_multiplier(image, multiplier, random_seed)
    return multiplicative_noise(image=image, multiplier=_multiplier)
RatioJitter
RatioJitter(proportion_limit: Optional[Union[List[float], int, float]] = None, resampling: str = 'bilinear', border_mode: int = cv2.BORDER_CONSTANT, value: Union[int, float, List[int], List[float]] = 0, mask_value: Union[int, float] = 0, **kwargs)

Bases: ImageSizeParams, ImageTransform

crop, pad, and resize to original image size

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    proportion_limit: Optional[Union[List[float], int, float]] = None,
    resampling: str = "bilinear",
    border_mode: int = cv2.BORDER_CONSTANT,
    value: Union[int, float, List[int], List[float]] = 0,
    mask_value: Union[int, float] = 0,
    **kwargs,
) -> None:
    super().__init__(**kwargs)

    if proportion_limit is None:
        proportion_limit = [-0.10, 0.10]

    if isinstance(proportion_limit, int):
        # NOTE: int 입력은 백분율로 적용.
        proportion_limit = [-abs(proportion_limit) / 100, abs(proportion_limit) / 100]

    if isinstance(proportion_limit, float):
        # NOTE: float 입력은 0~1 스케일로 적용.
        proportion_limit = [-abs(proportion_limit), abs(proportion_limit)]

    check_range(proportion_limit, -0.50, 0.50)

    self.proportion_limit = proportion_limit
    self.resampling = resampling
    self.border_mode = border_mode
    self.value = value
    self.mask_value = mask_value
proportion_limit instance-attribute
proportion_limit = proportion_limit
resampling instance-attribute
resampling = resampling
border_mode instance-attribute
border_mode = border_mode
value instance-attribute
value = value
mask_value instance-attribute
mask_value = mask_value
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {
        "proportion_left": random.uniform(*self.proportion_limit),
        "proportion_right": random.uniform(*self.proportion_limit),
        "proportion_top": random.uniform(*self.proportion_limit),
        "proportion_bottom": random.uniform(*self.proportion_limit),
    }
apply_to_image
apply_to_image(image: ImageType, proportion_left: float, proportion_right: float, proportion_top: float, proportion_bottom: float, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self,
    image: ImageType,
    proportion_left: float,
    proportion_right: float,
    proportion_top: float,
    proportion_bottom: float,
    **params,
) -> ImageType:
    return ratio_jitter(
        image=image,
        proportion_left=proportion_left,
        proportion_right=proportion_right,
        proportion_top=proportion_top,
        proportion_bottom=proportion_bottom,
        resampling=self.resampling,
        border_mode=self.border_mode,
        value=self.value,
    )
apply_to_mask
apply_to_mask(mask: MaskType, proportion_left: float, proportion_right: float, proportion_top: float, proportion_bottom: float, **params) -> MaskType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_mask(
    self,
    mask: MaskType,
    proportion_left: float,
    proportion_right: float,
    proportion_top: float,
    proportion_bottom: float,
    **params,
) -> MaskType:
    return ratio_jitter(
        image=mask,
        proportion_left=proportion_left,
        proportion_right=proportion_right,
        proportion_top=proportion_top,
        proportion_bottom=proportion_bottom,
        resampling="nearest",
        border_mode=self.border_mode,
        value=self.mask_value,
    )
apply_to_bboxes
apply_to_bboxes(bboxes: BBoxesType, image_size: Tuple[int], proportion_left: float, proportion_right: float, proportion_top: float, proportion_bottom: float, **params) -> BBoxesType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_bboxes(
    self,
    bboxes: BBoxesType,
    image_size: Tuple[int],
    proportion_left: float,
    proportion_right: float,
    proportion_top: float,
    proportion_bottom: float,
    **params,
) -> BBoxesType:
    return box_function.ratio_jitter(
        bboxes=bboxes,
        image_size=image_size,
        proportion_left=proportion_left,
        proportion_right=proportion_right,
        proportion_top=proportion_top,
        proportion_bottom=proportion_bottom,
    )
apply_to_polygons
apply_to_polygons(polygons: PolygonType, image_size: Tuple[int], proportion_left: float, proportion_right: float, proportion_top: float, proportion_bottom: float, **params) -> PolygonType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_polygons(
    self,
    polygons: PolygonType,
    image_size: Tuple[int],
    proportion_left: float,
    proportion_right: float,
    proportion_top: float,
    proportion_bottom: float,
    **params,
) -> PolygonType:
    return polygon_function.ratio_jitter(
        polygons=polygons,
        image_size=image_size,
        proportion_left=proportion_left,
        proportion_right=proportion_right,
        proportion_top=proportion_top,
        proportion_bottom=proportion_bottom,
    )
Zoom
Zoom(ratio_limit: Optional[List[float]] = None, resampling: str = 'bilinear', border_mode: int = cv2.BORDER_CONSTANT, value: Union[int, float, List[int], List[float]] = 0, mask_value: Union[int, float] = 0, **kwargs)

Bases: ImageSizeParams, ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    ratio_limit: Optional[List[float]] = None,
    resampling: str = "bilinear",
    border_mode: int = cv2.BORDER_CONSTANT,
    value: Union[int, float, List[int], List[float]] = 0,
    mask_value: Union[int, float] = 0,
    **kwargs,
) -> None:
    super().__init__(**kwargs)

    if ratio_limit is None:
        ratio_limit = [0.50, 2.00]

    check_range(ratio_limit, 0.01, 100.00)

    self.ratio_limit = ratio_limit
    self.resampling = resampling
    self.border_mode = border_mode
    self.value = value
    self.mask_value = mask_value
ratio_limit instance-attribute
ratio_limit = ratio_limit
resampling instance-attribute
resampling = resampling
border_mode instance-attribute
border_mode = border_mode
value instance-attribute
value = value
mask_value instance-attribute
mask_value = mask_value
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    log_ratio = (math.log(self.ratio_limit[0]), math.log(self.ratio_limit[1]))
    return {
        "ratio": math.exp(random.uniform(*log_ratio)),
        "h_start": random.random(),
        "w_start": random.random(),
    }
apply_to_image
apply_to_image(image: ImageType, ratio: float, h_start: float, w_start: float, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self,
    image: ImageType,
    ratio: float,
    h_start: float,
    w_start: float,
    **params,
) -> ImageType:
    return zoom(
        image=image,
        ratio=ratio,
        h_start=h_start,
        w_start=w_start,
        resampling=self.resampling,
        border_mode=self.border_mode,
        value=self.value,
    )
apply_to_mask
apply_to_mask(mask: MaskType, ratio: float, h_start: float, w_start: float, **params) -> MaskType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_mask(
    self,
    mask: MaskType,
    ratio: float,
    h_start: float,
    w_start: float,
    **params,
) -> MaskType:
    return zoom(
        image=mask,
        ratio=ratio,
        h_start=h_start,
        w_start=w_start,
        resampling="nearest",
        border_mode=self.border_mode,
        value=self.mask_value,
    )
apply_to_bboxes
apply_to_bboxes(bboxes: BBoxesType, image_size: Tuple[int], ratio: float, h_start: float, w_start: float, **params) -> BBoxesType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_bboxes(
    self,
    bboxes: BBoxesType,
    image_size: Tuple[int],
    ratio: float,
    h_start: float,
    w_start: float,
    **params,
) -> BBoxesType:
    return box_function.zoom(
        bboxes=bboxes,
        image_size=image_size,
        ratio=ratio,
        h_start=h_start,
        w_start=w_start,
    )
apply_to_polygons
apply_to_polygons(polygons: PolygonType, image_size: Tuple[int], ratio: float, h_start: float, w_start: float, **params) -> PolygonType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_polygons(
    self,
    polygons: PolygonType,
    image_size: Tuple[int],
    ratio: float,
    h_start: float,
    w_start: float,
    **params,
) -> PolygonType:
    return polygon_function.zoom(
        polygons=polygons,
        image_size=image_size,
        ratio=ratio,
        h_start=h_start,
        w_start=w_start,
    )
RandomResizedCrop
RandomResizedCrop(scale_limit: Optional[List[float]] = None, aspect_ratio_limit: Optional[List[float]] = None, resampling: str = 'bilinear', **kwargs)

Bases: ImageSizeParams, ImageTransform

Crop a random part of the input and rescale it to original size

Parameters:

  • scale_limit (Optional[List[float]], default: None ) –

    range of size of the origin size cropped. Defaults to [0.45, 1.00].

  • aspect_ratio_limit (Optional[List[float]], default: None ) –

    range of aspect ratio of the origin aspect ratio cropped. Defaults to [0.50, 2.00].

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

    interpolation method. Defaults to "bilinear".

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    scale_limit: Optional[List[float]] = None,
    aspect_ratio_limit: Optional[List[float]] = None,
    resampling: str = "bilinear",
    **kwargs,
) -> None:
    """
    Args:
        scale_limit (Optional[List[float]], optional): range of size of the origin size cropped. Defaults to [0.45, 1.00].
        aspect_ratio_limit (Optional[List[float]], optional): range of aspect ratio of the origin aspect ratio cropped. Defaults to [0.50, 2.00].
        resampling (str, optional): interpolation method. Defaults to "bilinear".
    """
    super().__init__(**kwargs)

    if scale_limit is None:
        scale_limit = [0.45, 1.00]
    if aspect_ratio_limit is None:
        aspect_ratio_limit = [0.50, 2.00]

    check_range(scale_limit, 0.01, 1.00)
    check_range(aspect_ratio_limit, 0.10, 10.00)

    self.scale_limit = scale_limit
    self.aspect_ratio_limit = aspect_ratio_limit
    self.resampling = resampling
scale_limit instance-attribute
scale_limit = scale_limit
aspect_ratio_limit instance-attribute
aspect_ratio_limit = aspect_ratio_limit
resampling instance-attribute
resampling = resampling
targets property
targets: Dict[str, Callable]
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    log_aspect_ratio = (math.log(self.aspect_ratio_limit[0]), math.log(self.aspect_ratio_limit[1]))
    w_scale, h_scale = 1.0, 1.0
    num_attempts = 10
    for _ in range(num_attempts):
        scale = random.uniform(*self.scale_limit)
        aspect_ratio = math.exp(random.uniform(*log_aspect_ratio))
        _w_scale = scale * math.sqrt(aspect_ratio)
        _h_scale = scale / math.sqrt(aspect_ratio)
        if _w_scale <= 1.0 and _h_scale <= 1.0:
            w_scale, h_scale = _w_scale, _h_scale
            break

    return {
        "w_scale": w_scale,
        "h_scale": h_scale,
        "h_start": random.random(),
        "w_start": random.random(),
    }
apply_to_image
apply_to_image(image: ImageType, h_scale: float, w_scale: float, h_start: float, w_start: float, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self,
    image: ImageType,
    h_scale: float,
    w_scale: float,
    h_start: float,
    w_start: float,
    **params,
) -> ImageType:
    return random_resized_crop(
        image=image,
        h_scale=h_scale,
        w_scale=w_scale,
        h_start=h_start,
        w_start=w_start,
        resampling=self.resampling,
    )
apply_to_mask
apply_to_mask(mask: MaskType, h_scale: float, w_scale: float, h_start: float, w_start: float, **params) -> MaskType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_mask(
    self,
    mask: MaskType,
    h_scale: float,
    w_scale: float,
    h_start: float,
    w_start: float,
    **params,
) -> MaskType:
    return random_resized_crop(
        image=mask,
        h_scale=h_scale,
        w_scale=w_scale,
        h_start=h_start,
        w_start=w_start,
        resampling="nearest",
    )
apply_to_bboxes
apply_to_bboxes(bboxes: BBoxesType, **params) -> BBoxesType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_bboxes(self, bboxes: BBoxesType, **params) -> BBoxesType:
    raise NotImplementedError
apply_to_polygons
apply_to_polygons(polygons: PolygonType, **params) -> PolygonType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_polygons(self, polygons: PolygonType, **params) -> PolygonType:
    raise NotImplementedError
RandomResizedCropAndPad
RandomResizedCropAndPad(scale_limit: Optional[List[float]] = None, aspect_ratio_limit: Optional[List[float]] = None, height: Optional[int] = None, width: Optional[int] = None, resampling: str = 'bilinear', border_mode: int = cv2.BORDER_CONSTANT, value: Union[int, float, List[int], List[float]] = 0, mask_value: Union[int, float] = 0, **kwargs)

Bases: ImageSizeParams, ImageTransform

pad, crop and resize to original image size

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    scale_limit: Optional[List[float]] = None,
    aspect_ratio_limit: Optional[List[float]] = None,
    height: Optional[int] = None,
    width: Optional[int] = None,
    resampling: str = "bilinear",
    border_mode: int = cv2.BORDER_CONSTANT,
    value: Union[int, float, List[int], List[float]] = 0,
    mask_value: Union[int, float] = 0,
    **kwargs,
) -> None:
    super().__init__(**kwargs)

    if scale_limit is None:
        scale_limit = [0.45, 1.00]
    if aspect_ratio_limit is None:
        aspect_ratio_limit = [0.50, 2.00]

    check_range(scale_limit, 0.01, 1.00)
    check_range(aspect_ratio_limit, 0.10, 10.00)

    self.height = height
    self.width = width
    self.scale_limit = scale_limit
    self.aspect_ratio_limit = aspect_ratio_limit
    self.resampling = resampling
    self.border_mode = border_mode
    self.value = value
    self.mask_value = mask_value
height instance-attribute
height = height
width instance-attribute
width = width
scale_limit instance-attribute
scale_limit = scale_limit
aspect_ratio_limit instance-attribute
aspect_ratio_limit = aspect_ratio_limit
resampling instance-attribute
resampling = resampling
border_mode instance-attribute
border_mode = border_mode
value instance-attribute
value = value
mask_value instance-attribute
mask_value = mask_value
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    log_aspect_ratio = (math.log(self.aspect_ratio_limit[0]), math.log(self.aspect_ratio_limit[1]))
    return {
        "scale": random.uniform(*self.scale_limit),
        "aspect_ratio": math.exp(random.uniform(*log_aspect_ratio)),
        "h_start": random.random(),
        "w_start": random.random(),
    }
apply_to_image
apply_to_image(image: ImageType, scale: float, aspect_ratio: float, h_start: float, w_start: float, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self,
    image: ImageType,
    scale: float,
    aspect_ratio: float,
    h_start: float,
    w_start: float,
    **params,
) -> ImageType:
    return random_resized_crop_and_pad(
        image=image,
        scale=scale,
        aspect_ratio=aspect_ratio,
        h_start=h_start,
        w_start=w_start,
        height=self.height,
        width=self.width,
        resampling=self.resampling,
        border_mode=self.border_mode,
        value=self.value,
    )
apply_to_mask
apply_to_mask(mask: MaskType, scale: float, aspect_ratio: float, h_start: float, w_start: float, **params) -> MaskType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_mask(
    self,
    mask: MaskType,
    scale: float,
    aspect_ratio: float,
    h_start: float,
    w_start: float,
    **params,
) -> MaskType:
    return random_resized_crop_and_pad(
        image=mask,
        scale=scale,
        aspect_ratio=aspect_ratio,
        h_start=h_start,
        w_start=w_start,
        height=self.height,
        width=self.width,
        resampling="nearest",
        border_mode=self.border_mode,
        value=self.mask_value,
    )
apply_to_bboxes
apply_to_bboxes(bboxes: BBoxesType, image_size: Tuple[int], scale: float, aspect_ratio: float, h_start: float, w_start: float, **params) -> BBoxesType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_bboxes(
    self,
    bboxes: BBoxesType,
    image_size: Tuple[int],
    scale: float,
    aspect_ratio: float,
    h_start: float,
    w_start: float,
    **params,
) -> BBoxesType:
    return box_function.random_resized_crop_and_pad(
        bboxes=bboxes,
        image_size=image_size,
        scale=scale,
        aspect_ratio=aspect_ratio,
        h_start=h_start,
        w_start=w_start,
        height=self.height,
        width=self.width,
    )
apply_to_polygons
apply_to_polygons(polygons: PolygonType, image_size: Tuple[int], scale: float, aspect_ratio: float, h_start: float, w_start: float, **params) -> PolygonType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_polygons(
    self,
    polygons: PolygonType,
    image_size: Tuple[int],
    scale: float,
    aspect_ratio: float,
    h_start: float,
    w_start: float,
    **params,
) -> PolygonType:
    return polygon_function.random_resized_crop_and_pad(
        polygons=polygons,
        image_size=image_size,
        scale=scale,
        aspect_ratio=aspect_ratio,
        h_start=h_start,
        w_start=w_start,
    )
LightReflect
LightReflect(radius_limit: Optional[List[float]] = None, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(self, radius_limit: Optional[List[float]] = None, **kwargs) -> None:
    super().__init__(**kwargs)

    if radius_limit is None:
        radius_limit = [0.10, 0.50]

    check_range(radius_limit, 0.00, 1.00)

    self.radius_limit = radius_limit
radius_limit instance-attribute
radius_limit = radius_limit
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    return {
        "xc": random.random(),
        "yc": random.random(),
        "x_radius": random.uniform(*self.radius_limit),
        "y_radius": random.uniform(*self.radius_limit),
        "angle": random.uniform(0.00, 360.00),
    }
apply_to_image
apply_to_image(image: ImageType, xc: float, yc: float, x_radius: float, y_radius: float, angle: float, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self,
    image: ImageType,
    xc: float,
    yc: float,
    x_radius: float,
    y_radius: float,
    angle: float,
    **params,
) -> ImageType:
    return light_reflect(
        image=image, xc=xc, yc=yc, x_radius=x_radius, y_radius=y_radius, angle=angle
    )
PerspectiveTransform
PerspectiveTransform(intensity_limit: Optional[List[float]] = None, **kwargs)

Bases: ImageSizeParams, ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    intensity_limit: Optional[List[float]] = None,
    **kwargs,
) -> None:
    super().__init__(**kwargs)

    if intensity_limit is None:
        intensity_limit = [0, 30]

    check_range(intensity_limit, 0, 49)

    self.intensity_limit = intensity_limit
intensity_limit instance-attribute
intensity_limit = intensity_limit
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    intensity = random.uniform(*self.intensity_limit)
    return {
        "offset_top_left": tuple((random.uniform(0, intensity), random.uniform(0, intensity))),
        "offset_top_right": tuple((random.uniform(0, intensity), random.uniform(0, intensity))),
        "offset_bottom_right": tuple((random.uniform(0, intensity), random.uniform(0, intensity))),
        "offset_bottom_left": tuple((random.uniform(0, intensity), random.uniform(0, intensity))),
    }
apply_to_image
apply_to_image(image: ImageType, offset_top_left: OffsetType, offset_top_right: OffsetType, offset_bottom_right: OffsetType, offset_bottom_left: OffsetType, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self,
    image: ImageType,
    offset_top_left: OffsetType,
    offset_top_right: OffsetType,
    offset_bottom_right: OffsetType,
    offset_bottom_left: OffsetType,
    **params,
) -> ImageType:
    return perspective_transform(
        image=image,
        offset_top_left=offset_top_left,
        offset_top_right=offset_top_right,
        offset_bottom_right=offset_bottom_right,
        offset_bottom_left=offset_bottom_left,
        interpolate_method="linear",
    )
apply_to_mask
apply_to_mask(mask: MaskType, offset_top_left: OffsetType, offset_top_right: OffsetType, offset_bottom_right: OffsetType, offset_bottom_left: OffsetType, **params) -> MaskType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_mask(
    self,
    mask: MaskType,
    offset_top_left: OffsetType,
    offset_top_right: OffsetType,
    offset_bottom_right: OffsetType,
    offset_bottom_left: OffsetType,
    **params,
) -> MaskType:
    return perspective_transform(
        image=mask,
        offset_top_left=offset_top_left,
        offset_top_right=offset_top_right,
        offset_bottom_right=offset_bottom_right,
        offset_bottom_left=offset_bottom_left,
        interpolate_method="nearest",
    )
apply_to_bboxes
apply_to_bboxes(bboxes: BBoxesType, image_size: Tuple[int], offset_top_left: OffsetType, offset_top_right: OffsetType, offset_bottom_right: OffsetType, offset_bottom_left: OffsetType, **params) -> BBoxesType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_bboxes(
    self,
    bboxes: BBoxesType,
    image_size: Tuple[int],
    offset_top_left: OffsetType,
    offset_top_right: OffsetType,
    offset_bottom_right: OffsetType,
    offset_bottom_left: OffsetType,
    **params,
) -> BBoxesType:
    return box_function.perspective_transform(
        bboxes=bboxes,
        image_size=image_size,
        offset_top_left=offset_top_left,
        offset_top_right=offset_top_right,
        offset_bottom_right=offset_bottom_right,
        offset_bottom_left=offset_bottom_left,
    )
apply_to_polygons
apply_to_polygons(polygons: PolygonType, image_size: Tuple[int], offset_top_left: OffsetType, offset_top_right: OffsetType, offset_bottom_right: OffsetType, offset_bottom_left: OffsetType, **params) -> PolygonType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_polygons(
    self,
    polygons: PolygonType,
    image_size: Tuple[int],
    offset_top_left: OffsetType,
    offset_top_right: OffsetType,
    offset_bottom_right: OffsetType,
    offset_bottom_left: OffsetType,
    **params,
) -> PolygonType:
    return polygon_function.perspective_transform(
        polygons=polygons,
        image_size=image_size,
        offset_top_left=offset_top_left,
        offset_top_right=offset_top_right,
        offset_bottom_right=offset_bottom_right,
        offset_bottom_left=offset_bottom_left,
    )
RandomErasing
RandomErasing(scale: Tuple[float, float] = (0.02, 0.33), ratio: Tuple[float, float] = (0.2, 3.3), value: Union[int, Tuple[int, int, int], str] = 0, randomly_select_values: bool = False, **kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(
    self,
    scale: Tuple[float, float] = (0.02, 0.33),
    ratio: Tuple[float, float] = (0.2, 3.3),
    value: Union[int, Tuple[int, int, int], str] = 0,
    randomly_select_values: bool = False,
    **kwargs,
) -> None:
    super().__init__(**kwargs)
    self.scale = scale
    self.ratio = ratio
    self._value = value
    self.randomly_select_values = randomly_select_values
scale instance-attribute
scale = scale
ratio instance-attribute
ratio = ratio
_value instance-attribute
_value = value
randomly_select_values instance-attribute
randomly_select_values = randomly_select_values
_get_random_erase_params
_get_random_erase_params()

This is modified version of get_params of random erasing see: https://pytorch.org/vision/main/_modules/torchvision/transforms/transforms.html#RandomErasing.forward

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def _get_random_erase_params(self):
    """
    This is modified version of get_params of random erasing
    see: https://pytorch.org/vision/main/_modules/torchvision/transforms/transforms.html#RandomErasing.forward
    """
    area = 1

    log_ratio = torch.log(torch.tensor(self.ratio))
    for _ in range(10):
        erase_area = area * torch.empty(1).uniform_(self.scale[0], self.scale[1]).item()
        aspect_ratio = torch.exp(torch.empty(1).uniform_(log_ratio[0], log_ratio[1])).item()

        h_in_ratio = math.sqrt(erase_area * aspect_ratio)
        w_in_ratio = math.sqrt(erase_area / aspect_ratio)

        if not (h_in_ratio < 1 and w_in_ratio < 1):
            continue

        x_in_ratio = random.uniform(0, 1 - w_in_ratio)
        y_in_ratio = random.uniform(0, 1 - h_in_ratio)

        return x_in_ratio, y_in_ratio, h_in_ratio, w_in_ratio

    # Return Original Image
    return 0, 0, 1.0, 1.0
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def get_apply_params(self) -> ParamsType:
    value = self._value
    if self.randomly_select_values:
        value_options = {
            "erase": 0,
            "rgb": (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)),
            "random": "random",
        }
        value = value_options[random.choice(list(value_options.keys()))]

    x_in_ratio, y_in_ratio, h_in_ratio, w_in_ratio = self._get_random_erase_params()

    return dict(
        x_in_ratio=x_in_ratio,
        y_in_ratio=y_in_ratio,
        h_in_ratio=h_in_ratio,
        w_in_ratio=w_in_ratio,
        value=value,
    )
apply_to_image
apply_to_image(image: ImageType, x_in_ratio: float, y_in_ratio: float, h_in_ratio: float, w_in_ratio: float, value: Union[int, Tuple[int, int, int], str], **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(
    self,
    image: ImageType,
    x_in_ratio: float,
    y_in_ratio: float,
    h_in_ratio: float,
    w_in_ratio: float,
    value: Union[int, Tuple[int, int, int], str],
    **params,
) -> ImageType:
    is_gray = image.ndim == 2
    if is_gray:
        img_height, img_width = image.shape
    else:
        img_height, img_width, _ = image.shape

    x = int(min(x_in_ratio * img_width, img_width))
    y = int(min(y_in_ratio * img_height, img_height))
    h = int(min(h_in_ratio * img_height, img_height))
    w = int(min(w_in_ratio * img_width, img_width))

    # cast value to script acceptable type
    if isinstance(value, (int, float)):
        value = np.array([float(value)]).astype("uint8")[None, None, :]
    elif isinstance(value, str):
        value = (np.random.normal(size=(h, w, 3)) * 255).astype("uint8")
    elif isinstance(value, (list, tuple)):
        value = np.array([float(v) for v in value]).astype("uint8")[None, None, :]

    return erase(image=image, x=x, y=y, w=w, h=h, v=value)
Grayscale
Grayscale(**kwargs)

Bases: ImageTransform

Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def __init__(self, **kwargs) -> None:
    super().__init__(**kwargs)
apply_to_image
apply_to_image(image: ImageType, **params) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_transform.py
def apply_to_image(self, image: ImageType, **params) -> ImageType:
    return grayscale(image=image)

base_transform

Data augmentation을 위한 기본 Transform의 interface를 정의합니다.

BaseTransform
BaseTransform(prob: float = 0.5)

Transform의 기본 interface를 정의합니다.

Source code in SaigeToolkit/data/transform/augmentation/base_transform.py
def __init__(self, prob: float = 0.5) -> None:
    self.prob = prob
    self._additional_targets = {}
prob instance-attribute
prob = prob
_additional_targets instance-attribute
_additional_targets = {}
data_for_params property
data_for_params: List[str]
targets property
targets: Dict[str, Callable]
__call__
__call__(*args, force_apply: bool = False, **data) -> Any
Source code in SaigeToolkit/data/transform/augmentation/base_transform.py
def __call__(self, *args, force_apply: bool = False, **data) -> Any:
    if args:
        raise KeyError("Data must be passed as named arguments, for example: aug(image=image)")

    if (random.random() < self.prob) or force_apply:
        params = {}
        if self.data_for_params:
            data_for_params = {k: data[k] for k in self.data_for_params}
            params_from_data = self.get_params_from_data(data_for_params)
            params.update(params_from_data)

        apply_param = self.get_apply_params()
        params.update(apply_param)
        data = self.apply_with_params(params, **data)

    return data
get_apply_params
get_apply_params() -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/base_transform.py
def get_apply_params(self) -> ParamsType:
    return {}
get_params_from_data
get_params_from_data(data_for_params: ParamsType) -> ParamsType

이 함수는 input으로부터 parameter들을 뽑을 때 사용됩니다.

Parameters:

  • data_for_params (ParamsType) –

    params을 추출할 데이터를 입력으로 갖습니다.

Returns:

  • ParamsType ( ParamsType ) –

    params로 쓰일 데이터를 반환합니다.

Source code in SaigeToolkit/data/transform/augmentation/base_transform.py
def get_params_from_data(self, data_for_params: ParamsType) -> ParamsType:
    """이 함수는 input으로부터 parameter들을 뽑을 때 사용됩니다.

    Args:
        data_for_params (ParamsType): params을 추출할 데이터를 입력으로 갖습니다.

    Returns:
        ParamsType: params로 쓰일 데이터를 반환합니다.
    """
    return {}
apply_with_params
apply_with_params(params: ParamsType, **data) -> ParamsType
Source code in SaigeToolkit/data/transform/augmentation/base_transform.py
def apply_with_params(self, params: ParamsType, **data) -> ParamsType:
    def _apply_with_params(method: callable, target: str, data: Dict, params: ParamsType):
        output = []

        if isinstance(data, List) and target == "image":
            is_multipage = True
        else:
            is_multipage = False
            data = [data]

        for d in data:
            output.append(method(d, **params))

        if not is_multipage:
            output = output[0]

        return output

    for target, target_method in self.targets.items():
        if target in data:
            data[target] = _apply_with_params(target_method, target, data[target], params)
    for additional_target, target in self._additional_targets.items():
        if additional_target in data:
            target_method = self.targets[target]
            data[additional_target] = _apply_with_params(
                target_method, target, data[additional_target], params
            )
    return data
add_targets
add_targets(additional_targets: Dict[str, str]) -> None
Source code in SaigeToolkit/data/transform/augmentation/base_transform.py
def add_targets(self, additional_targets: Dict[str, str]) -> None:
    self._additional_targets = additional_targets

builder

_types module-attribute
_types = {None: {__name__: _ZN4lfor _type in _types}, None: {pascal_to_snake(__name__): _JrMafor _type in _types}}
logger module-attribute
logger = getLogger('SaigeResearch')
Augmentation
Augmentation(transform)
Source code in SaigeToolkit/data/transform/augmentation/builder.py
def __init__(self, transform) -> None:
    self.to_numpy = ToNumpy()
    self.to_pil = ToPil()
    self.transform = transform
to_numpy instance-attribute
to_numpy = ToNumpy()
to_pil instance-attribute
to_pil = ToPil()
transform instance-attribute
transform = transform
__call__
__call__(**data) -> Dict
Source code in SaigeToolkit/data/transform/augmentation/builder.py
def __call__(self, **data) -> Dict:
    # XXX: image는 np.ndarray, mask는 PIL.Image.Image 인 경우 에러 발생할 수 있음
    # 현재는 해당 케이스로 사용하지 않기 때문에 해결하지 않음
    # 추후 해결 시 고려 사항: Transform의 add_targets 에서 image 혹은 mask 타입이 추가된 경우 모두 함께 처리되어야함
    pil = False
    if "image" in data:
        image = data["image"]
        if isinstance(image, List):
            image = image[0]
        pil = isinstance(image, Image.Image)

    if pil:
        data = self.to_numpy(**data, force_apply=True)

    data = self.transform(**data)

    if pil:
        data = self.to_pil(**data, force_apply=True)

    return data
build_augmentation
build_augmentation(config: Dict) -> Augmentation
Source code in SaigeToolkit/data/transform/augmentation/builder.py
def build_augmentation(config: Dict) -> Augmentation:
    logger.info(f"[AUGMENTATION]\n{pprint.pformat(config, sort_dicts=False)}")

    config = dict(config.items())
    _target_ = config.pop("_target_")
    target_class = _types[_target_]

    # Build transform
    if issubclass(target_class, ImageTransform):
        transform = target_class(**config)

    # Build compose
    elif issubclass(target_class, BaseCompose):
        transform_configs = config.pop("transforms", [])

        _transforms = []
        for transform_config in transform_configs:
            _transform = build_transform(**transform_config)
            _transforms.append(_transform)
        config["transforms"] = _transforms

        transform = build_compose(_target_, **config)

    else:
        raise NotImplementedError(f"Unsupported augmentation type: {_target_}")

    return Augmentation(transform)
build_transform
build_transform(_target_: str, **config)
Source code in SaigeToolkit/data/transform/augmentation/builder.py
def build_transform(_target_: str, **config):
    transform = _types[_target_]
    return transform(**config)

compose

여러 Data Transform들을 하나로 묶어서 관리해주는 기본 BaseCompose 클래스 및 Compose 구현을 제공합니다.

base_compose

Data augmentation을 위한 기본 Compose의 interface를 정의합니다.

BaseCompose
BaseCompose(transforms: Sequence[Union[BaseTransform, BaseCompose]], prob: float = 1.0)

여러 Data Transform들을 하나로 묶어서 관리해주는 Compose의 기본 interface를 정의합니다.

Source code in SaigeToolkit/data/transform/augmentation/compose/base_compose.py
def __init__(
    self, transforms: Sequence[Union[BaseTransform, BaseCompose]], prob: float = 1.0
) -> None:
    self.transforms = transforms
    self.prob = prob
transforms instance-attribute
transforms = transforms
prob instance-attribute
prob = prob
__call__
__call__(*args, force_apply: bool = False, **data) -> Dict
Source code in SaigeToolkit/data/transform/augmentation/compose/base_compose.py
def __call__(self, *args, force_apply: bool = False, **data) -> Dict:
    if args:
        raise KeyError("Data must be passed as named arguments, for example: aug(image=image)")

    if (random.random() < self.prob) or force_apply:
        data = self.apply(**data)

    return data
apply
apply(**data) -> Dict
Source code in SaigeToolkit/data/transform/augmentation/compose/base_compose.py
def apply(self, **data) -> Dict:
    raise NotImplementedError
__len__
__len__() -> int
Source code in SaigeToolkit/data/transform/augmentation/compose/base_compose.py
def __len__(self) -> int:
    return len(self.transforms)
__getitem__
__getitem__(index: int) -> Union[BaseTransform, BaseCompose]
Source code in SaigeToolkit/data/transform/augmentation/compose/base_compose.py
def __getitem__(self, index: int) -> Union[BaseTransform, BaseCompose]:
    return self.transforms[index]
add_targets
add_targets(additional_targets: Dict[str, str]) -> None
Source code in SaigeToolkit/data/transform/augmentation/compose/base_compose.py
def add_targets(self, additional_targets: Dict[str, str]) -> None:
    for t in self.transforms:
        t.add_targets(additional_targets)
builder
logger module-attribute
logger = getLogger('SaigeResearch')
_types module-attribute
_types = {None: {__name__: _Dw9yfor _type in _types}, None: {pascal_to_snake(__name__): _XVJCfor _type in _types}}
build_compose
build_compose(_target_: str, transforms: List[BaseTransform], **config) -> BaseCompose
Source code in SaigeToolkit/data/transform/augmentation/compose/builder.py
def build_compose(_target_: str, transforms: List[BaseTransform], **config) -> BaseCompose:
    return _types[_target_](transforms=transforms, **config)
compose
Compose
Compose(transforms: Sequence[Union[BaseTransform, BaseCompose]], prob: float = 1.0)

Bases: BaseCompose

Source code in SaigeToolkit/data/transform/augmentation/compose/base_compose.py
def __init__(
    self, transforms: Sequence[Union[BaseTransform, BaseCompose]], prob: float = 1.0
) -> None:
    self.transforms = transforms
    self.prob = prob
apply
apply(**data) -> Dict
Source code in SaigeToolkit/data/transform/augmentation/compose/compose.py
def apply(self, **data) -> Dict:
    for t in self.transforms:
        data = t(**data)

    return data
SomeOf
SomeOf(k: int, replacement: bool = False, **kwargs)

Bases: BaseCompose

Source code in SaigeToolkit/data/transform/augmentation/compose/compose.py
def __init__(self, k: int, replacement: bool = False, **kwargs) -> None:
    super().__init__(**kwargs)
    self.k = k
    self.replacement = replacement
k instance-attribute
k = k
replacement instance-attribute
replacement = replacement
apply
apply(**data) -> Dict
Source code in SaigeToolkit/data/transform/augmentation/compose/compose.py
def apply(self, **data) -> Dict:
    if self.replacement:
        cur_transforms = random.choices(self.transforms, k=self.k)
    else:
        cur_transforms = random.sample(self.transforms, k=min(len(self.transforms), self.k))

    for t in cur_transforms:
        data = t(**data)

    return data

box_function

preserve_coordinates

preserve_coordinates(func)

Box augmentation이 (left, top, right, bottom) coordinate system을 기반으로 구현 되어있기 때문에, input bboxes의 coordinate system을 확인하고 augmentation에 맞는 coordinate system으로 변환하고, augmentation이 끝나면 다시 기존 coordinate system으로 변경하여 출력합니다.

  • np.ndarray의 경우 coordinate system을 체크할 수 없기 때문에 (left, top, right, bottom) coordinate system이라고 가정합니다.

  • NumpyBBoxes의 경우 NumpyBBoxes 내부 변수 coordinate과 내부 함수 convert_coordinate를 활용하여 구현됩니다.

Source code in SaigeToolkit/data/transform/box_function.py
def preserve_coordinates(func):
    """
    Box augmentation이 (left, top, right, bottom) coordinate system을 기반으로 구현 되어있기 때문에,
    input bboxes의 coordinate system을 확인하고 augmentation에 맞는 coordinate system으로 변환하고,
    augmentation이 끝나면 다시 기존 coordinate system으로 변경하여 출력합니다.

    - np.ndarray의 경우
    coordinate system을 체크할 수 없기 때문에 (left, top, right, bottom) coordinate system이라고 가정합니다.

    - NumpyBBoxes의 경우
    NumpyBBoxes 내부 변수 coordinate과 내부 함수 convert_coordinate를 활용하여 구현됩니다.

    """
    _FUNCTIONAL_BBOX_COORDINATE = "xyxy"

    def wrapped_function_for_ndarray(bboxes: np.ndarray, *args, **kwargs) -> np.ndarray:
        new_bboxes = func(bboxes, *args, **kwargs)

        return new_bboxes

    def wrapped_function_for_numpyboxes(bboxes: NumpyBoxes, *args, **kwargs) -> NumpyBoxes:
        # original coordinate -> (left, top, right, bottom)
        original_coordinate = bboxes.coordinate  # record original coordinate
        bboxes = bboxes.convert_coordinate(_FUNCTIONAL_BBOX_COORDINATE)  # -> (left, top, right, bottom)

        # box augmentation
        new_bboxes = func(bboxes, *args, **kwargs)
        if not isinstance(new_bboxes, bboxes.__class__):
            new_bboxes = bboxes.__class__(new_bboxes, _FUNCTIONAL_BBOX_COORDINATE)

        # (left, top, right, bottom) -> original coordinate
        new_bboxes = new_bboxes.convert_coordinate(original_coordinate)

        return new_bboxes

    @wraps(func)
    def wrapped_function(bboxes: BBoxesType, *args, **kwargs) -> BBoxesType:
        # isinstance가 상속된 type도 true를 return 하기 때문에 여기서는 type(instance) == class로 체크
        if isinstance(bboxes, NumpyBoxes):
            new_bboxes = wrapped_function_for_numpyboxes(bboxes, *args, **kwargs)
        elif isinstance(bboxes, np.ndarray):
            new_bboxes = wrapped_function_for_ndarray(bboxes, *args, **kwargs)
        else:
            raise NotImplementedError

        return new_bboxes

    return wrapped_function

resize_box

resize_box(bboxes: BBoxesType, image_size: Tuple[int], tw: int, th: int) -> BBoxesType

bbox resize from (w, h) to (tw, th)

Source code in SaigeToolkit/data/transform/box_function.py
@preserve_coordinates
def resize_box(bboxes: BBoxesType, image_size: Tuple[int], tw: int, th: int) -> BBoxesType:
    """bbox resize from (w, h) to (tw, th)"""
    dtype = bboxes.dtype
    w, h = image_size
    if w == tw and h == th:
        return bboxes
    else:
        w_scale = tw / w
        h_scale = th / h
        return (bboxes * (w_scale, h_scale, w_scale, h_scale)).astype(dtype)

crop_box

crop_box(bboxes: BBoxesType, cropping_box: Union[ndarray, List[int]]) -> BBoxesType

bbox crop. An image is cropped at (new_left, new_top, new_right, new_bottom)

Source code in SaigeToolkit/data/transform/box_function.py
@preserve_coordinates
def crop_box(bboxes: BBoxesType, cropping_box: Union[np.ndarray, List[int]]) -> BBoxesType:
    """bbox crop. An image is cropped at (new_left, new_top, new_right, new_bottom)"""
    dtype = bboxes.dtype
    new_left, new_top, new_right, new_bottom = map(round, cropping_box)
    new_h = new_bottom - new_top
    new_bboxes = bboxes - (new_left, new_top, new_left, new_top)
    new_bboxes[:, [0, 1]] = np.maximum(new_bboxes[:, [0, 1]], 0)
    new_bboxes[:, [2, 3]] = np.minimum(new_bboxes[:, [2, 3]], (new_right - new_left, new_h))
    return new_bboxes.astype(dtype)

hflip_box

hflip_box(bboxes: BBoxesType, image_size: Tuple[int]) -> BBoxesType
Source code in SaigeToolkit/data/transform/box_function.py
@preserve_coordinates
def hflip_box(bboxes: BBoxesType, image_size: Tuple[int]) -> BBoxesType:
    w, h = image_size
    bboxes = bboxes.copy()
    bboxes[:, [0, 2]] = w - bboxes[:, [2, 0]]
    return bboxes

vflip_box

vflip_box(bboxes: BBoxesType, image_size: Tuple[int]) -> BBoxesType
Source code in SaigeToolkit/data/transform/box_function.py
@preserve_coordinates
def vflip_box(bboxes: BBoxesType, image_size: Tuple[int]) -> BBoxesType:
    w, h = image_size
    bboxes = bboxes.copy()
    bboxes[:, [1, 3]] = h - bboxes[:, [3, 1]]
    return bboxes

rotate

rotate(bboxes: BBoxesType, angle: Union[float, int], image_size: Tuple[int], target_size: Optional[Tuple[int]] = None, clipping: bool = True) -> BBoxesType
Source code in SaigeToolkit/data/transform/box_function.py
@preserve_coordinates
def rotate(
    bboxes: BBoxesType,
    angle: Union[float, int],
    image_size: Tuple[int],
    target_size: Optional[Tuple[int]] = None,
    clipping: bool = True,
) -> BBoxesType:
    dtype = bboxes.dtype
    w, h = image_size

    def _bboxes_to_polygons(bboxes: BBoxesType) -> PolygonType:
        polygons = []
        for bbox in bboxes:
            x1, y1, x2, y2 = bbox
            polygon = np.array([[x1, y1], [x1, y2], [x2, y2], [x2, y1]]).astype(dtype)
            polygons.append(polygon)
        return polygons

    def _polygons_to_bboxes(polygons: PolygonType) -> BBoxesType:
        bboxes = []
        for polygon in polygons:
            # Extract the bounding box from the rotated polygon
            x1, y1 = np.min(polygon, axis=0)
            x2, y2 = np.max(polygon, axis=0)
            bboxes.append([x1, y1, x2, y2])

        bboxes = np.array(bboxes).reshape(-1, 4)
        return bboxes

    if bboxes.size == 0:
        return bboxes

    polygons = _bboxes_to_polygons(bboxes)
    rotated_polygons = rotate_polygons(
        polygons=polygons, angle=angle, image_size=image_size, target_size=target_size
    )
    rotated_bboxes = _polygons_to_bboxes(rotated_polygons)

    if clipping:
        rotated_bboxes[:, [0, 2]] = np.clip(rotated_bboxes[:, [0, 2]], 0, w - 1)
        rotated_bboxes[:, [1, 3]] = np.clip(rotated_bboxes[:, [1, 3]], 0, h - 1)
    return rotated_bboxes.astype(dtype)

rotate90

rotate90(bboxes: BBoxesType, factor: int, image_size: Tuple[int], clipping: bool = True) -> BBoxesType
Source code in SaigeToolkit/data/transform/box_function.py
@preserve_coordinates
def rotate90(
    bboxes: BBoxesType,
    factor: int,
    image_size: Tuple[int],
    clipping: bool = True,
) -> BBoxesType:
    angle = factor * 90.0
    if factor % 2 == 0:
        target_size = image_size
    else:
        target_size = (image_size[1], image_size[0])
    return rotate(
        bboxes=bboxes, angle=angle, image_size=image_size, target_size=target_size, clipping=clipping
    )

translate_box

translate_box(bboxes: BBoxesType, offset: Tuple[int], image_size: Tuple[int]) -> BBoxesType
Source code in SaigeToolkit/data/transform/box_function.py
@preserve_coordinates
def translate_box(bboxes: BBoxesType, offset: Tuple[int], image_size: Tuple[int]) -> BBoxesType:
    w, h = image_size
    x_offset, y_offset = offset
    new_bboxes = bboxes.copy()
    new_bboxes[:, 0] = np.maximum(np.minimum(bboxes[:, 0] - x_offset, w), 0)
    new_bboxes[:, 1] = np.maximum(np.minimum(bboxes[:, 1] - y_offset, h), 0)
    new_bboxes[:, 2] = np.minimum(np.maximum(bboxes[:, 2] - x_offset, 0), w)
    new_bboxes[:, 3] = np.minimum(np.maximum(bboxes[:, 3] - y_offset, 0), h)
    return new_bboxes

ratio_jitter

ratio_jitter(bboxes: BBoxesType, image_size: Tuple[int], proportion_left: float = 0.0, proportion_right: float = 0.0, proportion_top: float = 0.0, proportion_bottom: float = 0.0) -> BBoxesType
Source code in SaigeToolkit/data/transform/box_function.py
@preserve_coordinates
def ratio_jitter(
    bboxes: BBoxesType,
    image_size: Tuple[int],
    proportion_left: float = 0.0,
    proportion_right: float = 0.0,
    proportion_top: float = 0.0,
    proportion_bottom: float = 0.0,
) -> BBoxesType:
    w, h = image_size

    left = int(w * proportion_left)
    right = int(w * proportion_right)
    top = int(h * proportion_top)
    bottom = int(h * proportion_bottom)

    # Crop
    crop_left = left if left > 0 else 0
    crop_right = right if right > 0 else 0
    crop_top = top if top > 0 else 0
    crop_bottom = bottom if bottom > 0 else 0

    crop_w = max(w - crop_left - crop_right, 1)
    crop_h = max(h - crop_top - crop_bottom, 1)

    bboxes = crop_box(bboxes, [crop_left, crop_top, crop_left + crop_w, crop_top + crop_h])

    # Padding
    pad_left = 0 if left > 0 else -left
    pad_right = 0 if right > 0 else -right
    pad_top = 0 if top > 0 else -top
    pad_bottom = 0 if bottom > 0 else -bottom

    bboxes = resize_box(bboxes, (crop_w + pad_left + pad_right, crop_h + pad_top + pad_bottom), w, h)

    # Translate
    w_scale = (crop_w + pad_left + pad_right) / w
    h_scale = (crop_h + pad_top + pad_bottom) / h
    bboxes = translate_box(bboxes, [-pad_left / w_scale, -pad_top / h_scale], image_size)

    return bboxes

zoom

zoom(bboxes: BBoxesType, image_size: Tuple[int], ratio: float = 1.0, h_start: float = 0.0, w_start: float = 0.0) -> BBoxesType
Source code in SaigeToolkit/data/transform/box_function.py
@preserve_coordinates
def zoom(
    bboxes: BBoxesType,
    image_size: Tuple[int],
    ratio: float = 1.0,
    h_start: float = 0.0,
    w_start: float = 0.0,
) -> BBoxesType:
    w, h = image_size
    if ratio == 1.0:
        return bboxes
    target_size = (int(w * ratio), int(h * ratio))
    bboxes = resize_box(bboxes, image_size, target_size[0], target_size[1])
    if ratio > 1.0:
        h_start = min(h_start, 1.0 - 1e-5)
        w_start = min(w_start, 1.0 - 1e-5)
        x1 = int((target_size[0] - w + 1) * w_start)
        y1 = int((target_size[1] - h + 1) * h_start)
        bboxes = crop_box(bboxes, [x1, y1, x1 + w, y1 + h])
    elif ratio < 1.0:
        x1 = int((w - target_size[0] + 1) * w_start)
        y1 = int((h - target_size[1] + 1) * h_start)
        bboxes = translate_box(bboxes, [-x1, -y1], image_size)

    return bboxes

random_resized_crop_and_pad

random_resized_crop_and_pad(bboxes: BBoxesType, image_size: Tuple[int], scale: float = 1.0, aspect_ratio: float = 1.0, h_start: float = 0.0, w_start: float = 0.0, height: Optional[int] = None, width: Optional[int] = None) -> BBoxesType
Source code in SaigeToolkit/data/transform/box_function.py
@preserve_coordinates
def random_resized_crop_and_pad(
    bboxes: BBoxesType,
    image_size: Tuple[int],
    scale: float = 1.0,
    aspect_ratio: float = 1.0,
    h_start: float = 0.0,
    w_start: float = 0.0,
    height: Optional[int] = None,
    width: Optional[int] = None,
) -> BBoxesType:
    w_original, h_original = image_size
    area = h_original * w_original
    target_area = scale * area

    crop_height = int(round(math.sqrt(target_area / aspect_ratio)))
    crop_width = int(round(math.sqrt(target_area * aspect_ratio)))

    w_start = min(w_start, 1.0 - 1e-5)
    h_start = min(h_start, 1.0 - 1e-5)

    # Padding
    pad_top = max(crop_height - h_original, 0)
    pad_bottom = max(crop_height - h_original, 0)
    pad_left = max(crop_width - w_original, 0)
    pad_right = max(crop_width - w_original, 0)

    padded_image_size = [w_original + pad_left + pad_right, h_original + pad_top + pad_bottom]
    bboxes = translate_box(bboxes, [-pad_left, -pad_top], padded_image_size)

    # Crop
    x1 = int((padded_image_size[0] - crop_width + 1) * w_start)
    y1 = int((padded_image_size[1] - crop_height + 1) * h_start)
    bboxes = crop_box(bboxes, [x1, y1, x1 + crop_width, y1 + crop_height])

    # Resize
    bboxes = resize_box(
        bboxes,
        [crop_width, crop_height],
        width or w_original,
        height or h_original,
    )

    return bboxes

perspective_transform

perspective_transform(bboxes: BBoxesType, image_size: Tuple[int], offset_top_left: OffsetType, offset_top_right: OffsetType, offset_bottom_right: OffsetType, offset_bottom_left: OffsetType) -> BBoxesType
Source code in SaigeToolkit/data/transform/box_function.py
@preserve_coordinates
def perspective_transform(
    bboxes: BBoxesType,
    image_size: Tuple[int],
    offset_top_left: OffsetType,
    offset_top_right: OffsetType,
    offset_bottom_right: OffsetType,
    offset_bottom_left: OffsetType,
) -> BBoxesType:
    width, height = image_size

    for point_offset in [
        offset_top_left,
        offset_top_right,
        offset_bottom_right,
        offset_bottom_left,
    ]:
        offset_x, offset_y = point_offset

        check_value(offset_x, 0, 49)
        check_value(offset_y, 0, 49)

    transform_matrix = calculate_transform_matrix(
        width=width,
        height=height,
        offset_top_left=offset_top_left,
        offset_top_right=offset_top_right,
        offset_bottom_right=offset_bottom_right,
        offset_bottom_left=offset_bottom_left,
    )

    new_bboxes = bboxes.copy()
    for idx_box, bbox in enumerate(bboxes):
        # [(x1, y1), (x2, y1), (x2, y2), (x1, y2)]
        points = [(bbox[0], bbox[1]), (bbox[2], bbox[1]), (bbox[2], bbox[3]), (bbox[0], bbox[3])]

        dtype_max_value = np.iinfo(np.int32).max
        min_x, min_y = dtype_max_value, dtype_max_value
        max_x, max_y = 0, 0

        for point in points:
            x, y, scaling_factor = np.dot(transform_matrix, np.append(point, 1))
            x = int(x / scaling_factor)
            y = int(y / scaling_factor)

            if x < min_x:
                min_x = x

            elif x > max_x:
                max_x = x

            if y < min_y:
                min_y = y

            elif y > max_y:
                max_y = y

        new_bboxes[idx_box] = [min_x, min_y, max_x, max_y]

    return new_bboxes

function_util

Define utility functions for data augmentation.

check_value

check_value(data: Union[int, float], min_value: Union[int, float], max_value: Union[int, float])
Source code in SaigeToolkit/data/transform/function_util.py
def check_value(data: Union[int, float], min_value: Union[int, float], max_value: Union[int, float]):
    if not (isinstance(data, (int, float))):
        raise AugmentationParameterTypeError

    if not (min_value <= data <= max_value):
        raise AugmentationParameterRangeError

check_range

check_range(data: Union[Sequence[int], Sequence[float]], min_value: Union[int, float], max_value: Union[int, float])
Source code in SaigeToolkit/data/transform/function_util.py
def check_range(
    data: Union[Sequence[int], Sequence[float]],
    min_value: Union[int, float],
    max_value: Union[int, float],
):
    if not (isinstance(data, Sequence) and len(data) == 2):
        raise AugmentationParameterTypeError

    check_value(data[0], min_value, max_value)
    check_value(data[1], min_value, max_value)

    if not (min_value <= data[0] <= data[1] <= max_value):
        raise AugmentationParameterRangeError

ratio_to_value

ratio_to_value(ratio: float, min_value: Union[int, float], max_value: Union[int, float])
Source code in SaigeToolkit/data/transform/function_util.py
def ratio_to_value(ratio: float, min_value: Union[int, float], max_value: Union[int, float]):
    check_value(ratio, 0.0, 1.0)

    min_value, max_value = min(min_value, max_value), max(min_value, max_value)

    return (ratio * (max_value - min_value)) + min_value

support_gray

support_gray(function)
Source code in SaigeToolkit/data/transform/function_util.py
def support_gray(function):
    @wraps(function)
    def wrapper(image: ImageType, *args, **kwargs):
        is_gray = image.ndim == 2
        if is_gray:
            image = np.stack([image, image, image], axis=-1)

        image = function(image, *args, **kwargs)

        if is_gray:
            image = image[:, :, 0]

        return image

    return wrapper

support_rgba

support_rgba(function)
Source code in SaigeToolkit/data/transform/function_util.py
def support_rgba(function):
    @wraps(function)
    def wrapper(image: ImageType, *args, **kwargs):
        is_rgba = image.ndim == 3 and image.shape[2] == 4
        if is_rgba:
            mask = image[:, :, 3:4]
            image = image[:, :, :3]

        image = function(image, *args, **kwargs)

        if is_rgba:
            image = np.concatenate([image, mask], axis=2)

        return image

    return wrapper

support_3dim_gray_image

support_3dim_gray_image(function)
Source code in SaigeToolkit/data/transform/function_util.py
def support_3dim_gray_image(function):
    @wraps(function)
    def decorator(image: ImageType, *args, **kwargs) -> Any:
        is_3dim_gray = image.ndim == 3 and image.shape[2] == 1
        if is_3dim_gray:
            image = image[:, :, 0]

        result = function(image=image, *args, **kwargs)

        if is_3dim_gray:
            result = result[:, :, np.newaxis]
        return result

    return decorator

support_multi_image

support_multi_image(function)
Source code in SaigeToolkit/data/transform/function_util.py
def support_multi_image(function):
    @wraps(function)
    def decorator(image: Union[List[ImageType], ImageType], *args, **kwargs) -> Any:
        multipage = isinstance(image, List)
        if not multipage:
            image = [image]

        results = [function(image=image_i, *args, **kwargs) for image_i in image]

        if not multipage:
            results = results[0]
        return results

    return decorator

image_function

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

image_load

ImageLoader

ImageLoader(image_mode: Union[str, List[str]] = 'RGB', to_numpy: bool = True)

여러 형태의 데이터를 입력으로 받아, 전처리 과정을 거쳐 PIL Image 혹은 np.ndarray 로 return 합니다. 현재 지원하는 데이터는 다음과 같습니다. - 데이터 타입: [image path, np.ndarray, PIL Image] - color: ["RGB", "Gray"] - bit: [8, 16]

입력으로 받은 데이터에 따른 출력값은 다음과 같습니다. | image path | np.ndarray | PIL | RGB; 8 | PIL(RGB;8) | PIL(RGB;8) | PIL(RGB;8) | RGB;16 | PIL(RGB;8) | PIL(RGB;8) | - | Gray; 8 | PIL(L;8) | PIL(L;8) | PIL(L;8) | Gray;16 | PIL(L;8) | PIL(L;8) | PIL(L;8) |

Note1

PIL은 RGB;16을 지원하지 않습니다. 따라서 PIL(RGB;16)은 입력으로 들어올 수 없습니다.

Note2

PIL은 "I;16" 모드로 Gray;16을 지원하지만, PIL 내부의 convert 함수를 써서 Gray;8로 변환시 값이 overflow 나는 issue가 있습니다.

Note3

이미지는 기본적으로 np.ndarray로 로드되며, PIL.Image.Image로 로드하려면 to_numpy=False를 세팅하세요.

Note4

image_mode는 "RGB", "L" 중 하나를 지원하며, multipage (이미지 리스트) 인 경우 각 페이지의 모드를 리스트로 입력하세요. 예시: 1, 3번째 페이지는 RGB 이고 2번째 페이지는 L 인 경우 image_mode = ["RGB", "L", "RGB"]

Source code in SaigeToolkit/data/transform/image_load.py
def __init__(self, image_mode: Union[str, List[str]] = "RGB", to_numpy: bool = True) -> None:
    if isinstance(image_mode, str):
        image_mode = [image_mode]
    elif not set(image_mode).issubset(set(IMAGE_MODES)):
        raise ValueError
    self.image_mode = image_mode
    self.to_numpy = to_numpy
image_mode instance-attribute
image_mode = image_mode
to_numpy instance-attribute
to_numpy = to_numpy
__call__
__call__(image: Union[Image, ndarray, str, List], **data) -> Dict
Source code in SaigeToolkit/data/transform/image_load.py
def __call__(self, image: Union[Image.Image, np.ndarray, str, List], **data) -> Dict:
    return self.call_method(image=image, **data)
call_method
call_method(image: Union[Image, ndarray, str, List], **data) -> Dict

make [PIL Image or np.ndarray] from PIL.Image, np.ndarray, or path string

Parameters:

  • image (Union[Image, ndarray, str, List]) –

    PIL.Image, array, path string or list of it.

Returns:

  • Dict ( Dict ) –

    { "image": Union[PIL.Image.Image, np.ndarray, List[PIL.Image.Image], List[np.ndarray]], **input_dict, }

Source code in SaigeToolkit/data/transform/image_load.py
def call_method(self, image: Union[Image.Image, np.ndarray, str, List], **data) -> Dict:
    """make [PIL Image or np.ndarray] from PIL.Image, np.ndarray, or path string

    Args:
        image (Union[Image.Image, np.ndarray, str, List]): PIL.Image, array, path string or list of it.

    Returns:
        Dict:
            {
                "image": Union[PIL.Image.Image, np.ndarray, List[PIL.Image.Image], List[np.ndarray]],
                **input_dict,
            }
    """
    multipage = isinstance(image, List)
    if not multipage:
        image = [image]

    if len(image) != len(self.image_mode):
        raise ValueError("number of images should match len(image_mode)")

    # 이미지들을 각자의 모드로 로드 (RGB 또는 L).
    loaded_images = []
    for image_i, image_mode_i in zip(image, self.image_mode):
        image_i = self.load(image_i)
        image_i = convert_image_mode(image=image_i, mode=image_mode_i, copy=False)
        loaded_images.append(image_i)

    if multipage:
        # multipage 이미지들의 사이즈가 모두 동일해야함.
        image_sizes = set(read_image_size(item) for item in loaded_images)
        if len(image_sizes) > 1:
            raise ValueError("sizef of images in multipage should be identical")
    else:
        loaded_images = loaded_images[0]

    data["image"] = loaded_images
    return data
load
load(image: Union[Image, ndarray, str]) -> Union[Image, ndarray]
Source code in SaigeToolkit/data/transform/image_load.py
def load(self, image: Union[Image.Image, np.ndarray, str]) -> Union[Image.Image, np.ndarray]:
    if isinstance(image, Image.Image):
        image = self.load_from_pil(image)
    elif isinstance(image, np.ndarray):
        image = self.load_from_array(image)
    elif isinstance(image, str):
        image = self.load_from_path(image)
    return image
load_from_path
load_from_path(path: str) -> Union[Image, ndarray]

Loading function using PIL.Image library. This function is introduced because {exif_transpose} must be processed. {exif_transpose} fix rotated image binary data using {EXIF} information. Without {exif_transpose}, network would accidently learn randomly rotated image data.

Parameters:

  • path (str) –

    path to image file

Returns:

  • Union[Image, ndarray]

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

Source code in SaigeToolkit/data/transform/image_load.py
def load_from_path(self, path: str) -> Union[Image.Image, np.ndarray]:
    """Loading function using PIL.Image library.
    This function is introduced because {exif_transpose} must be processed.
    {exif_transpose} fix rotated image binary data using {EXIF} information.
    Without {exif_transpose}, network would accidently learn randomly rotated image data.

    Args:
        path (str): path to image file

    Returns:
        Union[Image.Image, np.ndarray]: image
    """
    with open(path, "rb") as f:
        image = Image.open(f)
        image = ImageOps.exif_transpose(image)

    if self.to_numpy:
        image = np.array(image, dtype=np.uint16 if self._is_16bit(image) else np.uint8)

    if self._is_16bit(image):
        image = self._convert_16_to_8(image)

    return image
load_from_array
load_from_array(array: ndarray) -> Union[Image, ndarray]
Source code in SaigeToolkit/data/transform/image_load.py
def load_from_array(self, array: np.ndarray) -> Union[Image.Image, np.ndarray]:
    if array.ndim == 3 and array.shape[-1] == 1:  # grayscale shape HW1 -> HW
        array = np.squeeze(array, axis=2)

    if self._is_16bit(array):
        array = self._convert_16_to_8(array)

    if self.to_numpy:
        out = array
    else:
        out = Image.fromarray(array)

    return out
load_from_pil
load_from_pil(image: Image) -> Union[Image, ndarray]
Source code in SaigeToolkit/data/transform/image_load.py
def load_from_pil(self, image: Image.Image) -> Union[Image.Image, np.ndarray]:
    if self.to_numpy:
        image = np.array(image, dtype=np.uint16 if self._is_16bit(image) else np.uint8)

    if self._is_16bit(image):
        image = self._convert_16_to_8(image)

    return image
_convert_16_to_8 staticmethod
_convert_16_to_8(image: Union[Image, ndarray]) -> Union[Image, ndarray]

이미지 픽셀당 비트수를 16에서 8로 변화합니다. 입출력 데이터 타입이 같습니다. (pil로 받으면 pil을 ndarray로 받을 시 ndarray를 출력합니다.)

Source code in SaigeToolkit/data/transform/image_load.py
@staticmethod
def _convert_16_to_8(image: Union[Image.Image, np.ndarray]) -> Union[Image.Image, np.ndarray]:
    """
    이미지 픽셀당 비트수를 16에서 8로 변화합니다.
    입출력 데이터 타입이 같습니다. (pil로 받으면 pil을 ndarray로 받을 시 ndarray를 출력합니다.)
    """

    def _convert_16_to_8_np(array: np.ndarray) -> np.ndarray:
        return (array >> 8).astype(np.uint8)

    if isinstance(image, Image.Image):
        assert "I" in image.mode
        array_16bit = np.array(image, dtype=np.uint16)
        array_8bit = _convert_16_to_8_np(array_16bit)
        ret_image = Image.fromarray(array_8bit)
    elif isinstance(image, np.ndarray):
        assert image.dtype == np.uint16
        ret_image = _convert_16_to_8_np(image)
    else:
        raise NotImplementedError

    return ret_image
_is_16bit staticmethod
_is_16bit(image: Union[Image, ndarray]) -> bool
Source code in SaigeToolkit/data/transform/image_load.py
@staticmethod
def _is_16bit(image: Union[Image.Image, np.ndarray]) -> bool:
    if isinstance(image, Image.Image) and "I" in image.mode:
        return True
    elif isinstance(image, np.ndarray) and image.dtype == np.uint16:
        return True
    return False

polygon_function

vertical_flip

vertical_flip(polygons: PolygonType, image_size: Tuple[int]) -> PolygonType
Source code in SaigeToolkit/data/transform/polygon_function.py
def vertical_flip(polygons: PolygonType, image_size: Tuple[int]) -> PolygonType:
    _, h = image_size
    polygons = deepcopy(polygons)
    for polygon in polygons:
        polygon[:, 1] = h - polygon[:, 1]
    return polygons

horizontal_flip

horizontal_flip(polygons: PolygonType, image_size: Tuple[int]) -> PolygonType
Source code in SaigeToolkit/data/transform/polygon_function.py
def horizontal_flip(polygons: PolygonType, image_size: Tuple[int]) -> PolygonType:
    w, _ = image_size
    polygons = deepcopy(polygons)
    for polygon in polygons:
        polygon[:, 0] = w - polygon[:, 0]
    return polygons

rotate

rotate(polygons: PolygonType, angle: float, image_size: Tuple[int], target_size: Optional[Tuple[int]] = None) -> PolygonType
Source code in SaigeToolkit/data/transform/polygon_function.py
def rotate(
    polygons: PolygonType,
    angle: float,
    image_size: Tuple[int],
    target_size: Optional[Tuple[int]] = None,
) -> PolygonType:
    if len(polygons) == 0:
        return polygons

    dtype = polygons[0].dtype
    w, h = image_size

    anchor_origin = np.array([w // 2, h // 2]).astype(dtype)

    if target_size is None:
        anchor_target = anchor_origin
    else:
        anchor_target = np.array([target_size[0] // 2, target_size[1] // 2]).astype(dtype)

    theta = np.deg2rad(angle)
    rotation_matrix = [[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]

    rotated_polygons = []
    for polygon in polygons:
        # Translate polygon so that the rotation point is at the origin
        rotated_polygon = polygon - anchor_origin

        # Rotate the polygon
        rotated_polygon = np.dot(rotated_polygon, rotation_matrix)

        # Translate the polygon back
        rotated_polygon += anchor_target

        rotated_polygons.append(rotated_polygon)

    return rotated_polygons

rotate90

rotate90(polygons: PolygonType, factor: int, image_size: Tuple[int]) -> PolygonType
Source code in SaigeToolkit/data/transform/polygon_function.py
def rotate90(
    polygons: PolygonType,
    factor: int,
    image_size: Tuple[int],
) -> PolygonType:
    angle = factor * 90.0
    if factor % 2 == 0:
        target_size = image_size
    else:
        target_size = (image_size[1], image_size[0])
    return rotate(polygons=polygons, angle=angle, image_size=image_size, target_size=target_size)

ratio_jitter

ratio_jitter(polygons: PolygonType, image_size: Tuple[int], proportion_left: float, proportion_right: float, proportion_top: float, proportion_bottom: float) -> PolygonType
Source code in SaigeToolkit/data/transform/polygon_function.py
def ratio_jitter(
    polygons: PolygonType,
    image_size: Tuple[int],
    proportion_left: float,
    proportion_right: float,
    proportion_top: float,
    proportion_bottom: float,
) -> PolygonType:
    w, h = image_size

    left = int(w * proportion_left)
    right = int(w * proportion_right)
    top = int(h * proportion_top)
    bottom = int(h * proportion_bottom)

    # Crop
    crop_left = left if left > 0 else 0
    crop_right = right if right > 0 else 0
    crop_top = top if top > 0 else 0
    crop_bottom = bottom if bottom > 0 else 0

    crop_w = max(w - crop_left - crop_right, 1)
    crop_h = max(h - crop_top - crop_bottom, 1)
    polygons = translate_polygon(polygons, [crop_left, crop_top])

    # Padding
    pad_left = 0 if left > 0 else -left
    pad_right = 0 if right > 0 else -right
    pad_top = 0 if top > 0 else -top
    pad_bottom = 0 if bottom > 0 else -bottom

    polygons = resize_polygon(
        polygons, (crop_w + pad_left + pad_right, crop_h + pad_top + pad_bottom), w, h
    )

    # Translate
    w_scale = (crop_w + pad_left + pad_right) / w
    h_scale = (crop_h + pad_top + pad_bottom) / h
    polygons = translate_polygon(polygons, [-int(pad_left / w_scale), -int(pad_top / h_scale)])

    return polygons

zoom

zoom(polygons: PolygonType, image_size: Tuple[int], ratio: float, h_start: float, w_start: float) -> PolygonType
Source code in SaigeToolkit/data/transform/polygon_function.py
def zoom(
    polygons: PolygonType,
    image_size: Tuple[int],
    ratio: float,
    h_start: float,
    w_start: float,
) -> PolygonType:
    w, h = image_size

    if ratio == 1.0:
        return polygons

    target_size = (int(w * ratio), int(h * ratio))
    polygons = resize_polygon(polygons, image_size, target_size[0], target_size[1])

    x1 = 0
    y1 = 0

    if ratio > 1.0:
        h_start = min(h_start, 1.0 - 1e-5)
        w_start = min(w_start, 1.0 - 1e-5)
        x1 = int((target_size[0] - w + 1) * w_start)
        y1 = int((target_size[1] - h + 1) * h_start)

    elif ratio < 1.0:
        x1 = -int((w - target_size[0] + 1) * w_start)
        y1 = -int((h - target_size[1] + 1) * h_start)

    polygons = translate_polygon(polygons, [x1, y1])

    return polygons

random_resized_crop_and_pad

random_resized_crop_and_pad(polygons: PolygonType, image_size: Tuple[int], scale: float, aspect_ratio: float, h_start: float, w_start: float, h_target: Optional[int] = None, w_target: Optional[int] = None, **params) -> PolygonType
Source code in SaigeToolkit/data/transform/polygon_function.py
def random_resized_crop_and_pad(
    polygons: PolygonType,
    image_size: Tuple[int],
    scale: float,
    aspect_ratio: float,
    h_start: float,
    w_start: float,
    h_target: Optional[int] = None,
    w_target: Optional[int] = None,
    **params
) -> PolygonType:
    w_original, h_original = image_size
    area = h_original * w_original
    target_area = scale * area

    crop_height = int(round(math.sqrt(target_area / aspect_ratio)))
    crop_width = int(round(math.sqrt(target_area * aspect_ratio)))

    # Padding
    pad_top = max(crop_height - h_original, 0)
    pad_bottom = max(crop_height - h_original, 0)
    pad_left = max(crop_width - w_original, 0)
    pad_right = max(crop_width - w_original, 0)

    padded_image_size = [w_original + pad_left + pad_right, h_original + pad_top + pad_bottom]
    polygons = translate_polygon(
        polygons,
        [-(pad_left), -(pad_top)],
    )

    # Crop
    x1 = int((padded_image_size[0] - crop_width + 1) * w_start)
    y1 = int((padded_image_size[1] - crop_height + 1) * h_start)
    polygons = translate_polygon(polygons, [x1, y1])

    # Resize
    polygons = resize_polygon(
        polygons,
        [crop_width, crop_height],
        w_target or w_original,
        h_target or h_original,
    )
    return polygons

perspective_transform

perspective_transform(polygons: PolygonType, image_size: Tuple[int], offset_top_left: OffsetType, offset_top_right: OffsetType, offset_bottom_right: OffsetType, offset_bottom_left: OffsetType) -> PolygonType
Source code in SaigeToolkit/data/transform/polygon_function.py
def perspective_transform(
    polygons: PolygonType,
    image_size: Tuple[int],
    offset_top_left: OffsetType,
    offset_top_right: OffsetType,
    offset_bottom_right: OffsetType,
    offset_bottom_left: OffsetType,
) -> PolygonType:
    width, height = image_size

    for point_offset in [
        offset_top_left,
        offset_top_right,
        offset_bottom_right,
        offset_bottom_left,
    ]:
        offset_x, offset_y = point_offset

        check_value(offset_x, 0, 49)
        check_value(offset_y, 0, 49)

    transform_matrix = calculate_transform_matrix(
        width=width,
        height=height,
        offset_top_left=offset_top_left,
        offset_top_right=offset_top_right,
        offset_bottom_right=offset_bottom_right,
        offset_bottom_left=offset_bottom_left,
    )
    new_polygons = deepcopy(polygons)
    for idx_poly, polygon in enumerate(polygons):
        for idx_pt, point in enumerate(polygon):
            x, y, scaling_factor = np.dot(transform_matrix, np.append(point, 1))

            new_polygons[idx_poly][idx_pt][0] = int(x / scaling_factor)
            new_polygons[idx_poly][idx_pt][1] = int(y / scaling_factor)

    return new_polygons

resize_polygon

resize_polygon(polygons: PolygonType, image_size: Tuple[int], tw: int, th: int) -> PolygonType

Resize polygon from (w, h) to (tw, th)

Source code in SaigeToolkit/data/transform/polygon_function.py
def resize_polygon(polygons: PolygonType, image_size: Tuple[int], tw: int, th: int) -> PolygonType:
    """Resize polygon from (w, h) to (tw, th)"""
    if len(polygons) == 0:
        return polygons

    w, h = image_size
    if w == tw and h == th:
        return polygons

    dtype = polygons[0].dtype

    w_scale = tw / w
    h_scale = th / h

    new_polygons = deepcopy(polygons)

    for polygon in new_polygons:
        polygon[:, 0] = polygon[:, 0] * w_scale
        polygon[:, 1] = polygon[:, 1] * h_scale
        polygon = polygon.astype(dtype)

    return new_polygons

translate_polygon

translate_polygon(polygons: PolygonType, offset: Tuple[int]) -> PolygonType

Translate polyfon from [(x1, y1), ... ] to [(x1 - x_offset), (y1 - y_offset), ...]

Source code in SaigeToolkit/data/transform/polygon_function.py
def translate_polygon(
    polygons: PolygonType,
    offset: Tuple[int],
) -> PolygonType:
    """Translate polyfon from [(x1, y1), ... ] to [(x1 - x_offset), (y1 - y_offset), ...]"""
    if len(polygons) == 0:
        return polygons

    if offset[0] == 0 and offset[1] == 0:
        return polygons

    dtype = polygons[0].dtype

    x_offset, y_offset = np.array(offset).astype(dtype)

    new_polygons = deepcopy(polygons)

    for polygon in new_polygons:
        polygon[:, 0] -= x_offset
        polygon[:, 1] -= y_offset

    return new_polygons

resize

Resizer

Resizer(size: Optional[Union[ImageSizeType, int]] = None, scale: Optional[Union[int, float]] = None, area_sqrt: Optional[Union[int, float]] = None, max_size: Optional[Union[ImageSizeType, int]] = None, round: Optional[int] = None, round_type: str = 'round', resampling: str = 'bilinear', image_only: bool = False, use_cv2_for_numpy: bool = True)

Module for resizing PIL, torch, numpy images Resizer의 작동 방식은 다음과 같습니다. 1. target size 계산 2. target size 미세 조정 3. 데이터에 resize 적용

Args에 따라 1, 2번의 작동 방식이 달라집니다. 1. target size 계산 - resize 될 target size를 계산합니다. - target size는 4개의 args에 영향을 받을 수 있습니다. (size, scale, area_sqrt, max_size) 하나의 Resizer는 4개중 하나의 args만 사용할 수 있으며, 2개 이상의 args가 None이 아닐시 error를 raise 합니다. 1-1. size: image를 size로 resize 합니다. aspect ratio가 변경될 수 있습니다. 1-2. scale: image를 scale배로 늘리거나 줄입니다. aspect ratio는 유지됩니다. 1-3. area_sqrt: image의 면적이 area_sqrt^2이 되도록 이미지를 늘리거나 줄입니다. aspect ratio는 유지됩니다. 1-4. max_size: image의 size가 max_size보다 클 경우 max_size로 resize 합니다. aspect ratio를 유지하기 위해, width/height중 더 많이 줄어야 하는 비율에 맞추어 전체를 resize합니다.

  1. target size 미세 조정
  2. resize된 이미지가 특정 값의 배수가 되도록 target size를 미세 조정 합니다. model의 입력으로 넣을 때, image size가 특정 값의 배수가 되어야 하기 때문에 필요합니다.
    1. target size 계산의 size parameter와 함께 사용할 수 없습니다. (고정 size이기 때문)
  3. target size 미세 조정은 총 2개의 args에 영향을 받을 수 있습니다. (round, round_type) round가 None이면 미세 조정을 하지 않습니다, round_type에 따라 다른 방식의 미세 조정을 적용합니다. 2-1. round: 미세 조정시 반올림합니다. (target size보다 작거나 크거나 같습니다.) 2-2. floor: 미세 조정시 내림합니다. (target size보다 작거나 같습니다.) 2-3. ceil: 미세 조정시 올림합니다. (target size보다 크거나 같습니다.)
  4. 미세 조정된 target_size의 width혹은 height가 0이 될 경우, round 값으로 변경해줍니다. (ex. size=(3, 3), round=8이면 round_type에 상관없이 size=(8, 8)이 됨.)

Parameters:

  • size (Optional[Union[ImageSizeType, int]], default: None ) –

    target image size. Defaults to None.

  • scale (Optional[Union[int, float]], default: None ) –

    target image scale. Defaults to None.

  • area_sqrt (Optional[Union[int, float]], default: None ) –

    target image sqrt area. Defaults to None.

  • max_size (Optional[Union[ImageSizeType, int]], default: None ) –

    target maximum image size. Defaults to None.

  • round (Optional[int], default: None ) –

    round image size. Defaults to None.

  • round_type (Optional[str], default: 'round' ) –

    type of round image. One of ["round", "floor", "ceil"]. Defaults to "round".

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

    One of ["nearest", "bilinear", "bicubic"]. Defaults to "bilinear".

  • image_only (bool, default: False ) –

    to resize image only. Defaults to False.

  • use_cv2_for_numpy (bool, default: True ) –

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

Raises:

  • ResizerParameterValueError

    target size parameter가 2개 이상 들어오면 error를 raise 합니다. round_type이 ["round", "floor", "ceil"]안에 없으면 error를 raise 합니다. size와 미세조정 parameter가 함께 들어오면 error를 raise 합니다.

Source code in SaigeToolkit/data/transform/resize.py
def __init__(
    self,
    size: Optional[Union[ImageSizeType, int]] = None,
    scale: Optional[Union[int, float]] = None,
    area_sqrt: Optional[Union[int, float]] = None,
    max_size: Optional[Union[ImageSizeType, int]] = None,
    round: Optional[int] = None,
    round_type: str = "round",
    resampling: str = "bilinear",
    image_only: bool = False,
    use_cv2_for_numpy: bool = True,
) -> None:
    """Module for resizing PIL, torch, numpy images
    Resizer의 작동 방식은 다음과 같습니다.
    1. target size 계산
    2. target size 미세 조정
    3. 데이터에 resize 적용

    Args에 따라 1, 2번의 작동 방식이 달라집니다.
    1. target size 계산
    - resize 될 target size를 계산합니다.
    - target size는 4개의 args에 영향을 받을 수 있습니다. (size, scale, area_sqrt, max_size)
      하나의 Resizer는 4개중 하나의 args만 사용할 수 있으며, 2개 이상의 args가 None이 아닐시 error를 raise 합니다.
        1-1. size: image를 size로 resize 합니다. aspect ratio가 변경될 수 있습니다.
        1-2. scale: image를 scale배로 늘리거나 줄입니다. aspect ratio는 유지됩니다.
        1-3. area_sqrt: image의 면적이 area_sqrt^2이 되도록 이미지를 늘리거나 줄입니다. aspect ratio는 유지됩니다.
        1-4. max_size: image의 size가 max_size보다 클 경우 max_size로 resize 합니다.
                        aspect ratio를 유지하기 위해, width/height중 더 많이 줄어야 하는 비율에 맞추어 전체를 resize합니다.

    2. target size 미세 조정
    - resize된 이미지가 특정 값의 배수가 되도록 target size를 미세 조정 합니다.
      model의 입력으로 넣을 때, image size가 특정 값의 배수가 되어야 하기 때문에 필요합니다.
    - 1. target size 계산의 size parameter와 함께 사용할 수 없습니다. (고정 size이기 때문)
    - target size 미세 조정은 총 2개의 args에 영향을 받을 수 있습니다. (round, round_type)
      round가 None이면 미세 조정을 하지 않습니다, round_type에 따라 다른 방식의 미세 조정을 적용합니다.
        2-1. round: 미세 조정시 반올림합니다. (target size보다 작거나 크거나 같습니다.)
        2-2. floor: 미세 조정시 내림합니다. (target size보다 작거나 같습니다.)
        2-3. ceil: 미세 조정시 올림합니다. (target size보다 크거나 같습니다.)
    - 미세 조정된 target_size의 width혹은 height가 0이 될 경우, round 값으로 변경해줍니다.
      (ex. size=(3, 3), round=8이면 round_type에 상관없이 size=(8, 8)이 됨.)

    Args:
        size (Optional[Union[ImageSizeType, int]], optional): target image size. Defaults to None.
        scale (Optional[Union[int, float]], optional): target image scale. Defaults to None.
        area_sqrt (Optional[Union[int, float]], optional): target image sqrt area. Defaults to None.
        max_size (Optional[Union[ImageSizeType, int]], optional): target maximum image size. Defaults to None.
        round (Optional[int], optional): round image size. Defaults to None.
        round_type (Optional[str], optional): type of round image. One of ["round", "floor", "ceil"]. Defaults to "round".
        resampling (str): One of ["nearest", "bilinear", "bicubic"]. Defaults to "bilinear".
        image_only (bool): to resize image only. Defaults to False.
        use_cv2_for_numpy (bool): use cv2 instead of PIL for faster numpy array image resizing. Defaults to True.

    Raises:
        ResizerParameterValueError: target size parameter가 2개 이상 들어오면 error를 raise 합니다.
                                    round_type이 ["round", "floor", "ceil"]안에 없으면 error를 raise 합니다.
                                    size와 미세조정 parameter가 함께 들어오면 error를 raise 합니다.
    """

    self._check_parameter(
        size=size,
        scale=scale,
        area_sqrt=area_sqrt,
        max_size=max_size,
        round=round,
        round_type=round_type,
    )

    size = self._preprocess_size(size)
    max_size = self._preprocess_size(max_size)

    if scale == 1:
        scale = None

    self.size = size
    self.scale = scale
    self.area_sqrt = area_sqrt
    self.max_size = max_size
    self.round = round
    self.round_type = round_type

    if resampling not in RESAMPLE_PIL or resampling not in RESAMPLE_TORCH:
        raise ValueError
    self.resampling = resampling

    self.image_only = image_only
    self.use_cv2_for_numpy = use_cv2_for_numpy
round_functions class-attribute instance-attribute
round_functions = {'round': round, 'floor': floor, 'ceil': ceil}
size instance-attribute
size = size
scale instance-attribute
scale = scale
area_sqrt instance-attribute
area_sqrt = area_sqrt
max_size instance-attribute
max_size = max_size
round instance-attribute
round = round
round_type instance-attribute
round_type = round_type
resampling instance-attribute
resampling = resampling
image_only instance-attribute
image_only = image_only
use_cv2_for_numpy instance-attribute
use_cv2_for_numpy = use_cv2_for_numpy
__call__
__call__(return_revert_params: bool = False, **data) -> Union[Dict, Tuple[Dict, Dict]]
Source code in SaigeToolkit/data/transform/resize.py
def __call__(self, return_revert_params: bool = False, **data) -> Union[Dict, Tuple[Dict, Dict]]:
    input_size = self.compute_input_size(data)
    target_size = self.compute_target_size(input_size)
    data = self.apply_with_params(
        data=data,
        input_size=input_size,
        target_size=target_size,
        resampling=self.resampling,
        image_only=self.image_only,
        use_cv2_for_numpy=self.use_cv2_for_numpy,
    )

    if return_revert_params:
        revert_params = {
            "image_size_before_resize": input_size,
            "image_size_after_resize": target_size,
        }
        return_value = (data, revert_params)
    else:
        return_value = data

    return return_value
compute_input_size staticmethod
compute_input_size(data: Dict) -> ImageSizeType

target size를 계산하기 위해 input size를 가져옵니다. Note: data에 image 혹은 mask data는 있다고 가정하며, 둘의 size는 같다고 가정합니다.

Source code in SaigeToolkit/data/transform/resize.py
@staticmethod
def compute_input_size(data: Dict) -> ImageSizeType:
    """target size를 계산하기 위해 input size를 가져옵니다.
    Note: data에 image 혹은 mask data는 있다고 가정하며, 둘의 size는 같다고 가정합니다.
    """
    if "image" in data:
        multipage = isinstance(data["image"], List)
        input_size = read_image_size(data["image"][0] if multipage else data["image"])
    elif "mask" in data:
        input_size = read_image_size(data["mask"])
    else:
        raise NotImplementedError

    return input_size
compute_target_size
compute_target_size(input_size: Union[ImageSizeType, ndarray]) -> ImageSizeType

compute resize target size from input image size

Parameters:

Returns:

Source code in SaigeToolkit/data/transform/resize.py
def compute_target_size(self, input_size: Union[ImageSizeType, np.ndarray]) -> ImageSizeType:
    """compute resize target size from input image size

    Args:
        input_size (Union[ImageSizeType, np.ndarray]): (width, height)

    Returns:
        ImageSizeType: (width, height)
    """
    return self._compute_target_size(
        input_size=input_size,
        size=self.size,
        scale=self.scale,
        area_sqrt=self.area_sqrt,
        max_size=self.max_size,
        round=self.round,
        round_type=self.round_type,
    )
apply_with_params classmethod
apply_with_params(data: Dict, input_size: ImageSizeType, target_size: ImageSizeType, resampling: str, image_only: bool, use_cv2_for_numpy: bool = True) -> Dict
Source code in SaigeToolkit/data/transform/resize.py
@classmethod
def apply_with_params(
    cls,
    data: Dict,
    input_size: ImageSizeType,
    target_size: ImageSizeType,
    resampling: str,
    image_only: bool,
    use_cv2_for_numpy: bool = True,
) -> Dict:
    if "image" in data:
        data["image"] = cls.apply_to_image(
            image=data["image"],
            target_size=target_size,
            resampling=resampling,
            use_cv2_for_numpy=use_cv2_for_numpy,
        )

    if image_only:
        return data

    if "mask" in data:
        data["mask"] = cls.apply_to_mask(mask=data["mask"], target_size=target_size)

    if "bboxes" in data:
        data["bboxes"] = cls.apply_to_bboxes(
            bboxes=data["bboxes"], input_size=input_size, target_size=target_size
        )

    if "polygons" in data:
        data["polygons"] = cls.apply_to_polygons(
            polygons=data["polygons"], input_size=input_size, target_size=target_size
        )

    return data
apply_to_image staticmethod
apply_to_image(image, target_size: ImageSizeType, resampling: str, use_cv2_for_numpy: bool = True)
Source code in SaigeToolkit/data/transform/resize.py
@staticmethod
def apply_to_image(
    image,
    target_size: ImageSizeType,
    resampling: str,
    use_cv2_for_numpy: bool = True,
):
    multipage = isinstance(image, List)
    input_size = read_image_size(image[0] if multipage else image)
    if list(target_size) != list(input_size):
        if multipage:
            image = [
                resize_image(
                    image_, target_size, resampling=resampling, use_cv2_for_numpy=use_cv2_for_numpy
                )
                for image_ in image
            ]
        else:
            image = resize_image(
                image, target_size, resampling=resampling, use_cv2_for_numpy=use_cv2_for_numpy
            )

    return image
apply_to_mask staticmethod
apply_to_mask(mask, target_size: ImageSizeType)
Source code in SaigeToolkit/data/transform/resize.py
@staticmethod
def apply_to_mask(mask, target_size: ImageSizeType):
    input_size = read_image_size(mask)
    if list(target_size) != list(input_size):
        mask = resize_mask(mask, target_size)
    return mask
apply_to_bboxes staticmethod
apply_to_bboxes(bboxes, input_size: ImageSizeType, target_size: ImageSizeType)
Source code in SaigeToolkit/data/transform/resize.py
@staticmethod
def apply_to_bboxes(bboxes, input_size: ImageSizeType, target_size: ImageSizeType):
    bboxes = resize_box(bboxes, input_size, target_size[0], target_size[1])
    return bboxes
apply_to_polygons staticmethod
apply_to_polygons(polygons, input_size: ImageSizeType, target_size: ImageSizeType)
Source code in SaigeToolkit/data/transform/resize.py
@staticmethod
def apply_to_polygons(polygons, input_size: ImageSizeType, target_size: ImageSizeType):
    polygons = resize_polygon(
        polygons=polygons,
        image_size=input_size,
        tw=target_size[0],
        th=target_size[1],
    )
    return polygons
_compute_target_size classmethod
_compute_target_size(input_size: Union[ImageSizeType, ndarray], size: Optional[Union[ImageSizeType, int]] = None, scale: Optional[Union[int, float]] = None, area_sqrt: Optional[Union[int, float]] = None, max_size: Optional[Union[ImageSizeType, int]] = None, round: Optional[int] = None, round_type: str = 'round') -> ImageSizeType
Source code in SaigeToolkit/data/transform/resize.py
@classmethod
def _compute_target_size(
    cls,
    input_size: Union[ImageSizeType, np.ndarray],
    size: Optional[Union[ImageSizeType, int]] = None,
    scale: Optional[Union[int, float]] = None,
    area_sqrt: Optional[Union[int, float]] = None,
    max_size: Optional[Union[ImageSizeType, int]] = None,
    round: Optional[int] = None,
    round_type: str = "round",
) -> ImageSizeType:
    input_size = np.array(input_size)

    if size is not None:
        target_size = np.array(size)
    elif scale is not None:
        target_size = (input_size * scale).astype(int)
    elif area_sqrt is not None:
        scale = area_sqrt / sqrt(input_size[0] * input_size[1])
        target_size = (input_size * scale).astype(int)
    elif max_size is not None:
        max_size = np.array(max_size)
        scale = min(np.min(max_size / input_size), 1)
        target_size = (input_size * scale).astype(int)
    else:
        target_size = input_size

    if size is None and round is not None:
        round_func = cls.round_functions[round_type]
        target_size = (round * round_func(target_size / round)).astype(int)
        target_size = np.clip(a=target_size, a_min=round, a_max=None)

    return tuple(target_size)
_preprocess_size staticmethod
_preprocess_size(size: Optional[Union[ImageSizeType, int]] = None) -> ImageSizeType
Source code in SaigeToolkit/data/transform/resize.py
@staticmethod
def _preprocess_size(size: Optional[Union[ImageSizeType, int]] = None) -> ImageSizeType:
    if isinstance(size, int):
        size = (size, size)
    elif isinstance(size, Sequence):
        size = (size[0], size[1])

    return size
_check_parameter classmethod
_check_parameter(size: Optional[Union[ImageSizeType, int]], scale: Optional[Union[int, float]], area_sqrt: Optional[Union[int, float]], max_size: Optional[Union[ImageSizeType, int]], round: Optional[int], round_type: str) -> None
Source code in SaigeToolkit/data/transform/resize.py
@classmethod
def _check_parameter(
    cls,
    size: Optional[Union[ImageSizeType, int]],
    scale: Optional[Union[int, float]],
    area_sqrt: Optional[Union[int, float]],
    max_size: Optional[Union[ImageSizeType, int]],
    round: Optional[int],
    round_type: str,
) -> None:
    num_of_not_none = 0
    for arg in [size, scale, area_sqrt, max_size]:
        if arg is not None:
            num_of_not_none += 1
    if num_of_not_none > 1:
        raise ResizerParameterValueError

    if round_type not in cls.round_functions:
        raise ResizerParameterValueError

    if (size is not None) and (round is not None):
        raise ResizerParameterValueError

InspectionSizeResizer

InspectionSizeResizer(inspection_size_wh: Optional[ImageSizeType] = None, resizer: Optional[Resizer] = None)

Bases: Resizer

원본 이미지가 inspection_size를 넘지 않도록 resize 했을 때의 image scale만큼 resize를 해주는 resizer를 생성하는 class 입니다.

inspection_size는 원본 이미지에 대해 적용하지만, 실제 연산은 ROI가 먼저 계산되기 때문에, 원본 이미지가 inspection_size를 넘지 않도록 resize 했을 때의 image scale을 미리 계산해두고, 해당 scale만큼 resize를 하는 resizer를 생성하여 계산합니다.

Source code in SaigeToolkit/data/transform/resize.py
def __init__(
    self,
    inspection_size_wh: Optional[ImageSizeType] = None,
    resizer: Optional[Resizer] = None,
) -> None:
    self._check_inspection_size_type(inspection_size_wh)
    self._check_inspection_size_value(inspection_size_wh)

    initial_params = {}
    if resizer is not None:
        initial_params["resampling"] = resizer.resampling
        initial_params["image_only"] = resizer.image_only

    super().__init__(**initial_params)

    self.inspection_size_wh = self._preprocess_size(inspection_size_wh)
inspection_size_wh instance-attribute
inspection_size_wh = _preprocess_size(inspection_size_wh)
_check_inspection_size_type
_check_inspection_size_type(inspection_size_wh: Optional[ImageSizeType]) -> None
Source code in SaigeToolkit/data/transform/resize.py
def _check_inspection_size_type(self, inspection_size_wh: Optional[ImageSizeType]) -> None:
    # 타입 체크
    valid_type = (
        inspection_size_wh is None
        or len(inspection_size_wh) == 2
        and all(isinstance(v, int) for v in inspection_size_wh)
    )
    if not valid_type:
        raise InspectionSizeTypeError
_check_inspection_size_value
_check_inspection_size_value(inspection_size_wh: Optional[ImageSizeType]) -> None
Source code in SaigeToolkit/data/transform/resize.py
def _check_inspection_size_value(self, inspection_size_wh: Optional[ImageSizeType]) -> None:
    # 값 체크, inspection_size_wh의 값은 모두 1 이상이어야 한다.
    valid_value = inspection_size_wh is None or all(value >= 1 for value in inspection_size_wh)
    if not valid_value:
        raise InspectionSizeValueError
set_scale_from_data
set_scale_from_data(**data) -> None
Source code in SaigeToolkit/data/transform/resize.py
def set_scale_from_data(self, **data) -> None:
    if self.inspection_size_wh is not None:
        input_size = np.array(self.compute_input_size(data))
        inspection_size_wh = np.array(self.inspection_size_wh)
        self.scale = min(np.min(inspection_size_wh / input_size), 1)

StackedResizerHandler

StackedResizerHandler(resizer_list: List[Optional[Resizer]])

같은 image에 대해 여러번의 resize가 연속적으로 적용될 때, 여러개의 resize를 하나로 묶어서 최종 target size로 한번에 resize를 해주는 class입니다.

Source code in SaigeToolkit/data/transform/resize.py
def __init__(
    self,
    resizer_list: List[Optional[Resizer]],
) -> None:
    self.resizer_list = resizer_list
    self.is_empty = len(resizer_list) == 0 or all(x is None for x in resizer_list)

    if not self.is_empty:
        resampling = set()
        image_only = set()
        use_cv2_for_numpy = set()
        for resizer in resizer_list:
            if resizer is not None:
                resampling.add(resizer.resampling)
                image_only.add(resizer.image_only)
                use_cv2_for_numpy.add(resizer.use_cv2_for_numpy)

        if len(resampling) > 1 or len(image_only) > 1 or len(use_cv2_for_numpy) > 1:
            raise StackedResizerHandlerParameterValueError

        self.resampling = resampling.pop()
        self.image_only = image_only.pop()
        self.use_cv2_for_numpy = use_cv2_for_numpy.pop()
resizer_list instance-attribute
resizer_list = resizer_list
is_empty instance-attribute
is_empty = len(resizer_list) == 0 or all(x is None for x in resizer_list)
resampling instance-attribute
resampling = pop()
image_only instance-attribute
image_only = pop()
use_cv2_for_numpy instance-attribute
use_cv2_for_numpy = pop()
__call__
__call__(return_revert_params: bool = False, **data) -> Union[Dict, Tuple[Dict, Dict]]
Source code in SaigeToolkit/data/transform/resize.py
def __call__(self, return_revert_params: bool = False, **data) -> Union[Dict, Tuple[Dict, Dict]]:
    if return_revert_params:
        revert_params = []

    if self.is_empty:
        revert_params = [None] * len(self.resizer_list)
    else:
        input_size = Resizer.compute_input_size(data)
        target_size = input_size

        for resizer in self.resizer_list:
            if resizer is None:
                if return_revert_params:
                    revert_params.append(None)
            else:
                revert_param = {"image_size_before_resize": target_size}
                target_size = resizer.compute_target_size(target_size)
                revert_param["image_size_after_resize"] = target_size
                if return_revert_params:
                    revert_params.append(revert_param)

        if list(input_size) != list(target_size):
            data = Resizer.apply_with_params(
                data=data,
                input_size=input_size,
                target_size=target_size,
                resampling=self.resampling,
                image_only=self.image_only,
                use_cv2_for_numpy=self.use_cv2_for_numpy,
            )

    if return_revert_params:
        return_value = (data, revert_params)
    else:
        return_value = data

    return return_value

roi

ROI 좌표 계산 및 ROI 크롭, blind_mask 적용을 수행하는 ROIHandler 클래스를 제공합니다. Set-ROI 기능을 위한 API 클래스인 ROIHandlerAPI도 제공합니다.

api

error_handler module-attribute
error_handler = get_error_handler(SaigeToolkitError)
ROIHandlerAPI
ROIHandlerAPI(**kwargs)

Set-ROI 기능을 위한 API입니다.

Usage
# 핸들러 빌드. 아래는 simple mode의 예제 config이며, 자세한 설명은 ROIHandlerAPI.set() 함수 참고.
config = {
    "mode": "simple",
    "left": 0.0,
    "top": 0.0,
    "right": 1.0,
    "bottom": 1.0,
    "blind_mask": None,
}
error, roi_hander = ROIHandlerAPI.build(config)

# image에 대한 roi 계산. 리턴 결과 설명은 ROIHandlerAPI.apply() 함수 참고.
image = np.zeros((100, 100, 3), dtype=np.unit8)
error, roi_results = roi_handler.apply(image)

# roi 파라미터 변경
config["left"] = 0.1
error, _  = roi_handler.set(config)

# 변경된 파라미터로 roi 다시 계산
error, roi_results = roi_handler.apply(image)
Source code in SaigeToolkit/data/transform/roi/api.py
def __init__(self, **kwargs) -> None:
    self.handler = ROIHandler(**kwargs)
handler instance-attribute
handler = ROIHandler(**kwargs)
build classmethod
build(config: Dict) -> ROIHandlerAPI

ROIHandlerAPI를 빌드합니다. Args: config (Dict): ROIHandlerAPI.set의 파라미터와 동일합니다.

Returns:

Source code in SaigeToolkit/data/transform/roi/api.py
@classmethod
@error_handler
def build(cls, config: Dict) -> ROIHandlerAPI:
    """ROIHandlerAPI를 빌드합니다.
    Args:
        config (Dict): ROIHandlerAPI.set의 파라미터와 동일합니다.

    Returns:
        ROIHandlerAPI: 빌드된 ROIHandlerAPI
    """
    return cls(**config)
set
set(config: Dict) -> None

ROI 파라미터를 변경합니다.

Parameters:

  • config (Dict) –

    ROI 파라미터의 dict입니다. mode에 따라 다른 파라미터를 가집니다.

    # simple mode:
    {
        "mode": "simple",  # Simple ROI 모드.
        "left": float,  # ROI의 왼쪽 경계. [0.0, 1.0) 범위의 실수.
        "top": float,  # ROI의 위쪽 경계. [0.0, 1.0) 범위의 실수.
        "right": float,  # ROI의 오른쪽 경계. (right, 1.0] 범위의 실수.
        "bottom": float,  # ROI의 아래쪽 경계. (top, 1.0] 범위의 실수.
        "blind_mask": Optional[np.ndarray],  # blind mask 이미지. None인 경우 blind 적용 안함.
                                             # np.ndarray인 경우 (dtype=uint8, shape=(H_roi, W_roi))이며 픽셀 값은 0 또는 1.
                                             # mask의 값이 1인 영역이 학습/검사 시 마스킹됩니다.
                                             # polygons에는 blind_mask가 적용되지 않습니다.
    }
    # advanced mode:
    {
        "mode": "advanced",  # Advanced ROI 모드.
        "intensity": List[int],  # 필터링할 [최소, 최대] 픽셀값 범위. 각 값은 [0, 255] 범위의 정수.
        "expansion": int,  # 필터링된 픽셀 영역에 대한 확장/축소 정도. [-10, 10] 범위의 정수.
        "inversion": bool,  # True인 경우 필터링된 픽셀 영역을 반전.
        "offset_left": float,  # ROI 박스의 왼쪽 사이즈. [0.0, 2.0] 범위의 실수 이며, 1인 경우 기본 크기.
        "offset_right": float,  # ROI 박스의 오른쪽 사이즈. [0.0, 2.0] 범위의 실수 이며, 1인 경우 기본 크기.
        "offset_top": float,  # ROI 박스의 위쪽 사이즈. [0.0, 2.0] 범위의 실수 이며, 1인 경우 기본 크기.
        "offset_bottom": float,  # ROI 박스의 아래쪽 사이즈. [0.0, 2.0] 범위의 실수 이며, 1인 경우 기본 크기.
        "blind_mask": Optional[np.ndarray],  # blind mask 이미지. None인 경우 blind 적용 안함.
                                             # np.ndarray인 경우 (dtype=uint8, shape=(H_roi, W_roi))이며 픽셀 값은 0 또는 1.
                                             # mask의 값이 1인 영역이 학습/검사 시 마스킹됩니다.
                                             # polygons에는 blind_mask가 적용되지 않습니다.
    }
    

Source code in SaigeToolkit/data/transform/roi/api.py
@error_handler
def set(self, config: Dict) -> None:
    """ROI 파라미터를 변경합니다.

    Args:
        config (Dict): ROI 파라미터의 dict입니다. mode에 따라 다른 파라미터를 가집니다.
            ```python
            # simple mode:
            {
                "mode": "simple",  # Simple ROI 모드.
                "left": float,  # ROI의 왼쪽 경계. [0.0, 1.0) 범위의 실수.
                "top": float,  # ROI의 위쪽 경계. [0.0, 1.0) 범위의 실수.
                "right": float,  # ROI의 오른쪽 경계. (right, 1.0] 범위의 실수.
                "bottom": float,  # ROI의 아래쪽 경계. (top, 1.0] 범위의 실수.
                "blind_mask": Optional[np.ndarray],  # blind mask 이미지. None인 경우 blind 적용 안함.
                                                     # np.ndarray인 경우 (dtype=uint8, shape=(H_roi, W_roi))이며 픽셀 값은 0 또는 1.
                                                     # mask의 값이 1인 영역이 학습/검사 시 마스킹됩니다.
                                                     # polygons에는 blind_mask가 적용되지 않습니다.
            }
            # advanced mode:
            {
                "mode": "advanced",  # Advanced ROI 모드.
                "intensity": List[int],  # 필터링할 [최소, 최대] 픽셀값 범위. 각 값은 [0, 255] 범위의 정수.
                "expansion": int,  # 필터링된 픽셀 영역에 대한 확장/축소 정도. [-10, 10] 범위의 정수.
                "inversion": bool,  # True인 경우 필터링된 픽셀 영역을 반전.
                "offset_left": float,  # ROI 박스의 왼쪽 사이즈. [0.0, 2.0] 범위의 실수 이며, 1인 경우 기본 크기.
                "offset_right": float,  # ROI 박스의 오른쪽 사이즈. [0.0, 2.0] 범위의 실수 이며, 1인 경우 기본 크기.
                "offset_top": float,  # ROI 박스의 위쪽 사이즈. [0.0, 2.0] 범위의 실수 이며, 1인 경우 기본 크기.
                "offset_bottom": float,  # ROI 박스의 아래쪽 사이즈. [0.0, 2.0] 범위의 실수 이며, 1인 경우 기본 크기.
                "blind_mask": Optional[np.ndarray],  # blind mask 이미지. None인 경우 blind 적용 안함.
                                                     # np.ndarray인 경우 (dtype=uint8, shape=(H_roi, W_roi))이며 픽셀 값은 0 또는 1.
                                                     # mask의 값이 1인 영역이 학습/검사 시 마스킹됩니다.
                                                     # polygons에는 blind_mask가 적용되지 않습니다.
            }
            ```
    """
    self.handler.set(**config)
apply
apply(image: ndarray) -> Dict

image에 대한 ROI 좌표 및 기타 결과를 계산합니다.

Parameters:

  • image (ndarray) –

    ROI를 적용할 입력 이미지.

Returns:

  • Dict ( Dict ) –

    ROI 계산 결과 dict 입니다. mode에 따라 다른 결과 값들을 가집니다.

    # simple mode:
    {
        "roi_coordinates": List[int],  # [left, top, right, bottom].
    }
    # advanced mode:
    {
        "roi_coordinates": List[int],  # [left, top, right, bottom].
        "filtered_image": np.ndarray(uint8, shape=(H, W)),  # intensity, expansion, inversion이 적용된 중간 결과 이미지 입니다. (픽셀값: 0 or 1)
    }
    

Source code in SaigeToolkit/data/transform/roi/api.py
@error_handler
def apply(self, image: np.ndarray) -> Dict:
    """`image`에 대한 ROI 좌표 및 기타 결과를 계산합니다.

    Args:
        image (np.ndarray): ROI를 적용할 입력 이미지.

    Returns:
        Dict: ROI 계산 결과 dict 입니다. mode에 따라 다른 결과 값들을 가집니다.
            ```python
            # simple mode:
            {
                "roi_coordinates": List[int],  # [left, top, right, bottom].
            }
            # advanced mode:
            {
                "roi_coordinates": List[int],  # [left, top, right, bottom].
                "filtered_image": np.ndarray(uint8, shape=(H, W)),  # intensity, expansion, inversion이 적용된 중간 결과 이미지 입니다. (픽셀값: 0 or 1)
            }
            ```
    """
    return self.handler.roi_calculator(image=image, get_intermediate_results=True)

roi_calculator

logger module-attribute
logger = getLogger('SaigeResearch')
ROICalculator

Bases: ABC

ROI 좌표 계산을 위한 추상 클래스입니다.

set abstractmethod
set(**kwargs) -> None
Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
@abstractmethod
def set(self, **kwargs) -> None:
    pass
__call__ abstractmethod
__call__(image: Union[Image, ndarray], get_intermediate_results: bool = False, warmup: bool = False, **data) -> Dict
Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
@abstractmethod
def __call__(
    self,
    image: Union[Image.Image, np.ndarray],
    get_intermediate_results: bool = False,
    warmup: bool = False,
    **data,
) -> Dict:
    pass
RelativeBoxROI
RelativeBoxROI(**kwargs)

Bases: ROICalculator

이미지 크기에 비례하는 상대좌표 박스로 ROI를 계산합니다.

Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def __init__(self, **kwargs) -> None:
    self.set(**kwargs)
set
set(left: float, top: float, right: float, bottom: float) -> None
Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def set(self, left: float, top: float, right: float, bottom: float) -> None:
    if not (0.0 <= left < right <= 1.0) or not (0.0 <= top < bottom <= 1.0):
        raise SimpleROIParameterValueError

    self.left = left
    self.top = top
    self.right = right
    self.bottom = bottom
__call__
__call__(image: Union[Image, ndarray], get_intermediate_results: bool = False, warmup: bool = False, **data) -> Dict
Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def __call__(
    self,
    image: Union[Image.Image, np.ndarray],
    get_intermediate_results: bool = False,
    warmup: bool = False,
    **data,
) -> Dict:
    w, h = read_image_size(image)
    left = int(self.left * w)
    right = max(int(self.right * w), left + 1)
    top = int(self.top * h)
    bottom = max(int(self.bottom * h), top + 1)
    return {"roi_coordinates": [left, top, right, bottom]}
PixelIntensityROI
PixelIntensityROI(**kwargs)

Bases: ROICalculator

픽셀값이 intensity 범위에 들어오는 픽셀만 필터링한 뒤, 필터링 된 픽셀들의 컨투어를 찾고, 가장 면적이 큰 컨투어를 감싸는 최소 박스로 ROI를 계산합니다.

Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def __init__(self, **kwargs) -> None:
    self.set(**kwargs)
set
set(intensity: Union[Tuple[int, int], List[int]], expansion: int, inversion: bool, offset_left: float, offset_right: float, offset_top: float, offset_bottom: float) -> None
Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def set(
    self,
    intensity: Union[Tuple[int, int], List[int]],
    expansion: int,
    inversion: bool,
    offset_left: float,
    offset_right: float,
    offset_top: float,
    offset_bottom: float,
) -> None:
    if (
        not len(intensity) == 2
        or not isinstance(intensity[0], int)
        or not isinstance(intensity[1], int)
        or not (0 <= intensity[0] < intensity[1] <= 255)
    ):
        raise AdvancedROIParameterValueError

    if not (-10 <= expansion <= 10):
        raise AdvancedROIParameterValueError

    for offset in (offset_left, offset_right, offset_top, offset_bottom):
        if not (0 <= offset <= 2):
            raise AdvancedROIParameterValueError
    if not (offset_left + offset_right >= 0.1) or not (offset_top + offset_bottom >= 0.1):
        raise AdvancedROIParameterValueError

    self.intensity = intensity
    self.expansion = expansion
    self.inversion = inversion
    self.offset_left = offset_left
    self.offset_right = offset_right
    self.offset_top = offset_top
    self.offset_bottom = offset_bottom
__call__
__call__(image: Union[Image, ndarray], get_intermediate_results: bool = False, warmup: bool = False, **data) -> Dict
Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def __call__(
    self,
    image: Union[Image.Image, np.ndarray],
    get_intermediate_results: bool = False,
    warmup: bool = False,
    **data,
) -> Dict:
    w, h = read_image_size(image)

    # PixelIntensityROI의 경우 input image size가 같더라도 output image size가 계속해서 달라질 수 있으며,
    # output image size의 최대 값은 input image size이기 때문에, warmup 시에 input image size를 return 함.
    if warmup:
        output = {"roi_coordinates": [0, 0, w, h]}
        return output

    # 후속 연산을 위해 grayscale numpy array 이미지로 변환
    filtered_image = to_numpy(image, copy=False)
    filtered_image = convert_image_mode(filtered_image, "L", copy=False)

    # intensity 범위에 들어오는 픽셀을 찾고, 해당 픽셀들을 expand & inverse
    filtered_image = cv2.inRange(filtered_image, lowerb=self.intensity[0], upperb=self.intensity[1])

    expansion_kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(3, 3), anchor=(1, 1))
    if self.expansion > 0:
        filtered_image = cv2.dilate(
            filtered_image, kernel=expansion_kernel, anchor=(-1, -1), iterations=2 * self.expansion
        )
    elif self.expansion < 0:
        filtered_image = cv2.erode(
            filtered_image, kernel=expansion_kernel, anchor=(-1, -1), iterations=2 * self.expansion
        )

    if self.inversion:
        filtered_image = cv2.bitwise_not(filtered_image)

    contours, _ = cv2.findContours(
        filtered_image, mode=cv2.RETR_LIST, method=cv2.CHAIN_APPROX_SIMPLE
    )
    if contours:
        # contour 중 면적 가장 큰 것 선택 & bounding box 계산
        contour_areas = list(map(cv2.contourArea, contours))
        max_area_contour = contours[np.argmax(contour_areas)]
        left, top, roi_width, roi_height = cv2.boundingRect(max_area_contour)

        # bounding box에 offset 적용 & 이미지 내부로 clip
        roi_center_x = left + (roi_width / 2)
        roi_center_y = top + (roi_height / 2)

        roi_left = roi_center_x - (self.offset_left * roi_width / 2)
        roi_right = roi_center_x + (self.offset_right * roi_width / 2)
        roi_top = roi_center_y - (self.offset_top * roi_height / 2)
        roi_bottom = roi_center_y + (self.offset_bottom * roi_height / 2)

        roi_left = int(np.clip(roi_left, 0, w - 1))
        roi_right = int(np.clip(roi_right, roi_left + 1, w))
        roi_top = int(np.clip(roi_top, 0, h - 1))
        roi_bottom = int(np.clip(roi_bottom, roi_top + 1, h))

        roi_coordinates = [roi_left, roi_top, roi_right, roi_bottom]
    else:
        roi_coordinates = [0, 0, w, h]

    output = {"roi_coordinates": roi_coordinates}
    if get_intermediate_results:
        output["filtered_image"] = filtered_image // 255
    return output
AutoRelativeBoxROI
AutoRelativeBoxROI(padding: str = 'medium')

Bases: RelativeBoxROI

RelativeBoxROI class with automatic coordinate calculation.

Monostate pattern applied to prevent ROI coordinate mismatch between train ~ validation dataset.

Attributes:

  • left (float) –

    ROI coordinates.

  • top (float) –

    ROI coordinates.

  • right (float) –

    ROI coordinates.

  • bottom (float) –

    ROI coordinates.

  • is_ready (bol) –

    Whether auto ROI coordinates is set.

  • image_hw (Optional[List[int]]) –

    dataset image size.

  • discard_outer_polygons (bool) –

    Flag for discarding polygons outside of current ROI region.

  • expand_ratio

    Ratio for expanding ROI region. Larger value means more padding.

Example
# in dataset building...
@property
def files(self):
    return self._files

@files.setter
def files(self, value):
    self._files = value

    if isinstance(self.transform.roi_handler.roi_calculator, AutoRelativeBoxROI):
        self.transform.roi_handler.roi_calculator.autoupdate_roi_coordinate(self)
                                                                          # self is dataset
Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def __init__(self, padding: str = "medium"):
    self.__dict__ = self.__shared_state

    if padding not in self.REGION_EXPAND_RATIO.keys():
        raise AutoROIParameterValueError(f"AutoROI mode {padding} not available.")

    self.expand_ratio = self.REGION_EXPAND_RATIO[padding]
__shared_state class-attribute instance-attribute
__shared_state = {'_is_ready': False, '_image_hw': None, '_left': -1.0, '_top': -1.0, '_right': -1.0, '_bottom': -1.0}
REGION_EXPAND_RATIO class-attribute instance-attribute
REGION_EXPAND_RATIO = {'small': 0.05, 'medium': 0.1, 'large': 0.2}
__dict__ instance-attribute
__dict__ = __shared_state
expand_ratio instance-attribute
expand_ratio = REGION_EXPAND_RATIO[padding]
is_ready property writable
is_ready
image_hw property writable
image_hw
left property writable
left
top property writable
top
right property writable
right
bottom property writable
bottom
_check_auto_roi_is_ready
_check_auto_roi_is_ready(function)
Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def _check_auto_roi_is_ready(function):
    def wrapper(self, *args, **kwargs):
        if self.is_ready:
            return function(self, *args, **kwargs)
        else:
            raise AutoROIParameterRuntimeError(
                "AutoROI not avaliable. Check log and dataset config."
            )

    return wrapper
__call__
__call__(*args, **kwargs)
Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
@_check_auto_roi_is_ready
def __call__(self, *args, **kwargs):
    return super().__call__(*args, **kwargs)
autoupdate_roi_coordinate
autoupdate_roi_coordinate(dataset: Dataset) -> bool

Automatically update ROI coordinate using dataset information.

Returns success flag.

Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def autoupdate_roi_coordinate(self, dataset: Dataset) -> bool:
    """Automatically update ROI coordinate using dataset information.

    Returns success flag.
    """

    # XXX: only called once
    if self.is_ready:
        logger.info("ROI coordinate already set. Update only once")
        return True

    marginal_label_region_xyxy = np.array([np.inf, np.inf, 0, 0])

    for data_idx in range(len(dataset)):
        file = dataset.files[data_idx]
        data_dict = dataset.load_image(file)
        data_dict.update(dataset.load_label(file))

        data_image: Union[Image.Image, np.ndarray] = data_dict["image"]
        current_img_hw = list(read_image_size(data_image))[::-1]

        if self.image_hw:
            if self.image_hw != current_img_hw:
                logger.warn("Variation in image size detected.. autoROI update aborted.")
                logger.warn(
                    f"Expected image size: {self.image_hw} / Given image size: {current_img_hw}"
                )
                return False

        self.image_hw = current_img_hw

        for polygon in data_dict["polygons"]:
            x_min, x_max = np.min(polygon[:, 0]), np.max(polygon[:, 0])
            y_min, y_max = np.min(polygon[:, 1]), np.max(polygon[:, 1])

            if x_min < marginal_label_region_xyxy[0]:
                marginal_label_region_xyxy[0] = x_min

            if y_min < marginal_label_region_xyxy[1]:
                marginal_label_region_xyxy[1] = y_min

            if x_max > marginal_label_region_xyxy[2]:
                marginal_label_region_xyxy[2] = x_max

            if y_max > marginal_label_region_xyxy[3]:
                marginal_label_region_xyxy[3] = y_max

    if not _check_region_xyxy_is_valid(marginal_label_region_xyxy):
        logger.warning(
            "AutoROI setting update failed. Needs at least one training data & valid polygon."
        )
        return False

    marginal_label_region_xyxy = _expand_region_xyxy(marginal_label_region_xyxy, self.expand_ratio)
    marginal_label_region_xyxy = _fit_region_into_img_size(
        marginal_label_region_xyxy,
        self.image_hw,
    )

    self.left = marginal_label_region_xyxy[0] / self.image_hw[1]
    self.top = marginal_label_region_xyxy[1] / self.image_hw[0]
    self.right = marginal_label_region_xyxy[2] / self.image_hw[1]
    self.bottom = marginal_label_region_xyxy[3] / self.image_hw[0]

    self.is_ready = True
    marginal_label_region_ratio = [self.left, self.top, self.right, self.bottom]
    logger.info(f"AutoROI setting success. ROI coordinates: {marginal_label_region_ratio}")

    return True
_expand_region_xyxy
_expand_region_xyxy(region_xyxy: ndarray, expand_ratio: float) -> ndarray
Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def _expand_region_xyxy(region_xyxy: np.ndarray, expand_ratio: float) -> np.ndarray:
    min_loc = region_xyxy[:2]
    max_loc = region_xyxy[2:]
    w, h = max_loc - min_loc
    offset_w: float = w * expand_ratio / 2
    offset_h: float = h * expand_ratio / 2
    offset = np.array([-offset_w, -offset_h, offset_w, offset_h])
    new_region_xyxy = (region_xyxy + offset).astype(int)
    return new_region_xyxy
_fit_region_into_img_size
_fit_region_into_img_size(region_xyxy: ndarray, img_shape_hw: Tuple[int, int]) -> ndarray
Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def _fit_region_into_img_size(region_xyxy: np.ndarray, img_shape_hw: Tuple[int, int]) -> np.ndarray:
    H, W = img_shape_hw
    clipped_region_xyxy = region_xyxy.copy()
    clipped_region_xyxy[[0, 2]] = np.clip(region_xyxy[[0, 2]], 0, W)
    clipped_region_xyxy[[1, 3]] = np.clip(region_xyxy[[1, 3]], 0, H)
    return clipped_region_xyxy
_check_region_xyxy_is_valid
_check_region_xyxy_is_valid(region_xyxy: ndarray) -> bool
Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def _check_region_xyxy_is_valid(region_xyxy: np.ndarray) -> bool:
    return region_xyxy[0] < region_xyxy[2] and region_xyxy[1] < region_xyxy[3]

roi_handler

ROIHandler
ROIHandler(**kwargs)

ROI 기능을 수행합니다. 이미지에 대한 ROI 좌표를 계산해 크롭하고, 크롭된 이미지에 blind_mask를 적용해 마스크 영역의 픽셀 값을 0으로 치환합니다.

Source code in SaigeToolkit/data/transform/roi/roi_handler.py
def __init__(self, **kwargs) -> None:
    self.set(**kwargs)
roi_calculator_types class-attribute instance-attribute
roi_calculator_types = {'simple': RelativeBoxROI, 'advanced': PixelIntensityROI, 'auto': AutoRelativeBoxROI}
roi_calculator instance-attribute
roi_calculator: ROICalculator
set
set(mode: str, blind_mask: Union[None, ndarray, str], image_only: bool = False, discard_outer_polygons: bool = False, det_blind_mask_threshold: float = 0.5, **kwargs)
Source code in SaigeToolkit/data/transform/roi/roi_handler.py
def set(
    self,
    mode: str,
    blind_mask: Union[None, np.ndarray, str],
    image_only: bool = False,
    discard_outer_polygons: bool = False,
    det_blind_mask_threshold: float = 0.5,
    **kwargs,
):
    if mode not in self.roi_calculator_types:
        raise ROIModeError

    # mode 변경된 경우 roi_calculator 인스턴스 새로 생성, 그렇지 않은 경우 set 호출
    if not hasattr(self, "mode") or self.mode != mode:
        self.mode = mode
        self.roi_calculator = self.roi_calculator_types[mode](**kwargs)
    else:
        self.roi_calculator.set(**kwargs)

    if isinstance(blind_mask, str):
        blind_mask = np.array(Image.open(blind_mask))
    if blind_mask is not None and (blind_mask.dtype != np.uint8 or blind_mask.ndim != 2):
        raise ROIBlindMaskValueError
    self.blind_mask = blind_mask

    self.image_only = image_only

    self.discard_outer_polygons = discard_outer_polygons

    self.det_blind_mask_threshold = det_blind_mask_threshold
apply_crop
apply_crop(image: Union[Image, ndarray, List[Union[Image, ndarray]]], return_revert_params: bool = False, warmup: bool = False, **data) -> Union[Dict, Tuple[Dict, Dict]]
Source code in SaigeToolkit/data/transform/roi/roi_handler.py
def apply_crop(
    self,
    image: Union[Image.Image, np.ndarray, List[Union[Image.Image, np.ndarray]]],
    return_revert_params: bool = False,
    warmup: bool = False,
    **data,
) -> Union[Dict, Tuple[Dict, Dict]]:
    multipage = isinstance(image, list)

    roi_info = self.roi_calculator(
        image=image[0] if multipage else image,
        get_intermediate_results=False,
        warmup=warmup,
        **data,
    )
    roi_coordinates = roi_info["roi_coordinates"]  # [left, top, right, bottom]

    if multipage:
        image_size_before_roi = read_image_size(image[0])
        cropped_image = [crop(image_i, roi_coordinates) for image_i in image]
        image_size_after_roi = read_image_size(cropped_image[0])
    else:
        image_size_before_roi = read_image_size(image)
        cropped_image = crop(image, roi_coordinates)
        image_size_after_roi = read_image_size(cropped_image)

    data["image"] = cropped_image

    revert_params = {
        "image_size_before_roi": image_size_before_roi,
        "roi_coordinates": roi_coordinates,
        "image_size_after_roi": image_size_after_roi,
    }

    if self.image_only:
        return (data, revert_params) if return_revert_params else data

    # 라벨 크롭 & 마스킹
    if "mask" in data:
        data["mask"] = crop(data["mask"], roi_coordinates)

    if "bboxes" in data:
        data["bboxes"] = crop_box(data["bboxes"], roi_coordinates)

    if "polygons" in data:
        # NOTE: polygons에는 blind_mask가 적용되지 않습니다.
        new_polygons = []
        new_data = {key: [] for key in ["strings", "ignore"] if key in data}
        # TODO: add any keys to be updated which should have same length with polygons.

        for idx, polygon in enumerate(data["polygons"]):
            if self.discard_outer_polygons and not self._check_polygon_within_box(
                polygon, roi_coordinates
            ):
                continue

            new_polygons.append(polygon)
            for k, v in new_data.items():
                v.append(data[k][idx])

        new_polygons = translate_polygon(
            polygons=new_polygons,
            offset=roi_coordinates[:2],
        )

        data.update({"polygons": new_polygons, **new_data})

    return (data, revert_params) if return_revert_params else data
apply_mask
apply_mask(image: Union[Image, ndarray, List], **data)
Source code in SaigeToolkit/data/transform/roi/roi_handler.py
def apply_mask(
    self,
    image: Union[Image.Image, np.ndarray, List],
    **data,
):
    if self.blind_mask is None:
        data["image"] = image
        return data

    multipage = isinstance(image, List)

    # cv2 resize: 약간 부정확하지만 빠름. 여기서는 아주 정확할 필요없음.
    image_size = read_image_size(image[0] if multipage else image)
    blind_mask = cv2.resize(self.blind_mask, dsize=image_size, interpolation=cv2.INTER_NEAREST)
    bool_mask = blind_mask > 0

    if multipage:
        data["image"] = [fill_pixels_with_mask(image_i, bool_mask, value=0) for image_i in image]
    else:
        data["image"] = fill_pixels_with_mask(image, bool_mask, value=0)

    if self.image_only:
        return data

    # 라벨 크롭 & 마스킹
    if "mask" in data:
        data["mask"] = fill_pixels_with_mask(data["mask"], bool_mask, value=0)

    if "bboxes" in data:
        indices_alive = []

        if len(data["bboxes"]):
            xyxy_bboxes = data["bboxes"].convert_coordinate("xyxy")

            for i, bbox in enumerate(xyxy_bboxes):
                x0, y0, x1, y1 = bbox

                label_area = (x1 - x0) * (y1 - y0)
                overlapping_area = np.sum(blind_mask[int(y0) : int(y1), int(x0) : int(x1)])

                if overlapping_area / label_area < self.det_blind_mask_threshold:
                    indices_alive.append(i)

        data["bboxes"] = data["bboxes"][indices_alive]

        if "labels" in data:
            data["labels"] = data["labels"][indices_alive]

    return data
__call__
__call__(return_revert_params: bool = False, warmup: bool = False, **data) -> Union[Dict, Tuple[Dict, Dict]]
Source code in SaigeToolkit/data/transform/roi/roi_handler.py
def __call__(
    self,
    return_revert_params: bool = False,
    warmup: bool = False,
    **data,
) -> Union[Dict, Tuple[Dict, Dict]]:
    if return_revert_params:
        data, revert_params = self.apply_crop(return_revert_params=True, warmup=warmup, **data)
        data = self.apply_mask(**data)
        return data, revert_params
    else:
        data = self.apply_crop(return_revert_params=False, warmup=warmup, **data)
        data = self.apply_mask(**data)
        return data
_check_polygon_within_box
_check_polygon_within_box(polygon: List[List[float]], roi_coordinates: List[int]) -> bool
Source code in SaigeToolkit/data/transform/roi/roi_handler.py
def _check_polygon_within_box(self, polygon: List[List[float]], roi_coordinates: List[int]) -> bool:
    left, top, right, bottom = roi_coordinates

    def check_x_cord(p):
        return p >= left and p < right

    def check_y_cord(p):
        return p >= top and p < bottom

    def check_point_within_box(point_xy):
        x, y = point_xy
        return check_x_cord(x) and check_y_cord(y)

    return all(check_point_within_box(p) for p in polygon)

transform

End-to-end 데이터 변환을 지원하는 모듈입니다.

Transform

Transform(image_mode: Union[str, List[str]] = 'RGB', inspection_size_wh: Optional[ImageSizeType] = None, roi: Optional[Dict] = None, resize: Optional[Dict] = None, augmentation: Optional[Dict] = None, roi_mask_first: bool = True)
Source code in SaigeToolkit/data/transform/transform.py
def __init__(
    self,
    image_mode: Union[str, List[str]] = "RGB",
    inspection_size_wh: Optional[ImageSizeType] = None,
    roi: Optional[Dict] = None,
    resize: Optional[Dict] = None,
    augmentation: Optional[Dict] = None,
    roi_mask_first: bool = True,  # 하위 호환성을 위해 roi_mask_first=True를 기본값으로 설정
):
    self.roi_mask_first = roi_mask_first
    self.image_loader = ImageLoader(image_mode=image_mode)
    self.roi_handler = ROIHandler(**roi) if roi is not None else None
    self.base_resizer = Resizer(**resize) if resize is not None else None

    # inspection_size_wh 설정 시 그 값에 따라 초기화
    self.inspection_size_resizer: Optional[InspectionSizeResizer] = None
    self.stacked_resizer_handler: StackedResizerHandler = None

    # inspection_size_wh setter에서 inspection_size_resizer와 stacked_resizer_handler 생성
    self.inspection_size_wh = inspection_size_wh

    if augmentation is None:
        self.augmentation = None
    elif "_target_" in augmentation:
        self.augmentation = build_augmentation(augmentation)
    else:
        raise NotImplementedError("Old version of augmentation config is not supported.")
roi_mask_first instance-attribute
roi_mask_first = roi_mask_first
image_loader instance-attribute
image_loader = ImageLoader(image_mode=image_mode)
roi_handler instance-attribute
roi_handler = ROIHandler(**roi) if roi is not None else None
base_resizer instance-attribute
base_resizer = Resizer(**resize) if resize is not None else None
inspection_size_resizer instance-attribute
inspection_size_resizer: Optional[InspectionSizeResizer] = None
stacked_resizer_handler instance-attribute
stacked_resizer_handler: StackedResizerHandler = None
augmentation instance-attribute
augmentation = None
inspection_size_wh property writable
inspection_size_wh: Optional[ImageSizeType]
Operation

Bases: str, Enum

ROI class-attribute instance-attribute
ROI = auto()
InspectionSize class-attribute instance-attribute
InspectionSize = auto()
Resize class-attribute instance-attribute
Resize = auto()
__call__
__call__(data: Dict, warmup: bool = False) -> Dict

data Dict에 transform을 적용합니다.

Parameters:

  • data (Dict) –

    data Dictionary

  • warmup (bool, default: False ) –

    InferenceHandler warmup시에 사용하는 파라미터 입니다. True일 경우, 현재 transform을 적용했을 때 나올 수 있는 가장 큰 image size로 transform을 적용합니다. Transform operation 중 ROI의 경우 input image에 따라 output size가 매번 바뀔 수 있기 때문에 해당 옵션이 추가되었습니다. Defaults to False.

Returns:

  • Dict ( Dict ) –

    transform이 적용된 데이터 Dict입니다.

Source code in SaigeToolkit/data/transform/transform.py
def __call__(self, data: Dict, warmup: bool = False) -> Dict:
    """data Dict에 transform을 적용합니다.

    Args:
        data (Dict): data Dictionary
        warmup (bool, optional): InferenceHandler warmup시에 사용하는 파라미터 입니다. True일 경우,
                                    현재 transform을 적용했을 때 나올 수 있는 가장 큰 image size로
                                    transform을 적용합니다.
                                 Transform operation 중 ROI의 경우 input image에 따라 output size가
                                    매번 바뀔 수 있기 때문에 해당 옵션이 추가되었습니다.
                                 Defaults to False.

    Returns:
        Dict: transform이 적용된 데이터 Dict입니다.
    """
    # transform_params 설정
    if "transform_params" not in data:
        data["transform_params"] = {
            "operation_stack": [
                self.Operation.ROI,
                self.Operation.InspectionSize,
                self.Operation.Resize,
            ],
            "operation_params": {},
        }

    operation_params: Dict = data["transform_params"]["operation_params"]

    # load PIL Image
    data = self.image_loader(**data)

    # 원본 이미지가 inspection_size를 넘지 않도록 resize 했을 때의 image scale 계산
    if self.inspection_size_resizer is not None:
        self.inspection_size_resizer.set_scale_from_data(**data)

    # ROI crop 적용
    if self.roi_handler is not None:
        data, roi_revert_params = self.roi_handler.apply_crop(
            return_revert_params=True, warmup=warmup, **data
        )
        if self.roi_mask_first:
            data = self.roi_handler.apply_mask(**data)

        if self.Operation.ROI in operation_params:
            raise KeyError
        operation_params[self.Operation.ROI] = roi_revert_params

    # inspection_size와 resize_factor를 하나로 묶어서 resize
    data, revert_param_list = self.stacked_resizer_handler(return_revert_params=True, **data)
    inspection_size_revert_params, resizer_revert_params = revert_param_list

    if inspection_size_revert_params is not None:
        if self.Operation.InspectionSize in operation_params:
            raise KeyError
        operation_params[self.Operation.InspectionSize] = inspection_size_revert_params

    if resizer_revert_params is not None:
        if self.Operation.Resize in operation_params:
            raise KeyError
        operation_params[self.Operation.Resize] = resizer_revert_params

    # # ROI blind mask 적용
    if self.roi_handler is not None and not self.roi_mask_first:
        data = self.roi_handler.apply_mask(**data)

    # data augmentation
    if self.augmentation is not None:
        data = self.augmentation(**data)

    return data
get_resize_scale classmethod
get_resize_scale(transform_params: Dict) -> List[float]

before_transform_image_size -> after_transform_input_size가 되기 위한 scale을 구합니다. before_transform_image_size * scale = after_transform_input_size

Parameters:

  • transform_params (Dict) –

    transform시에 저장해둔 parameter 입니다.

Returns:

  • List[float]

    List[float]: before_transform_image_size * scale = after_transform_input_size인 scale scale: [scale_width, scale_height]

Source code in SaigeToolkit/data/transform/transform.py
@classmethod
def get_resize_scale(cls, transform_params: Dict) -> List[float]:
    """before_transform_image_size -> after_transform_input_size가 되기 위한 scale을 구합니다.
    before_transform_image_size * scale = after_transform_input_size

    Args:
        transform_params (Dict): transform시에 저장해둔 parameter 입니다.

    Returns:
        List[float]: before_transform_image_size * scale = after_transform_input_size인 scale
                     scale: [scale_width, scale_height]
    """
    operation_params: Dict = transform_params["operation_params"]
    inspection_size_revert_params = operation_params.get(cls.Operation.InspectionSize, None)
    resizer_revert_params = operation_params.get(cls.Operation.Resize, None)

    if inspection_size_revert_params is None and resizer_revert_params is None:
        resize_scale = [1.0, 1.0]
    else:
        if inspection_size_revert_params is None:
            image_size_before_resize = resizer_revert_params["image_size_before_resize"]
            image_size_after_resize = resizer_revert_params["image_size_after_resize"]
        elif resizer_revert_params is None:
            image_size_before_resize = inspection_size_revert_params["image_size_before_resize"]
            image_size_after_resize = inspection_size_revert_params["image_size_after_resize"]
        else:
            image_size_before_resize = inspection_size_revert_params["image_size_before_resize"]
            image_size_after_resize = resizer_revert_params["image_size_after_resize"]

        resize_scale = (
            np.array(image_size_after_resize) / np.array(image_size_before_resize)
        ).tolist()

    return resize_scale
_get_next_revert_operation staticmethod
_get_next_revert_operation(transform_params: Dict) -> Optional[Operation]

다음으로 할 revert operation을 가져옵니다. operation_stack에서 operation이 제거되지는 않습니다.

Parameters:

  • transform_params (Dict) –

    transform시에 저장해둔 parameter 입니다.

Returns:

  • Optional[Operation]

    Optional[Operation]: 남아있는 revert operation이 있으면 operation을 없으면 None을 반환합니다.

Source code in SaigeToolkit/data/transform/transform.py
@staticmethod
def _get_next_revert_operation(transform_params: Dict) -> Optional[Operation]:
    """다음으로 할 revert operation을 가져옵니다. operation_stack에서 operation이 제거되지는 않습니다.

    Args:
        transform_params (Dict): transform시에 저장해둔 parameter 입니다.

    Returns:
        Optional[Operation]: 남아있는 revert operation이 있으면 operation을 없으면 None을 반환합니다.
    """
    operation_stack: List = transform_params["operation_stack"]
    next_operation = None
    if len(operation_stack) > 0:
        next_operation = operation_stack[-1]

    return next_operation
revert classmethod
revert(data: Dict, transform_params: Dict, operation: Optional[Operation] = None) -> Dict

summary

Parameters:

  • data (Dict) –

    revert operation을 적용할 data dictionary입니다. data는 다음과 같은 구조를 지닙니다. { "key1": { "data" (Union[np.ndarray, torch.Tensor, List[Dict]]): 실제 data입니다. "data_type" (str): 해당 data의 type 입니다. 현재 ["array", "objects"]를 지원합니다. }, "key2": { "data" (Union[np.ndarray, torch.Tensor, List[Dict]]): 실제 data입니다. "data_type" (str): 해당 data의 type 입니다. 현재 ["array", "objects"]를 지원합니다. }, ... }

  • transform_params (Dict) –

    transform시에 저장해둔 parameter 입니다.

  • operation (Optional[Operation], default: None ) –

    transform에서 revert가 가능한 operation 입니다. Enum class인 Transform.Operation에 있는 항목들을 지원합니다. operation이 None이 아닐 시, 해당 operation 까지 revert를 적용하고, None일 시, 그 다음 revert operation을 적용합니다. Defaults to None.

Raises:

  • RevertOperationNotFoundError

    args로 넣은 operation이 남은 revert operation 중에 없을 때 에러를 발생합니다.

Returns:

  • Dict ( Dict ) –

    revert operation이 적용된 data dictionary 입니다. 구조는 Args의 data와 같습니다.

Source code in SaigeToolkit/data/transform/transform.py
@classmethod
def revert(
    cls,
    data: Dict,
    transform_params: Dict,
    operation: Optional[Operation] = None,
) -> Dict:
    """_summary_

    Args:
        data (Dict): revert operation을 적용할 data dictionary입니다. data는 다음과 같은 구조를 지닙니다.
            {
                "key1": {
                    "data" (Union[np.ndarray, torch.Tensor, List[Dict]]): 실제 data입니다.
                    "data_type" (str): 해당 data의 type 입니다. 현재 ["array", "objects"]를 지원합니다.
                },
                "key2": {
                    "data" (Union[np.ndarray, torch.Tensor, List[Dict]]): 실제 data입니다.
                    "data_type" (str): 해당 data의 type 입니다. 현재 ["array", "objects"]를 지원합니다.
                },
                ...
            }
        transform_params (Dict): transform시에 저장해둔 parameter 입니다.
        operation (Optional[Operation], optional): transform에서 revert가 가능한 operation 입니다.
                                                    Enum class인 Transform.Operation에 있는 항목들을 지원합니다.
                                                    operation이 None이 아닐 시, 해당 operation 까지 revert를 적용하고,
                                                    None일 시, 그 다음 revert operation을 적용합니다.
                                                    Defaults to None.

    Raises:
        RevertOperationNotFoundError: args로 넣은 operation이 남은 revert operation 중에 없을 때 에러를 발생합니다.

    Returns:
        Dict: revert operation이 적용된 data dictionary 입니다. 구조는 Args의 data와 같습니다.
    """
    # operation이 None인 경우 다음 revert operation 하나를 진행
    if operation is None:
        data = cls._revert(data=data, transform_params=transform_params)
    # operation이 None이 아닌 경우
    else:
        # 해당 operation이 operation_stack에 있는지 확인
        operation_stack: List = transform_params["operation_stack"]
        if operation not in operation_stack:
            raise RevertOperationNotFoundError
        # 해당 operation까지 revert를 적용
        while cls._get_next_revert_operation(transform_params=transform_params) != operation:
            data = cls._revert(data=data, transform_params=transform_params)
        data = cls._revert(data=data, transform_params=transform_params)

    return data
_revert classmethod
_revert(data: Dict, transform_params: Dict) -> Dict
Source code in SaigeToolkit/data/transform/transform.py
@classmethod
def _revert(
    cls,
    data: Dict,
    transform_params: Dict,
) -> Dict:
    operation_stack: List = transform_params["operation_stack"]
    operation_params: Dict = transform_params["operation_params"]

    if cls._get_next_revert_operation(transform_params) is None:
        raise RevertOperationStackEmptyError

    next_revert_operation = operation_stack.pop()
    revert_params = operation_params.pop(next_revert_operation, None)

    next_revert_operation = {
        cls.Operation.ROI: cls._revert_roi,
        cls.Operation.Resize: cls._revert_resize,
        cls.Operation.InspectionSize: cls._revert_resize,
    }[next_revert_operation]

    result = {}
    for k, v in data.items():
        result[k] = {
            "data": next_revert_operation(revert_params=revert_params, **v),
            "data_type": v["data_type"],
        }

    return result
_revert_resize staticmethod
_revert_resize(data: Optional[Union[Tensor, ndarray, List[Dict]]], revert_params: Optional[Dict], data_type: str) -> Union[Tensor, ndarray, List[Dict]]
Source code in SaigeToolkit/data/transform/transform.py
@staticmethod
def _revert_resize(
    data: Optional[Union[torch.Tensor, np.ndarray, List[Dict]]],
    revert_params: Optional[Dict],
    data_type: str,
) -> Union[torch.Tensor, np.ndarray, List[Dict]]:
    if data is not None and revert_params is not None:
        image_size_before_resize = revert_params["image_size_before_resize"]
        image_size_after_resize = revert_params["image_size_after_resize"]

        if list(image_size_after_resize) != list(image_size_before_resize):
            if data_type == "array":
                data = resize_array(array=data, target_size=image_size_before_resize)
            elif data_type == "objects":
                resize_scale = (
                    np.array(image_size_before_resize) / np.array(image_size_after_resize)
                ).tolist()
                data = scale_segments_properties(segments_properties=data, scale=resize_scale)
            elif data_type == "polygons":
                data = resize_polygon(
                    polygons=data,
                    image_size=image_size_after_resize,
                    tw=image_size_before_resize[0],
                    th=image_size_before_resize[1],
                )
            else:
                raise NotImplementedError

    return data
_revert_roi staticmethod
_revert_roi(data: Optional[Union[Tensor, ndarray, List[Dict]]], revert_params: Optional[Dict], data_type: str) -> Union[Tensor, ndarray, List[Dict]]
Source code in SaigeToolkit/data/transform/transform.py
@staticmethod
def _revert_roi(
    data: Optional[Union[torch.Tensor, np.ndarray, List[Dict]]],
    revert_params: Optional[Dict],
    data_type: str,
) -> Union[torch.Tensor, np.ndarray, List[Dict]]:
    if data is not None and revert_params is not None:
        image_size_before_roi = revert_params["image_size_before_roi"]
        image_size_after_roi = revert_params["image_size_after_roi"]

        if list(image_size_before_roi) != list(image_size_after_roi):
            image_width, image_height = image_size_before_roi
            roi_left, roi_top, roi_right, roi_bottom = revert_params["roi_coordinates"]
            pad_left = roi_left
            pad_top = roi_top
            pad_right = image_width - roi_right
            pad_bottom = image_height - roi_bottom

            if data_type == "array":
                data = add_constant_margin_array(
                    array=data,
                    left=pad_left,
                    top=pad_top,
                    right=pad_right,
                    bottom=pad_bottom,
                    value=0,
                )
            elif data_type == "objects":
                data = translate_segments_properties(
                    segments_properties=data,
                    left=pad_left,
                    top=pad_top,
                )
            elif data_type == "polygons":
                data = translate_polygon(
                    polygons=data,
                    offset=(-pad_left, -pad_top),
                )
            else:
                raise NotImplementedError

    return data
is_oversized staticmethod
is_oversized(transform_params: Optional[Dict]) -> bool

InspectionSize 연산에서 resize 되었는지 여부를 판단

Source code in SaigeToolkit/data/transform/transform.py
@staticmethod
def is_oversized(transform_params: Optional[Dict]) -> bool:
    """InspectionSize 연산에서 resize 되었는지 여부를 판단"""
    if transform_params is None:
        return False

    inspection_size_params = get_tree_node_with_default(
        transform_params, ["operation_params", Transform.Operation.InspectionSize], None
    )
    if inspection_size_params is None:
        return False

    size1 = tuple(inspection_size_params["image_size_before_resize"])
    size2 = tuple(inspection_size_params["image_size_after_resize"])

    if size1 != size2:
        return True
    else:
        return False

typevar

ImageSizeType module-attribute

ImageSizeType = Sequence[int]

ParamsType module-attribute

ParamsType = Dict[str, Any]

ImageType module-attribute

ImageType = ndarray

MaskType module-attribute

MaskType = ndarray

BBoxesType module-attribute

BBoxesType = Union[ndarray, NumpyBoxes]

PolygonType module-attribute

PolygonType = List[ndarray]

OffsetType module-attribute

OffsetType = Tuple[float, float]