Skip to content

edge_cropper

data.crop.edge_cropper

Crop image patches along the contour of mask (or polygon)

Imported from DefectGeneration repository. generation/data/crop/edge_cropper.py

logger module-attribute

logger = getLogger('SaigeResearch')

BaseCropper

Bases: ABC

WindowState

Bases: Enum

relation state enum between center point and target point

WindowManager

WindowManager(width: int = 256, height: int = 256, strict_inner_patch: bool = True)

Manager for cropping window size, relation and window coordinates.

Attributes:

  • width (int) –

    window width for image patch.

  • half_width (int) –

    half of window width for image patch.

  • height (int) –

    window height for image patch.

  • half_height (int) –

    half of window height for image patch.

  • strict_inner_patch (bool) –

    whether not allowing outer area of image.

Source code in SaigeToolkit/data/crop/edge_cropper.py
def __init__(
    self,
    width: int = 256,
    height: int = 256,
    strict_inner_patch: bool = True,
):
    self.width = width
    self.half_width = width // 2
    self.height = height
    self.half_height = height // 2
    self.strict_inner_patch = strict_inner_patch

check_window

check_window(point: Iterable[int], center: Iterable[int]) -> WindowState

check whether target point is inside of window, returns WindowState

Parameters:

  • point (Iterable[int]) –

    target point to be checked

  • center (Iterable[int]) –

    reference center point

Returns:

Source code in SaigeToolkit/data/crop/edge_cropper.py
def check_window(
    self, point: Iterable[int], center: Iterable[int]
) -> WindowState:  # XXX: is this ok?
    """check whether target point is inside of window, returns WindowState

    Args:
        point (Iterable[int]): target point to be checked
        center (Iterable[int]): reference center point

    Returns:
        WindowState: relation state enum
    """
    width_distance = point[0] - center[0]
    abs_width_distance = abs(width_distance)
    height_distance = point[1] - center[1]
    abs_height_distance = abs(height_distance)

    if abs_width_distance <= self.half_width and abs_height_distance <= self.half_height:
        return WindowState.IN

    if abs_width_distance > abs_height_distance:
        if width_distance > 0:
            return WindowState.WIDTH_POSITIVE_OUT
        else:
            return WindowState.WIDTH_NEGATIVE_OUT
    else:
        if height_distance > 0:
            return WindowState.HEIGHT_POSITIVE_OUT
        else:
            return WindowState.HEIGHT_NEGATIVE_OUT

is_in_window

is_in_window(point: Iterable[int], center: Iterable[int]) -> bool

is target point inside of window

Parameters:

  • point (Iterable[int]) –

    target point to be checked

  • center (Iterable[int]) –

    reference center point

Returns:

  • bool ( bool ) –

    boolean result

Source code in SaigeToolkit/data/crop/edge_cropper.py
def is_in_window(self, point: Iterable[int], center: Iterable[int]) -> bool:
    """is target point inside of window

    Args:
        point (Iterable[int]): target point to be checked
        center (Iterable[int]): reference center point

    Returns:
        bool: boolean result
    """
    window_state = self.check_window(point, center)
    return window_state == WindowState.IN

is_in_any_window

is_in_any_window(point: Iterable[int], centers: Iterable[Iterable[int]]) -> bool

is target point inside of any of windows

Parameters:

  • point (Iterable[int]) –

    target point to be checked

  • center (Iterable[int]) –

    list of reference center points

Returns:

  • bool ( bool ) –

    boolean result

Source code in SaigeToolkit/data/crop/edge_cropper.py
def is_in_any_window(self, point: Iterable[int], centers: Iterable[Iterable[int]]) -> bool:
    """is target point inside of any of windows

    Args:
        point (Iterable[int]): target point to be checked
        center (Iterable[int]): list of reference center points

    Returns:
        bool: boolean result
    """
    return any(self.is_in_window(point, c) for c in centers)

get_coordinates_from_center

get_coordinates_from_center(h_center: int, w_center: int, h: int, w: int) -> Tuple[int, int, int, int]

get left/right top/bottom coordinates from picked center point.

