Skip to content

polygon_cropper

data.crop.polygon_cropper

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

PolygonCropper

PolygonCropper(mode: str = 'center', crop_w: int = 512, crop_h: int = 512, patch_per_polygon: int = 1, random_patch_per_img: int = 1, max_patch_per_img: Optional[int] = None, force_random_patch_normal: bool = False, strict_inner_patch: bool = False, fixed_polygon_order: bool = False)

Bases: BaseCropper

Crop image patches using polygons

Attributes:

  • mode (str) –

    cropping mode. ["center", "defectrandom"] available

  • crop_w (int) –

    cropping width for image patch.

  • crop_h (int) –

    cropping height for image patch.

  • half_w (int) –

    half of cropping width.

  • half_h (int) –

    half of cropping height.

  • patch_per_polygon (int) –

    number of repeat for cropping patch around each polygon.

  • random_patch_per_img (int) –

    number of random patch cropping per image.

  • force_random_patch_normal (bool) –

    whether random patch should not contain polygon area.

  • strict_inner_patch (bool) –

    whether not allowing outer area of image.

  • fixed_polygon_order (bool) –

    whether order of polygons is fixed during cropping.

All input is directly set to class attribute.

Parameters:

  • mode (str, default: 'center' ) –

    Defaults to "center".

  • crop_w (int, default: 512 ) –

    Defaults to 512.

  • crop_h (int, default: 512 ) –

    Defaults to 512.

  • patch_per_polygon (int, default: 1 ) –

    Defaults to 1.

  • random_patch_per_img (int, default: 1 ) –

    Defaults to 1.

  • force_random_patch_normal (bool, default: False ) –

    Defaults to False.

  • strict_inner_patch (bool, default: False ) –

    Defaults to False.

  • fixed_polygon_order (bool, default: False ) –

    Defaults to False.

Source code in SaigeToolkit/data/crop/polygon_cropper.py
def __init__(
    self,
    mode: str = "center",
    crop_w: int = 512,
    crop_h: int = 512,
    patch_per_polygon: int = 1,
    random_patch_per_img: int = 1,
    max_patch_per_img: Optional[int] = None,
    force_random_patch_normal: bool = False,
    strict_inner_patch: bool = False,
    fixed_polygon_order: bool = False,
) -> None:
    """All input is directly set to class attribute.

    Args:
        mode (str, optional): Defaults to "center".
        crop_w (int, optional): Defaults to 512.
        crop_h (int, optional): Defaults to 512.
        patch_per_polygon (int, optional): Defaults to 1.
        random_patch_per_img (int, optional): Defaults to 1.
        force_random_patch_normal (bool, optional): Defaults to False.
        strict_inner_patch (bool, optional): Defaults to False.
        fixed_polygon_order (bool, optional): Defaults to False.
    """
    self.mode = mode
    self.crop_w = crop_w
    self.crop_h = crop_h
    self.patch_per_polygon = patch_per_polygon
    self.random_patch_per_img = random_patch_per_img
    self.max_patch_per_img = max_patch_per_img
    self.force_random_patch_normal = force_random_patch_normal
    self.strict_inner_patch = strict_inner_patch
    self.fixed_polygon_order = fixed_polygon_order

mode instance-attribute

mode = mode

patch_per_polygon instance-attribute

patch_per_polygon = patch_per_polygon

random_patch_per_img instance-attribute

random_patch_per_img = random_patch_per_img

max_patch_per_img instance-attribute

max_patch_per_img = max_patch_per_img

force_random_patch_normal instance-attribute

force_random_patch_normal = force_random_patch_normal

strict_inner_patch instance-attribute

strict_inner_patch = strict_inner_patch

fixed_polygon_order instance-attribute

fixed_polygon_order = fixed_polygon_order

crop_w property writable

crop_w

crop_h property writable

crop_h

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/polygon_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
    """
    n_patch = len(polygons) * self.patch_per_polygon + self.random_patch_per_img
    if self.max_patch_per_img is not None:
        n_patch = min(n_patch, self.max_patch_per_img)

    return n_patch

pick_point

pick_point(polygon: ndarray) -> Tuple[int, int]

picking cropping center point.

Parameters:

  • polygon (ndarray) –

    original polygon data

Raises:

  • NotImplementedError

    only two modes ["center", "defectrandom"] available

Returns:

  • Tuple[int, int]

    Tuple[int, int]: picked point, (h_center, w_center)

Source code in SaigeToolkit/data/crop/polygon_cropper.py
def pick_point(self, polygon: np.ndarray) -> Tuple[int, int]:
    """picking cropping center point.

    Args:
        polygon (np.ndarray): original polygon data

    Raises:
        NotImplementedError: only two modes ["center", "defectrandom"] available

    Returns:
        Tuple[int, int]: picked point, (h_center, w_center)
    """
    plg = list(zip(*polygon))
    pts_w, pts_h = np.array(plg[0]), np.array(plg[1])
    h_min, h_max = np.min(pts_h), np.max(pts_h)
    w_min, w_max = np.min(pts_w), np.max(pts_w)

    if self.mode == "center":
        h_center = int(np.round((h_min + h_max) / 2.0))
        w_center = int(np.round((w_min + w_max) / 2.0))

    elif self.mode == "defectrandom":
        count = 0
        max_count = 10
        while True:
            h_center = np.random.randint(h_min, h_max + 1)
            w_center = np.random.randint(w_min, w_max + 1)

            if cv2.pointPolygonTest(polygon, (w_center, h_center), False) >= 0:
                break

            if count > max_count:
                h_center = int(np.round((h_min + h_max) / 2.0))
                w_center = int(np.round((w_min + w_max) / 2.0))
                break
            count += 1

    else:
        raise NotImplementedError

    return h_center, w_center

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/polygon_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_w, w - self.crop_w + self.half_w)
        h_center = np.clip(h_center, self.half_h, h - self.crop_h + self.half_h)
    crop_left = w_center - self.half_w
    crop_right = crop_left + self.crop_w
    crop_top = h_center - self.half_h
    crop_bottom = crop_top + self.crop_h
    return (crop_left, crop_top, crop_right, crop_bottom)

__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/polygon_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.array(mask), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        polygons = [polygon[:, 0] for polygon in polygons]  # TODO: check..

    l_data = []

    # crop defect polygon
    _polygons = polygons if self.fixed_polygon_order else random.sample(polygons, len(polygons))
    for polygon in _polygons:
        for _ in range(self.patch_per_polygon):
            h_center, w_center = self.pick_point(polygon)
            crop_coordinates = self.get_coordinates_from_center(h_center, w_center, h, w)
            l_data.append(
                {
                    "image": crop(image, crop_coordinates),
                    "mask": crop(mask, crop_coordinates),
                    **kwargs,
                }
            )
            if (
                self.max_patch_per_img is not None
                and len(l_data) + self.random_patch_per_img >= self.max_patch_per_img
            ):
                break
        else:
            continue
        break

    # crop undefect patches
    for _ in range(self.random_patch_per_img):
        while True:
            crop_coordinates = self.get_coordinates_from_center(
                np.random.randint(h), np.random.randint(w), h, w
            )

            patch_lbl = crop(mask, crop_coordinates)
            if (np.array(patch_lbl).sum() == 0) or (not self.force_random_patch_normal):
                break

        l_data.append(
            {
                "image": crop(image, crop_coordinates),
                "mask": patch_lbl,
                **kwargs,
            }
        )

    return l_data

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