Skip to content

transform

data.transform.transform

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

ImageSizeType module-attribute

ImageSizeType = tuple[int, int]

InspectionSizeResizer

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

Bases: ScaleResizer

원본 이미지가 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:

    validate_inspection_size_wh(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)

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_w, input_h = self.compute_input_size(data)
        inspection_w, inspection_h = self.inspection_size_wh
        self.scale = min(inspection_w / input_w, inspection_h / input_h, 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, ignore_round: bool = False, **data) -> Union[Dict, Tuple[Dict, Dict]]
Source code in SaigeToolkit/data/transform/resize.py
def __call__(
    self, return_revert_params: bool = False, ignore_round: 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, ignore_round)
                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

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
    if blind_mask is not None:
        if np.sum(blind_mask) / blind_mask.size > 0.3:
            self.masking_method = "np_where"
        else:
            # blind mask 영역 비율이 적을수록 inplace 방식이 빠름
            self.masking_method = "inplace"
    else:
        self.masking_method = None

    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 roi_info["do_crop"] is False:
        revert_params = {
            "image_size_before_roi": (roi_coordinates[2], roi_coordinates[3]),
            "roi_coordinates": roi_coordinates,
            "image_size_after_roi": (roi_coordinates[2], roi_coordinates[3]),
        }
        data["image"] = image
        return (data, revert_params) if return_revert_params else data

    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,
                method=self.masking_method,
            )
            for image_i in image
        ]
    else:
        data["image"] = fill_pixels_with_mask(
            image,
            bool_mask,
            value=0,
            method=self.masking_method,
        )

    if self.image_only:
        return data

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

    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: ndarray, roi_coordinates: List[int]) -> bool
