def test_counterclockwise_with_check(self): assert not is_clockwise([(0, 0), (50, 250), (100, 200), (100, 0)], check_convexity=True) assert not is_clockwise([(0, 0), (50, 500), (150, 300), (150, 200), (100, 0)], check_convexity=True) assert not is_clockwise([(0, 0), (50, 110), (100, 0)], check_convexity=True)
def crop_cv2(image, polygon, fillcolor=None, cut_out=False, clip=False): """ Crop image according to given (convex) polygon. .. note:: All edges of the polygon are included in the cropped image. This is not the same behaviour as :py:meth:`PIL.Image.Image.crop` when using a straight rectangular crop box because the ``right`` and ``bottom`` coordinates are not included in the cropped image. :param numpy.ndarray image: the (opencv) image to crop :param list[tuple[int,int]] polygon: the list of points coordinates of the polygon to crop :param fillcolor: the color to use for filling area outside of polygon :param bool cut_out: if ``False`` (the default), the fillcolor is only used for filling areas outside of original image when doing crop. Il ``True``, then all areas outside of polygon are filled with fillcolor. :param bool clip: if ``False`` (the default), the cropped image will include the whole polygon, even if some of its coordinates are outside of the original image, which can lead to a large image with areas filled with fillcolor on edges of cropped image. If ``True``, the polygon is first clipped to the original image box, thus leading to the minimal cropped image containing all the *visible* parts of polygon in original image. :return: the cropped image :rtype: numpy.ndarray """ im_height, im_width = image.shape[:2] if cut_out: image = image.copy() cut_out_cv2(image, polygon, fillcolor=fillcolor) flip = not is_clockwise(polygon, check_convexity=False) resize_params, rotate_params, crop_params = _get_transforms_params(polygon, im_width, im_height, clip=clip) center, angle = rotate_params if fillcolor is None: fillcolor = 0 left_add, top_add, new_width, new_height = resize_params if left_add > 0 or top_add > 0 or new_width > im_width or new_height > im_height: new_image = np.empty((new_height, new_width, *image.shape[2:]), dtype=image.dtype) new_image[:, :] = fillcolor new_image[top_add:top_add+im_height, left_add:left_add+im_width] = image image = new_image if angle != 0: matrix = cv2.getRotationMatrix2D(center, angle, 1) image = cv2.warpAffine( image, matrix, (new_width, new_height), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_CONSTANT, borderValue=fillcolor ) left, top, right, bottom = crop_params # Add 1 to include right and bottom edges in the cropped image image = image[top:bottom + 1, left:right + 1] if flip: image = cv2.flip(cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE), 0) return image
def crop_pil(image, polygon, fillcolor=None, cut_out=False, clip=False): """ Crop image according to given (convex) polygon. .. note:: All edges of the polygon are included in the cropped image. This is not the same behaviour as :py:meth:`PIL.Image.Image.crop` when using a straight rectangular crop box because the ``right`` and ``bottom`` coordinates are not included in the cropped image. :param PIL.Image.Image image: the (pillow) image to crop :param list[tuple[int,int]] polygon: the list of points coordinates of the polygon to crop :param fillcolor: the color to use for filling area outside of polygon :param bool cut_out: if ``False`` (the default), the fillcolor is only used for filling areas outside of original image when doing crop. Il ``True``, then all areas outside of polygon are filled with fillcolor. :param bool clip: if ``False`` (the default), the cropped image will include the whole polygon, even if some of its coordinates are outside of the original image, which can lead to a large image with areas filled with fillcolor on edges of cropped image. If ``True``, the polygon is first clipped to the original image box, thus leading to the minimal cropped image containing all the *visible* parts of polygon in original image. :return: the cropped image :rtype: PIL.Image.Image """ im_width, im_height = image.size if cut_out: image = image.copy() cut_out_pil(image, polygon, fillcolor=fillcolor) flip = not is_clockwise(polygon, check_convexity=False) # using expand=True in PIL.Image.Image.rotate assumes that center is the center of image, so it does not give # correct result when center is near an image side. This is the reason why we manually apply a first transform to # resize image before rotation and crop. resize_params, rotate_params, crop_params = _get_transforms_params(polygon, im_width, im_height, clip=clip) _, angle = rotate_params left_add, top_add, new_width, new_height = resize_params if left_add > 0 or top_add > 0 or new_width > im_width or new_height > im_height: image = image.transform( size=(new_width, new_height), method=Image.AFFINE, data=(1, 0, -left_add, 0, 1, -top_add), fillcolor=fillcolor ) left, top, right, bottom = crop_params if angle != 0: # recompute center because the center computed by minAreaRect seems not to be the exact center (as if right and # bottom edges of the rectangle are not included in the crop box). PIL.Image.rotate requires the exact center center = (left + (right + 1 - left) / 2, top + (bottom + 1 - top) / 2) image = image.rotate(angle, resample=Image.BICUBIC, expand=False, center=center, fillcolor=fillcolor) # Add 1 to include right and bottom edges in the cropped image image = image.crop((left, top, right + 1, bottom + 1)) if flip: image = image.transpose(Image.ROTATE_270).transpose(Image.FLIP_LEFT_RIGHT) return image
def test_flat_angle_with_sufficient_number_of_vertices(self): assert is_clockwise([(0, 0), (100, 0), (200, 0), (100, 50)], check_convexity=True) assert not is_clockwise([(0, 0), (100, 50), (200, 0), (100, 0)], check_convexity=True)
def test_null_angle(self): with pytest.raises(ValueError): is_clockwise([(0, 0), (100, 0), (50, 0)]) with pytest.raises(ValueError): is_clockwise([(0, 0), (0, 100), (0, 50)])
def test_clockwise(self): assert is_clockwise([(0, 0), (100, 0), (100, 200), (50, 250)]) assert is_clockwise([(0, 0), (100, 0), (150, 200), (150, 300), (50, 500)]) assert is_clockwise([(0, 0), (100, 0), (50, 110)])
def test_not_convex_without_check(self): assert is_clockwise([(100, 100), (150, 100), (150, 150), (200, 150), (200, 200), (100, 200)])
def test_not_convex_with_check(self): with pytest.raises(ValueError): is_clockwise([(100, 100), (150, 100), (150, 150), (200, 150)], check_convexity=True)
def test_insufficient_number_of_distinct_vertices(self): with pytest.raises(ValueError): is_clockwise([(0, 0), (100, 100), (100, 100), (0, 0)])