Skip to content

roi_handler

data.transform.roi.roi_handler

ROICalculator

Bases: ABC

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

RelativeBoxROI

RelativeBoxROI(**kwargs)

Bases: ROICalculator

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

Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def __init__(self, **kwargs) -> None:
    self.set(**kwargs)

PixelIntensityROI

PixelIntensityROI(**kwargs)

Bases: ROICalculator

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

Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def __init__(self, **kwargs) -> None:
    self.set(**kwargs)

AutoRelativeBoxROI

AutoRelativeBoxROI(padding: str = 'medium')

Bases: RelativeBoxROI

RelativeBoxROI class with automatic coordinate calculation.

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

Attributes:

  • left (float) –

    ROI coordinates.

  • top (float) –

    ROI coordinates.

  • right (float) –

    ROI coordinates.

  • bottom (float) –

    ROI coordinates.

  • is_ready (bol) –

    Whether auto ROI coordinates is set.

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

    dataset image size.

  • discard_outer_polygons (bool) –

    Flag for discarding polygons outside of current ROI region.

  • expand_ratio

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

Example

in dataset building...

@property def files(self): return self._files

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

if isinstance(self.transform.roi_handler.roi_calculator, AutoRelativeBoxROI):
    self.transform.roi_handler.roi_calculator.autoupdate_roi_coordinate(self)
                                                                        self is dataset

```

Source code in SaigeToolkit/data/transform/roi/roi_calculator.py
def __init__(self, padding: str = "medium"):
    self.__dict__ = self.__shared_state

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

    self.expand_ratio = self.REGION_EXPAND_RATIO[padding]

autoupdate_roi_coordinate

autoupdate_roi_coordinate(dataset: Dataset) -> bool

Automatically update ROI coordinate using dataset information.

Returns success flag.

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

    Returns success flag.
    """

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

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

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

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

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

        self.image_hw = current_img_hw

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

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

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

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

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

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

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

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

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

    return True

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)

crop_box

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

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

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

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

crop

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

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

Parameters:

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

    image

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

    [left, top, right, bottom]

Returns:

  • Union[Image, Tensor, ndarray]

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

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

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

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

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

fill_pixels_with_mask

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

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

Parameters:

  • image (Union[Image, ndarray]) –

    image (HW or HWC)

  • bool_mask (ndarray) –

    boolean mask (HW)

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

    . Defaults to 0.

Returns:

  • Union[Image, ndarray]

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

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

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

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

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

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

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

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

    if is_pil:
        image = Image.fromarray(image)

    return image