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

get_n_patch abstractmethod

get_n_patch(*_) -> int
Source code in SaigeToolkit/data/crop/base_cropper.py
@abstractmethod
def get_n_patch(self, *_) -> int:
    pass

__call__ abstractmethod

__call__(*_) -> Any
Source code in SaigeToolkit/data/crop/base_cropper.py
@abstractmethod
def __call__(self, *_) -> Any:
    pass

WindowState

Bases: Enum

relation state enum between center point and target point

IN class-attribute instance-attribute

IN = 0

WIDTH_POSITIVE_OUT class-attribute instance-attribute

WIDTH_POSITIVE_OUT = 1

WIDTH_NEGATIVE_OUT class-attribute instance-attribute

WIDTH_NEGATIVE_OUT = 2

HEIGHT_POSITIVE_OUT class-attribute instance-attribute

HEIGHT_POSITIVE_OUT = 3

HEIGHT_NEGATIVE_OUT class-attribute instance-attribute

HEIGHT_NEGATIVE_OUT = 4

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

width instance-attribute

width = width

half_width instance-attribute

half_width = width // 2

height instance-attribute

height = height

half_height instance-attribute

half_height = height // 2

strict_inner_patch instance-attribute

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:

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)

coordinate instance-attribute

coordinate = point

window_manager instance-attribute

window_manager = window_manager

max_width_distance instance-attribute

max_width_distance = 0

max_height_distance instance-attribute

max_height_distance = 0

min_width_distance instance-attribute

min_width_distance = 0

min_height_distance instance-attribute

min_height_distance = 0

satellite_points instance-attribute

satellite_points = {}

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:

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

crop_w instance-attribute

crop_w = crop_w

crop_h instance-attribute

crop_h = crop_h

window_manager instance-attribute

window_manager = WindowManager(crop_w, crop_h, strict_inner_patch)

random_sample instance-attribute

random_sample = random_sample

patch_per_polygon instance-attribute

patch_per_polygon = patch_per_polygon

ok_patch_prob instance-attribute

ok_patch_prob = ok_patch_prob

n_defect_patch_made instance-attribute

n_defect_patch_made = 0

init_centers

init_centers()
Source code in SaigeToolkit/data/crop/edge_cropper.py
def init_centers(self):
    self.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_data_dict_from_center_point

get_data_dict_from_center_point(image: Union[Image, ndarray], mask: Union[Image, ndarray], center, **kwargs)
Source code in SaigeToolkit/data/crop/edge_cropper.py
def get_data_dict_from_center_point(
    self,
    image: Union[Image.Image, np.ndarray],
    mask: Union[Image.Image, np.ndarray],
    center,
    **kwargs,
):
    w, h = read_image_size(image)
    w_center, h_center = center
    crop_coordinates = self.window_manager.get_coordinates_from_center(h_center, w_center, h, w)
    return {
        "image": crop(image, crop_coordinates),
        "mask": crop(mask, crop_coordinates),
        "crop_coordinates": crop_coordinates,
        **kwargs,
    }

pick_centers_from_polygon

pick_centers_from_polygon(polygon_points: List[Iterable[int]]) -> List[Iterable[int]]
Source code in SaigeToolkit/data/crop/edge_cropper.py
def pick_centers_from_polygon(self, polygon_points: List[Iterable[int]]) -> List[Iterable[int]]:
    if self.random_sample:
        max_iter_init = 10 if self.random_sample else 1  # TODO: as class variable?
        for _ in range(max_iter_init):
            idx_start = random.choice(range(len(polygon_points)))

            if not self.window_manager.is_in_any_window(polygon_points[idx_start], self.centers):
                break

            # if fail to get proper init index, restart random choice
        else:
            # if fail to get proper init index in given repeat num, return []
            return []
    else:
        idx_start = 0

    polygon_points = [*polygon_points[idx_start:], *polygon_points[:idx_start]]
    new_centers = [polygon_points[0]]

    (
        polygon_points,
        inner_point_positive,
        inner_point_negative,
    ) = self.remove_inner_points_and_get_outer_points(new_centers[-1], polygon_points)
    if inner_point_positive is None:
        return new_centers

    while polygon_points:
        previous_boundary = CenterWithSatellite(inner_point_positive, self.window_manager)
        idx_inside_checked = 0
        for idx, p in enumerate(polygon_points):
            if self.window_manager.is_in_any_window(p, [*self.centers, *new_centers]):
                continue

            state_as_satellite = previous_boundary.calculate_relation_as_satellite(p)

            # if current point and boundary point of previous center
            # (or current point and satellites of boundary point of previous center)
            # in the condition [cannot be contained in one window],
            # break from searching next center point candidate.
            if (
                isinstance(state_as_satellite, list)
                or state_as_satellite["relation"] != WindowState.IN
            ):
                break
            else:
                # if condition failed, current point is considered as `point inside window`.
                idx_inside_checked = idx

        else:
            polygon_points_for_mid_point = [
                _point
                for _point in polygon_points
                if not self.window_manager.is_in_any_window(_point, [*self.centers, *new_centers])
            ]
            if polygon_points_for_mid_point:
                new_centers.append(get_mid_point(polygon_points_for_mid_point))
            break

        # When finding next center, following points should be contained in the window:
        # boundary point of previous edge and its satelitte points.
        # TODO: 이거 Center 클래스에 넣자
        must_contain_coordinate = [
            previous_boundary.coordinate,
            *[sate["coordinate"] for sate in previous_boundary.satellite_points.values()],
        ]

        for idx_positive_direction in range(idx - 1, idx_inside_checked - 1, -1):
            # `polygon_points[idx_positive_direction]` should in window of previouss edge point,
            # so the next center can be placed right outside of previous window.
            relation_prev_point = self.window_manager.check_window(
                polygon_points[idx_positive_direction], previous_boundary.coordinate
            )
            if relation_prev_point != WindowState.IN:
                continue

            # if needed, state_as_satellite for outside point should be re-calculated.
            if idx_positive_direction != idx - 1:
                state_as_satellite = previous_boundary.calculate_relation_as_satellite(
                    polygon_points[idx_positive_direction + 1]
                )

            p_dst = self.get_proper_point(
                state_as_satellite,
                must_contain_coordinate,
                polygon_points[idx_positive_direction],
                polygon_points[idx_positive_direction + 1],
            )

            # success finding new center point, break
            if p_dst is not None:
                break

        # failed to find more center points, break.
        else:
            break

        new_centers.append(p_dst)
        polygon_points, inner_point_positive, _check = self.remove_inner_points_and_get_outer_points(
            new_centers[-1], polygon_points[idx_positive_direction:]
        )

    return new_centers

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)