Parameters:

  • h_center (int) –

    picked center point h-coordinate

  • w_center (int) –

    picked center point w-coordinate

  • h (int) –

    image size height

  • w (int) –

    image size width

Returns:

  • Tuple[int, int, int, int]

    Tuple[int, int, int, int]: (crop_left, crop_top, crop_right, crop_bottom)

Source code in SaigeToolkit/data/crop/edge_cropper.py
def get_coordinates_from_center(
    self, h_center: int, w_center: int, h: int, w: int
) -> Tuple[int, int, int, int]:
    """get left/right top/bottom coordinates from picked center point.

    Args:
        h_center (int): picked center point h-coordinate
        w_center (int): picked center point w-coordinate
        h (int): image size height
        w (int): image size width

    Returns:
        Tuple[int, int, int, int]: (crop_left, crop_top, crop_right, crop_bottom)
    """
    if self.strict_inner_patch:
        w_center = np.clip(w_center, self.half_width, w - self.width + self.half_width)
        h_center = np.clip(h_center, self.half_height, h - self.height + self.half_height)
    crop_left = w_center - self.half_width
    crop_right = crop_left + self.width
    crop_top = h_center - self.half_height
    crop_bottom = crop_top + self.height
    return (crop_left, crop_top, crop_right, crop_bottom)

CenterWithSatellite dataclass

CenterWithSatellite(point: Iterable[int], window_manager: WindowManager, satellite: Optional[Iterable[int]] = None)

Center point with 'satellites'. 'Satellites' is a point within center point's window, but not the center point. Also intra distance between 'satellites' should shorter than window size.

Attributes:

  • coordinate (Iterable[int]) –

    center point coordinate

  • window_manager (WindowManager) –

    WindowManager

  • max_width_distance (int) –

    max width-wise positive distance of satellites

  • max_height_distance (int) –

    max height-wise positive distance of satellites

  • min_width_distance (int) –

    max width-wise negative distance of satellites

  • min_height_distance (int) –

    max height-wise negative distance of satellites

  • satellite_points (List[Dict[str, any]]) –

    satellite_points data

Source code in SaigeToolkit/data/crop/edge_cropper.py
def __init__(
    self,
    point: Iterable[int],
    window_manager: WindowManager,
    satellite: Optional[Iterable[int]] = None,
):
    self.coordinate = point
    self.window_manager = window_manager

    self.max_width_distance = 0
    self.max_height_distance = 0
    self.min_width_distance = 0
    self.min_height_distance = 0

    self.satellite_points = {}
    if satellite is not None:
        self.calculate_relation_as_satellite(satellite)

calculate_relation_as_satellite

calculate_relation_as_satellite(point: Iterable[int]) -> Union[Dict[str, Any], List[Dict[str, Any]]]

calculate relation between target point and center point. case 1: If target point is outside of center point's window, target point should be considered as next center point candidate.

case 2

Even when target point is in the window, check whether distance between satellites is larger than window size. If so, target point should be considered as next center point candidate.

Parameters:

  • point (Iterable[int]) –

    target point to be checked

Returns:

  • Union[Dict[str, Any], List[Dict[str, Any]]]

    Union[Dict[str, Any], List[Dict[str, Any]]]: Dict[str, Any]: simple relation between center point and target point List[Dict[str, Any]]: reltations between satellite points and target point