Source code in SaigeToolkit/data/transform/roi/roi_handler.py
def _check_polygon_within_box(self, polygon: np.ndarray, 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_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

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_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
    """
    if path.endswith(SAIGE_GENERATED_IMAGE_EXTENSIONS):
        image = self._load_from_encrypted_file_path(path)
    else:
        image = self._load_from_plain_file_path(path)

    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_plain_file_path staticmethod

_load_from_plain_file_path(path: str) -> Image

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:

  • Image

    Image.Image: image

Source code in SaigeToolkit/data/transform/image_load.py
@staticmethod
def _load_from_plain_file_path(path: str) -> Image.Image:
    """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:
        Image.Image: image
    """

    try:
        with open(path, "rb") as f:
            image = Image.open(f)
            image = ImageOps.exif_transpose(image)
    except Exception as e:
        raise InvalidImageFileError(f"[path] {path}") from e
    return image

_load_from_encrypted_file_path staticmethod

_load_from_encrypted_file_path(path: str) -> Image

Loading function using PIL.Image library and decrypting encrypted image file.

Parameters:

  • path (str) –

    encrypted image file path

Returns:

  • Image

    Image.Image: decrypted image

Source code in SaigeToolkit/data/transform/image_load.py
@staticmethod
def _load_from_encrypted_file_path(path: str) -> Image.Image:
    """Loading function using PIL.Image library and decrypting encrypted image file.

    Args:
        path (str): encrypted image file path

    Returns:
        Image.Image: decrypted image
    """
    try:
        image = decrypt_load_image(path)
    except Exception as e:
        raise InvalidImageFileError(f"[path] {path}") from e
    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

OversizedImageHandling

Bases: Enum

RESIZE_TO_FIT class-attribute instance-attribute

RESIZE_TO_FIT = 'resize_to_fit'

DO_NOT_RESIZE class-attribute instance-attribute

DO_NOT_RESIZE = 'do_not_resize'

values classmethod

values() -> List[str]
Source code in SaigeToolkit/data/transform/transform.py
@classmethod
def values(cls) -> List[str]:
    return [item.value for item in cls]

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, oversized_image_handling: str = 'resize_to_fit', skip_round_resize_if_undersized: bool = False)
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를 기본값으로 설정
    oversized_image_handling: str = "resize_to_fit",  # 하위 호환성을 위해 기본값을 "resize_to_fit"으로 설정
    skip_round_resize_if_undersized: bool = False,  # 하위 호환성을 위해 기본값을 False로 설정
):
    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 = build_resizer(**resize) if resize is not None else None

    # inspection_size_wh와 oversized_image_handling에 따라서 inspection_size_resizer와 stacked_resizer_handler를 설정합니다.
    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

    self.oversized_image_handling = oversized_image_handling
    self.skip_round_resize_if_undersized = skip_round_resize_if_undersized

    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 = build_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

skip_round_resize_if_undersized instance-attribute

skip_round_resize_if_undersized = skip_round_resize_if_undersized

augmentation instance-attribute

augmentation = None

inspection_size_wh property writable

inspection_size_wh: Optional[ImageSizeType]

oversized_image_handling property writable

oversized_image_handling: OversizedImageHandling

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()

_set_inspection_size_resizer_and_stacked_resizer_handler

_set_inspection_size_resizer_and_stacked_resizer_handler() -> None

inspection_size_wh와 oversized_image_handling에 따라서 inspection_size_resizer와 stacked_resizer_handler 설정합니다.

Source code in SaigeToolkit/data/transform/transform.py
def _set_inspection_size_resizer_and_stacked_resizer_handler(self) -> None:
    """
    inspection_size_wh와 oversized_image_handling에 따라서 inspection_size_resizer와 stacked_resizer_handler 설정합니다.
    """

    # XXX: inspection size가 설정되어 있고, oversized_image_handling이 resize_to_fit일 경우에 InspectionSizeResizer 생성합니다.
    #     inspection size가 설정되어 있어도 oversized_image_handling이 do_not_resize일 경우에는 inspection_size에 맞추는 resize를 하지 않습니다.
    if (
        self.inspection_size_wh
        and self.oversized_image_handling == OversizedImageHandling.RESIZE_TO_FIT
    ):
        self.inspection_size_resizer = InspectionSizeResizer(
            inspection_size_wh=self.inspection_size_wh, resizer=self.base_resizer
        )
    else:
        self.inspection_size_resizer = None

    self.stacked_resizer_handler = StackedResizerHandler(
        resizer_list=[self.inspection_size_resizer, self.base_resizer]
    )

__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)

    # image가 설정된 inspection_size_wh보다 작은지 확인합니다.
    _undersized = False
    if self.inspection_size_wh:
        multipage = isinstance(data["image"], List)
        input_size = read_image_size(data["image"][0] if multipage else data["image"])

        _undersized = (input_size[0] < self.inspection_size_wh[0]) and (
            input_size[1] < self.inspection_size_wh[1]
        )

    # XXX: 원본 이미지가 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

    ignore_round_resize = False
    # XXX: inspection size보다 작고, skip_round_resize_if_undersized가 True일 경우 round resize를 skip합니다.
    # Transform 바깥에서 작은 이미지에 padding 처리를 하는 경우, 시간 손실을 줄이기 위하여 추가되었습니다.
    if _undersized and self.skip_round_resize_if_undersized:
        ignore_round_resize = True

    # inspection_size와 resize_factor를 하나로 묶어서 resize
    data, revert_param_list = self.stacked_resizer_handler(
        return_revert_params=True, ignore_round=ignore_round_resize, **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

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)

validate_inspection_size_wh

validate_inspection_size_wh(inspection_size_wh: ImageSizeType) -> None

inspection_size_wh가 유효한지 확인합니다. inspection_size_wh는 (width, height)로 모두 1 이상의 int여야 합니다.

유효하지 않을 경우, error를 발생시킵니다.

Parameters:

  • inspection_size_wh (Optional[ImageSizeType]) –

    검사할 inspection_size_wh.

Raises:

  • InspectionSizeTypeError

    맞지 않는 타입일 경우 발생합니다.

  • InspectionSizeValueError

    값이 1 이상이 아닐 경우 발생합니다.

Source code in SaigeToolkit/data/transform/parameter_validation.py
def validate_inspection_size_wh(inspection_size_wh: ImageSizeType) -> None:
    """inspection_size_wh가 유효한지 확인합니다.
    inspection_size_wh는 (width, height)로 모두 1 이상의 int여야 합니다.

    유효하지 않을 경우, error를 발생시킵니다.

    Args:
        inspection_size_wh (Optional[ImageSizeType], optional): 검사할 inspection_size_wh.

    Raises:
        InspectionSizeTypeError: 맞지 않는 타입일 경우 발생합니다.
        InspectionSizeValueError: 값이 1 이상이 아닐 경우 발생합니다.
    """
    # 타입 체크
    valid_type = inspection_size_wh is None or (  # inspection_size는 None 이거나 혹은
        len(inspection_size_wh) == 2  # length가 2이면서 (width, height)
        and all([isinstance(v, int) for v in inspection_size_wh])  # 값은 모두 int여야 한다.
    )
    if not valid_type:
        raise InspectionSizeTypeError

    # 값 체크, 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

resize_polygon

resize_polygon(polygons: PolygonType, image_size: Sequence[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: Sequence[int], tw: int, th: int) -> PolygonType:
    """Resize polygon from (w, h) to (tw, th)"""
    if len(polygons) == 0:
        return polygons

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

    dtype = polygons[0].dtype

    w_scale = tw / w
    h_scale = th / h

    new_polygons = deepcopy(polygons)

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

    return new_polygons

translate_polygon

translate_polygon(polygons: PolygonType, offset: Sequence[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: Sequence[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_resizer

build_resizer(**kwargs) -> Resizer
Source code in SaigeToolkit/data/transform/resize.py
def build_resizer(**kwargs) -> "Resizer":

    # Check if multiple resizer parameters are given
    resizer_params = [kwargs.get(key) for key in Resizer.registry.keys()]
    if sum(param is not None for param in resizer_params) > 1:
        raise ResizerParameterValueError

    # Return the resizer instance if the resizer type is given
    for resizer_type in Resizer.registry.keys():
        if kwargs.get(resizer_type) is not None:
            return Resizer.registry[resizer_type](**kwargs)

    # Return the default resizer instance if no resizer type is given
    return Resizer(**kwargs)

read_image_size

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

image size: (W, H)

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

resize_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
    ]