Skip to content

augment_function

data.transform.augmentation.augment_function

ImageType module-attribute

ImageType = ndarray

MaskType module-attribute

MaskType = ndarray

OffsetType module-attribute

OffsetType = tuple[float, float]

INTERPOLATE_METHOD_CV2 module-attribute

INTERPOLATE_METHOD_CV2 = {'nearest': INTER_NEAREST, 'linear': INTER_LINEAR, 'cubic': INTER_CUBIC}

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

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

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

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_CONSTANT, 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_CONSTANT,
    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, value: ndarray, inplace: bool = False)
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,
    value: np.ndarray,
    inplace: bool = False,
):
    if not inplace:
        image = image.copy()

    image[y : y + h, x : x + w, ...] = value
    return image.astype("uint8")

random_erase

random_erase(image: ImageType, scale: float = 0.02, aspect_ratio: float = 1.0, value: Union[tuple[int, int, int], None] = None, inplace: bool = False) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_gray
@support_rgba
def random_erase(
    image: ImageType,
    scale: float = 0.02,
    aspect_ratio: float = 1.0,
    value: Union[tuple[int, int, int], None] = None,
    inplace: bool = False,
) -> ImageType:

    check_value(scale, 0.0, 0.5)
    check_value(aspect_ratio, 0.2, 5.0)

    if scale == 0.0:
        return image.astype("uint8")

    img_height, img_width, _ = image.shape

    area = img_height * img_width * scale

    _h = math.sqrt(area * aspect_ratio)
    _w = math.sqrt(area / aspect_ratio)

    x = random.randint(0, max(0, img_width - int(_w)))
    y = random.randint(0, max(0, img_height - int(_h)))
    h = min(int(_h), img_height)
    w = min(int(_w), img_width)

    if value is None:
        # black or gray or random
        value_options = {
            "black": (0, 0, 0),
            "gray": (127, 127, 127),
            "random": "random",
        }
        value = value_options[random.choice(list(value_options.keys()))]

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

    if not inplace:
        image = image.copy()

    image[y : y + h, x : x + w, ...] = value
    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")

gauss_noise

gauss_noise(image: ImageType, intensity: float = 0.0, mean: float = 0.0, per_channel: bool = True)
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_gray
@support_rgba
def gauss_noise(
    image: ImageType,
    intensity: float = 0.0,
    mean: float = 0.0,
    per_channel: bool = True,
):
    var = intensity * 1000
    sigma = var**0.5

    if per_channel:
        gauss = random_utils.normal(mean, sigma, image.shape)
    else:
        gauss = random_utils.normal(mean, sigma, image.shape[:2])
        if len(image.shape) == 3:
            gauss = np.expand_dims(gauss, -1)

    return AF.gauss_noise(image, gauss=gauss)

get_advanced_blur_kernel

get_advanced_blur_kernel(ksize: int, sigmaX: float, sigmaY: float, angle: float, beta: float, noise_limit: Sequence[float]) -> Any
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def get_advanced_blur_kernel(
    ksize: int,
    sigmaX: float,
    sigmaY: float,
    angle: float,
    beta: float,
    noise_limit: Sequence[float],
) -> Any:
    # Generate mesh grid centered at zero.
    ax = np.arange(-ksize // 2 + 1.0, ksize // 2 + 1.0)
    # Shape (ksize, ksize, 2)
    grid = np.stack(np.meshgrid(ax, ax), axis=-1)

    # Calculate rotated sigma matrix
    d_matrix = np.array([[sigmaX**2, 0], [0, sigmaY**2]])
    u_matrix = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])
    sigma_matrix = np.dot(u_matrix, np.dot(d_matrix, u_matrix.T))

    inverse_sigma = np.linalg.inv(sigma_matrix)
    # Described in "Parameter Estimation For Multivariate Generalized Gaussian Distributions"
    kernel = np.exp(-0.5 * np.power(np.sum(np.dot(grid, inverse_sigma) * grid, 2), beta))
    # Add noise
    noise_matrix = np.random.uniform(*noise_limit, size=[ksize, ksize])
    kernel = kernel * noise_matrix

    # Normalize kernel
    kernel = kernel.astype(np.float32) / np.sum(kernel)

    return kernel

advanced_blur

advanced_blur(image: ImageType, ksize: int = 1)
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
@support_gray
@support_rgba
def advanced_blur(
    image: ImageType,
    ksize: int = 1,
):
    if ksize % 2 == 0:
        ksize = ksize + 1

    kernel = get_advanced_blur_kernel(
        ksize=ksize,
        sigmaX=0.3 * ((ksize - 1) * 0.5 - 1) + 0.8,
        sigmaY=0.3 * ((ksize - 1) * 0.5 - 1) + 0.8,
        angle=np.deg2rad(np.random.uniform(-180, 180)),
        beta=1.0,
        noise_limit=(0.9, 1.1),
    )
    return AF.convolve(image, kernel=kernel)

elastic_transform

elastic_transform(image: ImageType, intensity: float = 0.0, alpha: float = 400.0, alpha_affine: float = 0.0, interpolation: int = cv2.INTER_LINEAR, border_mode: int = cv2.BORDER_REFLECT_101, value: Union[int, float, List[int], List[float]] = 0, approximate: bool = False, same_dxdy: bool = False) -> ImageType
Source code in SaigeToolkit/data/transform/augmentation/augment_function.py
def elastic_transform(
    image: ImageType,
    intensity: float = 0.0,
    alpha: float = 400.0,
    alpha_affine: float = 0.0,
    interpolation: int = cv2.INTER_LINEAR,
    border_mode: int = cv2.BORDER_REFLECT_101,
    value: Union[int, float, List[int], List[float]] = 0,
    approximate: bool = False,
    same_dxdy: bool = False,
) -> ImageType:

    sigma = 15 * (1 - intensity)
    return AFGeometric.elastic_transform(
        img=image,
        alpha=alpha,
        sigma=sigma,
        alpha_affine=alpha_affine,
        interpolation=interpolation,
        border_mode=border_mode,
        value=value,
        approximate=approximate,
        same_dxdy=same_dxdy,
    )