Source code in SaigeToolkit/data/crop/edge_cropper.py
def calculate_relation_as_satellite(
    self, point: Iterable[int]
) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
    """calculate relation between target point and center point.
    case 1:
        If target point is outside of center point's window,
        target point should be considered as next center point candidate.

    case 2:
        Even when target point is in the window,
        check whether distance between satellites is larger than window size.
        If so, target point should be considered as next center point candidate.

    Args:
        point (Iterable[int]): target point to be checked

    Returns:
        Union[Dict[str, Any], List[Dict[str, Any]]]:
            Dict[str, Any]: simple relation between center point and target point
            List[Dict[str, Any]]: reltations between satellite points and target point
    """
    width_distance = point[0] - self.coordinate[0]
    height_distance = point[1] - self.coordinate[1]
    relation = self.window_manager.check_window(point, self.coordinate)

    # target point is outside of center point's window.
    # Do not update satellite and return the relationship.
    if relation != WindowState.IN:
        return {"coordinate": self.coordinate, "relation": relation}

    satellites_intra_distance_errors = []
    if width_distance > self.max_width_distance:
        if abs(width_distance) + abs(self.min_width_distance) > self.window_manager.half_width:
            self._compare_point_and_calculate_error(
                "min_width", point, satellites_intra_distance_errors
            )
        else:
            # else, update as satellite point.
            self.max_width_distance = width_distance
            self.satellite_points.update({"max_width": {"coordinate": point, "relation": relation}})

    if height_distance > self.max_height_distance:
        if abs(height_distance) + abs(self.min_height_distance) > self.window_manager.half_height:
            self._compare_point_and_calculate_error(
                "min_height", point, satellites_intra_distance_errors
            )
        else:
            # else, update as satellite point.
            self.max_height_distance = height_distance
            self.satellite_points.update({"max_height": {"coordinate": point, "relation": relation}})

    if width_distance < self.min_width_distance:
        if abs(width_distance) + abs(self.max_width_distance) > self.window_manager.half_width:
            self._compare_point_and_calculate_error(
                "max_width", point, satellites_intra_distance_errors
            )
        else:
            # else, update as satellite point.
            self.min_width_distance = width_distance
            self.satellite_points.update({"min_width": {"coordinate": point, "relation": relation}})

    if height_distance < self.min_height_distance:
        if abs(height_distance) + abs(self.max_height_distance) > self.window_manager.half_height:
            self._compare_point_and_calculate_error(
                "max_height", point, satellites_intra_distance_errors
            )
        else:
            # else, update as satellite point.
            self.min_height_distance = height_distance
            self.satellite_points.update({"min_height": {"coordinate": point, "relation": relation}})

    if satellites_intra_distance_errors:
        # any distance between satellites larger than window
        return satellites_intra_distance_errors

    return {"coordinate": self.coordinate, "relation": relation}

_compare_point_and_calculate_error

_compare_point_and_calculate_error(key, point, satellites_intra_distance_errors)

Refactored from the original code from Defect Generation (Function extracted).

Source code in SaigeToolkit/data/crop/edge_cropper.py
def _compare_point_and_calculate_error(self, key, point, satellites_intra_distance_errors):
    """Refactored from the original code from Defect Generation (Function extracted)."""
    # distance between satellites is larger than window size.
    compare_point = (
        self.satellite_points[key]["coordinate"] if key in self.satellite_points else self.coordinate
    )
    _relation = self.window_manager.check_window(point, compare_point)
    satellites_intra_distance_errors.append({"coordinate": compare_point, "relation": _relation})

EdgeCropper

EdgeCropper(crop_w: int = 512, crop_h: int = 512, random_sample: bool = False, patch_per_polygon: int = 1, strict_inner_patch: bool = True, ok_patch_prob: float = 0.01)

Bases: BaseCropper

Crop image patches along the contour of mask (or polygon)

Attributes:

  • window_manager (WindowManager) –

    WindowManager

  • random_sample (bool) –

    whether randomly select the first point of each polygon

  • patch_per_polygon (bool) –

    number of repeat for cropping patches around each polygon.

Source code in SaigeToolkit/data/crop/edge_cropper.py
def __init__(
    self,
    crop_w: int = 512,
    crop_h: int = 512,
    random_sample: bool = False,
    patch_per_polygon: int = 1,
    strict_inner_patch: bool = True,
    ok_patch_prob: float = 0.01,
) -> None:
    self.crop_w = crop_w
    self.crop_h = crop_h
    self.window_manager = WindowManager(crop_w, crop_h, strict_inner_patch)
    self.random_sample = random_sample
    self.patch_per_polygon = patch_per_polygon
    self.ok_patch_prob = ok_patch_prob
    self.n_defect_patch_made = 0

    self.init_centers()

get_n_patch

get_n_patch(polygons: List[ndarray]) -> int

PolygonCropper crops all polygon areas {self.patch_per_polygon} times per polygons, and also crops random position of image {self.random_patch_per_img} times per images. Therefore, resultant number of patches is weighted-sum result as coded below.

