Skip to content

transform

data.transform.transform

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

ImageSizeType module-attribute

ImageSizeType = Sequence[int]

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)

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

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

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

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

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)

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

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

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

scale_segments_properties

scale_segments_properties(segments_properties: List[Dict], scale: Sequence[float]) -> List[Dict]
Source code in SaigeToolkit/data/dataclass/segment.py
def scale_segments_properties(segments_properties: List[Dict], scale: Sequence[float]) -> List[Dict]:
    return [
        scale_segment_properties(segment_properties, scale) for segment_properties in segments_properties
    ]

translate_segments_properties

translate_segments_properties(segments_properties: List[Dict], left: int, top: int, **kwargs) -> List[Dict]
Source code in SaigeToolkit/data/dataclass/segment.py
def translate_segments_properties(
    segments_properties: List[Dict],
    left: int,
    top: int,
    **kwargs,
) -> List[Dict]:
    return [
        translate_segment_properties(segment_properties, left, top, **kwargs)
        for segment_properties in segments_properties
    ]