Skip to content

api

data.transform.roi.api

error_handler module-attribute

error_handler = get_error_handler(SaigeToolkitError)

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)

ROIHandlerAPI

ROIHandlerAPI(**kwargs)

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

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

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

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

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

handler instance-attribute

handler = ROIHandler(**kwargs)

build classmethod

build(config: Dict) -> ROIHandlerAPI

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

Returns:

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

    Returns:
        ROIHandlerAPI: 빌드된 ROIHandlerAPI
    """
    return cls(**config)

set

set(config: Dict) -> None

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

Parameters:

  • config (Dict) –

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

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

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

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

apply

apply(image: ndarray) -> Dict

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

Parameters:

  • image (ndarray) –

    ROI를 적용할 입력 이미지.

Returns:

  • Dict ( Dict ) –

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

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

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

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

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