Parameters:

  • polygons (List[ndarray]) –

    polygons of single image in dataset.

Returns:

  • int ( int ) –

    number of patches to be cropped in single image

Source code in SaigeToolkit/data/crop/edge_cropper.py
def get_n_patch(self, polygons: List[np.ndarray]) -> int:
    """PolygonCropper crops all polygon areas {self.patch_per_polygon} times per polygons,
    and also crops random position of image {self.random_patch_per_img} times per images.
    Therefore, resultant number of patches is weighted-sum result as coded below.

    Args:
        polygons (List[np.ndarray]): polygons of single image in dataset.

    Returns:
        int: number of patches to be cropped in single image
    """
    raise NotImplementedError

__call__

__call__(image: Union[Image, ndarray], mask: Union[Image, ndarray], polygons: Optional[List[ndarray]] = None, **kwargs) -> List[dict]

crop image into image patches.

Parameters:

  • image (Union[Image, ndarray]) –

    original image

  • polygons (List[ndarray], default: None ) –

    polygon data (# of polygons, (4, 2)) polygon should have 4 points

  • mask (Union[Image, ndarray]) –

    segmentation mask image

Returns:

  • List[dict]

    List[dict]: list of cropped image data dict

Source code in SaigeToolkit/data/crop/edge_cropper.py
def __call__(
    self,
    image: Union[Image.Image, np.ndarray],
    mask: Union[Image.Image, np.ndarray],
    polygons: Optional[List[np.ndarray]] = None,
    **kwargs,
) -> List[dict]:
    """crop image into image patches.

    Args:
        image (Union[Image.Image, np.ndarray]): original image
        polygons (List[np.ndarray]): polygon data
            (# of polygons, (4, 2)) polygon should have 4 points
        mask (Union[Image.Image, np.ndarray]): segmentation mask image

    Returns:
        List[dict]: list of cropped image data dict
    """

    w, h = read_image_size(image)

    if polygons is None:
        polygons, _ = cv2.findContours(np.asarray(mask), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        polygons = [polygon[:, 0] for polygon in polygons]  # TODO: check..

    l_data = []
    self.init_centers()

    # crop defect polygon
    _polygons = random.sample(polygons, len(polygons)) if self.random_sample else polygons
    for polygon in _polygons:
        polygon = subdivide_points_into_min_resolution(list(polygon), 32, 32)
        for _ in range(self.patch_per_polygon):
            max_iter_per_polygon = 3 if self.random_sample else 1  # TODO: as class variable?
            min_num_point_not_included = None
            for _ in range(max_iter_per_polygon):
                new_centers = self.pick_centers_from_polygon(polygon)

                num_point_not_included = sum(
                    not self.window_manager.is_in_any_window(p, [*self.centers, *new_centers])
                    for p in polygon
                )
                if num_point_not_included == 0:
                    self.centers.extend(new_centers)
                    break

                if min_num_point_not_included is None:
                    min_num_point_not_included = num_point_not_included
                    new_centers_candidate = new_centers

                if num_point_not_included < min_num_point_not_included:
                    min_num_point_not_included = num_point_not_included
                    new_centers_candidate = new_centers

            else:
                # If fail to get `num_point_not_included==0` sample,
                # use min `num_point_not_included` sample.
                new_centers = new_centers_candidate
                self.centers.extend(new_centers)

            for center in new_centers:
                single_defect_patch_data = self.get_data_dict_from_center_point(
                    image, mask, center, **kwargs
                )
                l_data.append(single_defect_patch_data)

    self.n_defect_patch_made += len(l_data)

    # crop ok region
    # XXX: OK patch portion can be larger w.r.t num of ok images.
    probability_multiplier = max(self.n_defect_patch_made, 1)
    if random.random() < self.ok_patch_prob * probability_multiplier:
        num_to_make_ok_patch = ceil(self.ok_patch_prob * probability_multiplier)
        max_iteration = 5
        for _ in range(num_to_make_ok_patch):
            for _ in range(max_iteration):
                if self.window_manager.strict_inner_patch:
                    window_half_w = self.window_manager.half_width
                    window_half_h = self.window_manager.half_height

                    # Cannot sample random window center.
                    if w - window_half_w < window_half_w or h - window_half_h < window_half_h:
                        logger.warning(f"Crop size is larger than image size: ({w}, {h}).")
                        continue

                    random_center = (
                        random.randint(window_half_w, w - window_half_w),
                        random.randint(window_half_h, h - window_half_h),
                    )
                else:
                    random_center = random.randint(0, w), random.randint(0, h)
                if not self.window_manager.is_in_any_window(random_center, self.centers):
                    self.centers.append(random_center)
                    self.n_defect_patch_made -= 1 / self.ok_patch_prob
                    break
            else:
                # Max trial ended with no ok patch.
                return l_data

            single_ok_patch_data = self.get_data_dict_from_center_point(
                image, mask, random_center, **kwargs
            )
            l_data.append(single_ok_patch_data)

    return l_data

get_proper_point

get_proper_point(state_as_satellite: Union[Dict[str, Any], List[Dict[str, Any]]], must_contain_coordinate: List[Iterable[int]], point_in: Iterable[int], point_out: Iterable[int]) -> Optional[Iterable[int]]

get proper next center point that contains every must_contain_coordinate

Parameters:

  • state_as_satellite (Union[Dict[str, Any], List[Dict[str, Any]]]) –

    relation between center point(s) and target point.

  • must_contain_coordinate (List[Iterable[int]]) –

    points should be in next window

  • point_in (Iterable[int]) –

    point inside window from previous boundary

  • point_out (Iterable[int]) –

    point outside window from previous boundary

Returns:

  • Optional[Iterable[int]]

    Optional[Iterable[int]]: proper next center (or None)

Source code in SaigeToolkit/data/crop/edge_cropper.py
def get_proper_point(
    self,
    state_as_satellite: Union[Dict[str, Any], List[Dict[str, Any]]],
    must_contain_coordinate: List[Iterable[int]],
    point_in: Iterable[int],
    point_out: Iterable[int],
) -> Optional[Iterable[int]]:
    """get proper next center point that contains every `must_contain_coordinate`

    Args:
        state_as_satellite (Union[Dict[str, Any], List[Dict[str, Any]]]):
            relation between center point(s) and target point.
        must_contain_coordinate (List[Iterable[int]]): points should be in next window
        point_in (Iterable[int]): point inside window from previous boundary
        point_out (Iterable[int]): point outside window from previous boundary

    Returns:
        Optional[Iterable[int]]: proper next center (or None)
    """

    if isinstance(state_as_satellite, list):
        for u_r in state_as_satellite:
            p_dst = self.get_proper_point(u_r, must_contain_coordinate, point_in, point_out)
            if p_dst is not None:
                break

        else:
            return None

    else:
        relation, inner_point = state_as_satellite["relation"], state_as_satellite["coordinate"]
        p_dst = self.get_destination_point(relation, inner_point, point_in, point_out)

        if p_dst is None:
            return None

        for _p in must_contain_coordinate:
            if not self.window_manager.is_in_window(_p, p_dst):
                return None

    return p_dst

get_destination_point

get_destination_point(relation: WindowState, point_ref: Iterable[int], point_in: Iterable[int], point_out: Iterable[int]) -> ndarray

calculate point a-window-away from reference point

Parameters:

  • relation (WindowState) –

    relation between reference point and next center

  • point_ref (Iterable[int]) –

    reference point to calculate next center

  • point_in (Iterable[int]) –

    point inside window from previous boundary

  • point_out (Iterable[int]) –

    point outside window from previous boundary

Returns:

  • ndarray

    np.ndarray: result destination point

Source code in SaigeToolkit/data/crop/edge_cropper.py
def get_destination_point(
    self,
    relation: WindowState,
    point_ref: Iterable[int],
    point_in: Iterable[int],
    point_out: Iterable[int],
) -> np.ndarray:
    """calculate point a-window-away from reference point

    Args:
        relation (WindowState): relation between reference point and next center
        point_ref (Iterable[int]): reference point to calculate next center
        point_in (Iterable[int]): point inside window from previous boundary
        point_out (Iterable[int]): point outside window from previous boundary

    Returns:
        np.ndarray: result destination point
    """
    w_in, h_in = point_in
    w_out, h_out = point_out

    def _get_interior_point(a_dst, a_in, a_out, b_in, b_out):
        if (a_out != a_in) and (a_in <= a_dst <= a_out or a_out <= a_dst <= a_in):
            rate = (a_dst - a_in) / (a_out - a_in)
        else:
            # if a_dst is not interior between two points, update a_dst to the middle point.
            a_dst = int(round((a_out + a_in) / 2))
            rate = 0.5

        b_dst = b_in + int(round(rate * (b_out - b_in)))
        return a_dst, b_dst

    if relation == WindowState.WIDTH_POSITIVE_OUT:
        w_dst = point_ref[0] + self.window_manager.half_width
        w_dst, h_dst = _get_interior_point(w_dst, w_in, w_out, h_in, h_out)
    elif relation == WindowState.WIDTH_NEGATIVE_OUT:
        w_dst = point_ref[0] - self.window_manager.half_width
        w_dst, h_dst = _get_interior_point(w_dst, w_in, w_out, h_in, h_out)
    elif relation == WindowState.HEIGHT_POSITIVE_OUT:
        h_dst = point_ref[1] + self.window_manager.half_height
        h_dst, w_dst = _get_interior_point(h_dst, h_in, h_out, w_in, w_out)
    elif relation == WindowState.HEIGHT_NEGATIVE_OUT:
        h_dst = point_ref[1] - self.window_manager.half_height
        h_dst, w_dst = _get_interior_point(h_dst, h_in, h_out, w_in, w_out)

    else:
        # if relation is `WindowState.In`, find farthest point and set as target point.
        width_distance = w_out - point_ref[0]
        height_distance = h_out - point_ref[1]
        if abs(width_distance) > abs(height_distance):
            if width_distance > 0:
                relation = WindowState.WIDTH_POSITIVE_OUT
            else:
                relation = WindowState.WIDTH_NEGATIVE_OUT
        else:
            if height_distance > 0:
                relation = WindowState.HEIGHT_POSITIVE_OUT
            else:
                relation = WindowState.HEIGHT_NEGATIVE_OUT

        dst = self.get_destination_point(relation, point_ref, point_in, point_out)

        return dst

    return np.array([w_dst, h_dst])

remove_inner_points_and_get_outer_points

remove_inner_points_and_get_outer_points(center: Iterable[int], points: List[Iterable[int]]) -> Tuple[List[Iterable[int]], Iterable[int], Iterable[int]]

once center decided, check other points and remove insiders.

Parameters:

  • center (Iterable[int]) –

    next center coordinate

  • points (List[Iterable[int]]) –

    current points on polygon

Returns:

  • Tuple[List[Iterable[int]], Iterable[int], Iterable[int]]

    Tuple[List[Iterable[int]], Iterable[int], Iterable[int]]: - List[Iterable[int]]: clean-up'd polygon points - Iterable[int]: boundary point (positive direction) - Iterable[int]: boundary point (negative direction)

Source code in SaigeToolkit/data/crop/edge_cropper.py
def remove_inner_points_and_get_outer_points(
    self, center: Iterable[int], points: List[Iterable[int]]
) -> Tuple[List[Iterable[int]], Iterable[int], Iterable[int]]:
    """once center decided, check other points and remove insiders.

    Args:
        center (Iterable[int]): next center coordinate
        points (List[Iterable[int]]): current points on polygon

    Returns:
        Tuple[List[Iterable[int]], Iterable[int], Iterable[int]]:
            - List[Iterable[int]]: clean-up'd polygon points
            - Iterable[int]: boundary point (positive direction)
            - Iterable[int]: boundary point (negative direction)
    """
    idx_positive_direction, idx_negative_direction = 0, 0
    p_dst, n_dst = None, None

    for idx, p in enumerate(points):
        relation = self.window_manager.check_window(p, center)
        if relation == WindowState.IN:
            continue

        idx_positive_direction = idx - 1
        p_dst = self.get_destination_point(
            relation,
            center,
            points[idx_positive_direction],
            points[idx_positive_direction + 1],
        )
        break

    for idx, p in enumerate(points[::-1]):
        relation = self.window_manager.check_window(p, center)
        if relation == WindowState.IN:
            continue

        idx_negative_direction = -idx
        n_dst = self.get_destination_point(
            relation,
            center,
            points[idx_negative_direction],
            points[idx_negative_direction - 1],
        )
        break

    if p_dst is None:
        return [], p_dst, n_dst

    points = (
        points[idx_positive_direction + 1 : idx_negative_direction]
        if idx_negative_direction < 0
        else points[idx_positive_direction + 1 :]
    )

    return points, p_dst, n_dst

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

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

subdivide_points_into_min_resolution

subdivide_points_into_min_resolution(points: List[Iterable[int]], width_resolution: int = 32, height_resolution: int = 32) -> List[Iterable[int]]

refine cv2.findContours result. long straight edge in mask results in long distance between two points.

Parameters:

  • points (List[Iterable[int]]) –

    cv2.findContours result

  • width_resolution (int, default: 32 ) –

    maximum distance width-wise. Defaults to 32.

  • height_resolution (int, default: 32 ) –

    maximum distance height-wise. Defaults to 32.

Returns:

  • List[Iterable[int]]

    List[Iterable[int]]: refined polygon points

Source code in SaigeToolkit/data/crop/edge_cropper.py
def subdivide_points_into_min_resolution(
    points: List[Iterable[int]], width_resolution: int = 32, height_resolution: int = 32
) -> List[Iterable[int]]:
    """refine cv2.findContours result.
    long straight edge in mask results in long distance between two points.

    Args:
        points (List[Iterable[int]]): cv2.findContours result
        width_resolution (int, optional): maximum distance width-wise. Defaults to 32.
        height_resolution (int, optional): maximum distance height-wise. Defaults to 32.

    Returns:
        List[Iterable[int]]: refined polygon points
    """

    idx_inspected = 0

    while idx_inspected < len(points):
        for idx in range(idx_inspected, len(points)):
            point_prev = points[idx]
            point_next = points[idx + 1] if idx + 1 < len(points) else points[0]

            w_length = abs(point_next[0] - point_prev[0])
            if w_length > width_resolution:
                num_insert = ceil(w_length / width_resolution) - 1
                break

            h_length = abs(point_next[1] - point_prev[1])
            if h_length > height_resolution:
                num_insert = ceil(h_length / height_resolution) - 1
                break
        else:
            break

        for n in range(num_insert - 1, -1, -1):
            rate = (n + 1) / (num_insert + 1)
            point_prev = points[idx]
            point_next = points[idx + 1] if idx + 1 < len(points) else points[0]
            points.insert(
                idx + 1, point_prev + np.round(rate * (point_next - point_prev)).astype(np.int32)
            )

        idx_inspected = idx + n

    return points

get_mid_point

get_mid_point(points: List[Iterable[int]]) -> Iterable[int]

In case failed to get next center point, get mid point from remaining polygon points.

Parameters:

  • points (List[Iterable[int]]) –

    remaining polygon points

Returns:

  • Iterable[int]

    Iterable[int]: next center point (mid)

Source code in SaigeToolkit/data/crop/edge_cropper.py
def get_mid_point(points: List[Iterable[int]]) -> Iterable[int]:
    """In case failed to get next center point,
    get mid point from remaining polygon points.

    Args:
        points (List[Iterable[int]]): remaining polygon points

    Returns:
        Iterable[int]: next center point (mid)
    """
    if len(points) == 1:
        return points[0]

    np_points = np.array(points)
    length_between_points = np.linalg.norm(np_points[1:] - np_points[:-1], axis=1)
    length_half = np.sum(length_between_points) / 2
    for idx in range(len(length_between_points)):
        if np.sum(length_between_points[:idx]) >= length_half:
            break

    length_desired = length_half - np.sum(length_between_points[: idx - 1])
    length_entire = length_between_points[idx - 1]
    rate = length_desired / length_entire

    return points[idx] + np.round(rate * (points[idx + 1] - points[idx])).astype(np.int32)