import random
import cv2
import numpy as np
from numba import config
from numba import jit
from augraphy.augmentations.lib import load_image_from_cache
from augraphy.augmentations.lib import rotate_image_PIL
from augraphy.augmentations.lib import warp_fold
from augraphy.base.augmentation import Augmentation
[docs]
class PageBorder(Augmentation):
"""Add page border effect by stacking images multiple times.
:param page_border_width_height: Pair of values determining the width and height of the page border effect.
If width > 0, the effect applies in right side of the page, while if width <0, the effect applies in the left side of the page.
If height > 0, the effect applies in bottom side of the page, while if height <0, the effect applies in the top side of the page.
If the value is within the range of -1.0 to 1.0 and the value is float,
border width will be scaled by image width, while border height will be sccaled by image height.
width (int) = image width * width (float and -1.0 - 1.0);
height (int) = image height * height (float and -1.0 - 1.0);
Default value is "random".
:type page_border_width_height: tuple or string , optional
:param page_border_color: The color (BGR) of border effect.
:type page_border_color: tuple, optional
:param page_border_background_color: The color (BGR) of border background.
:type page_border_background_color: tuple, optional
:param page_border_use_cache_images: Flag to enable the usage of cache images in creating page border effect.
:type page_border_use_cache_images: int, optional
:param page_border_trim_sides: Tuple of 4 (left, top, right, bottom) determining which sides of the image to be trimmed.
This is valid only if same_page_border is false.
:type page_border_trim_sides: int, optional
:param page_numbers: An integer determining the number of pages in the border.
:type page_numbers: int, optional
:param page_rotation_angle_in_order: Flag to enable an ordered or random angle rotation.
:type page_rotation_angle_in_order: int , optional
:param page_rotation_angle_range: Pair of ints determining the angle of rotation in the border effect.
:type page_rotation_angle_range: tuple , optional
:param curve_frequency: Pair of ints determining number of curvy section in the generated page borders.
:type curve_frequency: tuple, optional
:param curve_height: Pair of ints determining height of curvy section in the generated page borders.
:type curve_height: tuple, optional
:param curve_length_one_side: Pair of ints determining one side length of generated curvy effect.
:type curve_length_one_side: tuple, optional
:param same_page_border: Flag to decide whether the added borders will be within the input image or not.
:type same_page_border: int, optional
:param numba_jit: The flag to enable numba jit to speed up the processing in the augmentation.
:type numba_jit: int, optional
:param p: The probability this Augmentation will be applied.
:type p: float, optional
"""
def __init__(
self,
page_border_width_height="random",
page_border_color=(0, 0, 0),
page_border_background_color=(0, 0, 0),
page_border_use_cache_images=0,
page_border_trim_sides=(0, 0, 0, 0),
page_numbers="random",
page_rotate_angle_in_order=1,
page_rotation_angle_range=(-3, 3),
curve_frequency=(0, 1),
curve_height=(2, 4),
curve_length_one_side=(50, 100),
same_page_border=1,
numba_jit=1,
p=1,
):
"""Constructor method"""
super().__init__(p=p, numba_jit=numba_jit)
self.page_border_width_height = page_border_width_height
self.page_border_color = page_border_color
self.page_border_background_color = page_border_background_color
self.page_border_use_cache_images = page_border_use_cache_images
self.page_border_trim_sides = page_border_trim_sides
self.page_numbers = page_numbers
self.page_rotate_angle_in_order = page_rotate_angle_in_order
self.page_rotation_angle_range = page_rotation_angle_range
self.curve_frequency = curve_frequency
self.curve_height = curve_height
self.curve_length_one_side = curve_length_one_side
self.same_page_border = same_page_border
self.numba_jit = numba_jit
config.DISABLE_JIT = bool(1 - numba_jit)
def __repr__(self):
return f"PageBorder(page_border_width_height={self.page_border_width_height}, page_border_color={self.page_border_color}, page_border_background_color={self.page_border_background_color}, page_border_use_cache_images={self.page_border_use_cache_images}, page_border_trim_sides={self.page_border_trim_sides}, page_numbers={self.page_numbers}, page_rotate_angle_in_order={self.page_rotate_angle_in_order}, page_rotation_angle_range={self.page_rotation_angle_range}, curve_frequency={self.curve_frequency}, curve_height={self.curve_height}, curve_length_one_side={self.curve_length_one_side}, same_page_border={self.same_page_border}, numba_jit={self.numba_jit}, p={self.p})"
[docs]
def random_folding(self, image):
"""Create random folding effect at the image border.
:param Image: Image to be folded.
:type image: numpy.array (numpy.uint8)
"""
# get image x and y size
ysize, xsize = image.shape[:2]
# height of curve, min value is 1
curve_y_shift = random.randint(
max(1, self.curve_height[0]),
max(1, self.curve_height[1]),
)
# length of one side curvy part, min value is 5
curve_width_one_side = random.randint(
max(5, self.curve_length_one_side[0]),
max(5, self.curve_length_one_side[1]),
)
# prevent random randint second value is smaller than first value
while xsize - curve_width_one_side - 1 < curve_width_one_side + 1:
curve_width_one_side = max(1, int(curve_width_one_side / 2))
# center of curvy part
curve_x = random.randint(
curve_width_one_side + 1,
xsize - curve_width_one_side - 1,
)
# filler of folding function
curve_noise = 0
# warp image to produce curvy effect
image_curve_left = warp_fold(
image,
ysize,
curve_noise,
curve_x,
curve_width_one_side,
curve_y_shift,
side="left",
backdrop_color=self.page_border_background_color,
)
image_curve_right = warp_fold(
image_curve_left,
ysize,
curve_noise,
curve_x,
curve_width_one_side,
curve_y_shift,
side="right",
backdrop_color=self.page_border_background_color,
)
image_color = np.full_like(image_curve_right, fill_value=self.page_border_color, dtype="uint8")
image_curve_right[image_curve_right == (0, 0, 0)] = image_color[image_curve_right == (0, 0, 0)]
return image_curve_right
[docs]
def center_overlay(self, image_background, image_foreground, image_mask_background, image_mask_foreground):
"""Overlays foreground image into background image in the center.
:param image_background: The background image.
:type image_background: numpy array
:param image_foreground: The foreground image.
:type image_foreground: numpy array
:param image_mask_background: The mask of background image.
:type image_mask_background: numpy array
:param image_mask_foreground: The mask of foreground image.
:type image_mask_foreground: numpy array
"""
ysize, xsize = image_foreground.shape[:2]
bysize, bxsize = image_background.shape[:2]
# y
ycenter = int(ysize / 2)
bycenter = int(bysize / 2)
dy_top = abs(ycenter - bycenter)
dy_bottom = abs(abs(ysize - ycenter) - abs(bysize - bycenter))
# x
xcenter = int(xsize / 2)
bxcenter = int(bxsize / 2)
dx_left = abs(xcenter - bxcenter)
dx_right = abs(abs(xsize - xcenter) - abs(bxsize - bxcenter))
# condition where foreground image is larger
if ysize > bysize:
# (top, bottom), (left, right)
image_background = np.pad(
image_background,
pad_width=((dy_top, dy_bottom), (0, 0), (0, 0)),
mode="constant",
constant_values=0,
)
image_mask_background = np.pad(
image_mask_background,
pad_width=((dy_top, dy_bottom), (0, 0), (0, 0)),
mode="constant",
constant_values=0,
)
# flag to know if foreground x size is larger
if xsize > bxsize:
# (top, bottom), (left, right)
image_background = np.pad(
image_background,
pad_width=((0, 0), (dx_left, dx_right), (0, 0)),
mode="constant",
constant_values=0,
)
image_mask_background = np.pad(
image_mask_background,
pad_width=((0, 0), (dx_left, dx_right), (0, 0)),
mode="constant",
constant_values=0,
)
indices = image_mask_foreground > 0
# overlay image
if bysize > ysize and bxsize > xsize:
image_background[dy_top:-dy_bottom, dx_left:-dx_right][indices] = image_foreground[indices]
image_mask_background[dy_top:-dy_bottom, dx_left:-dx_right] += image_mask_foreground
elif bysize > ysize:
image_background[dy_top:-dy_bottom, :][indices] = image_foreground[indices]
image_mask_background[dy_top:-dy_bottom, :] += image_mask_foreground
elif bxsize > xsize:
image_background[:, dx_left:-dx_right][indices] = image_foreground[indices]
image_mask_background[:, dx_left:-dx_right] += image_mask_foreground
else:
image_background[indices] = image_foreground[indices]
image_mask_background += image_mask_foreground
return image_background, image_mask_background, dy_top, dy_bottom, dx_left, dx_right
[docs]
def create_page_borders(
self,
image,
page_border_width,
page_border_height,
mask,
keypoints,
bounding_boxes,
):
"""Create page borders effect and apply it into input image.
:param image: The input image.
:type image: numpy array
:param border_width: Horizontal direction and width of the borders.
:type border_width: int
:param border_height: Vertical direction and height of borders.
:type border_height: int
:param mask: The mask of labels for each pixel. Mask value should be in range of 1 to 255.
Value of 0 will be assigned to the filled area after the transformation.
:type mask: numpy array (uint8)
:param keypoints: A dictionary of single or multiple labels where each label is a nested list of points coordinate.
:type keypoints: dictionary
:param bounding_boxes: A nested list where each nested list contains box location (x1, y1, x2, y2).
:type bounding_boxes: list
"""
border_width = abs(page_border_width)
border_height = abs(page_border_height)
# temporary, in case we need random input
page_border_background_color = self.page_border_background_color
# generate page number
if self.page_numbers == "random":
min_length = min(max(1, abs(page_border_width)), max(1, abs(page_border_height)))
min_page = max(1, int(min_length / 10))
max_page = max(1, int(min_length / 8))
page_numbers = random.randint(min_page, max_page)
else:
page_numbers = self.page_numbers
# +1 to have an extra top layer page
page_numbers += 1
ysize, xsize = image.shape[:2]
# load border images
border_images = []
if self.page_border_use_cache_images:
for i in range(page_numbers - 1):
image_cache = load_image_from_cache(random_image=1)
if image_cache is None:
image_cache = image.copy()
else:
image_cache = cv2.resize(
image_cache,
(xsize, ysize),
interpolation=cv2.INTER_AREA,
)
border_images.append(image_cache)
# last image on the surface must be the input image
border_images.append(image.copy())
else:
for i in range(page_numbers):
border_images.append(image.copy())
yxsize = []
for i, border_image in enumerate(border_images):
# default, extend top left
if page_border_width < 0 and page_border_height < 0:
page_border_trim_sides = self.page_border_trim_sides
# extend bottom left
elif page_border_width < 0 and page_border_height > 0:
# rotate counter clockwise 3 times to align bottomleft to topleft (topleft is reference)
border_image = np.rot90(border_image, 3)
page_border_trim_sides = (
self.page_border_trim_sides[3],
self.page_border_trim_sides[0],
self.page_border_trim_sides[1],
self.page_border_trim_sides[2],
)
# extend bottom right
elif page_border_width > 0 and page_border_height > 0:
# rotate counter clockwise twice to align bottomright to topleft (topleft is reference)
border_image = np.rot90(border_image, 2)
page_border_trim_sides = (
self.page_border_trim_sides[2],
self.page_border_trim_sides[3],
self.page_border_trim_sides[0],
self.page_border_trim_sides[1],
)
# extend top right
elif page_border_width > 0 and page_border_height < 0:
# rotate counter clockwise once to align topright to topleft (topleft is reference)
border_image = np.rot90(border_image, 1)
page_border_trim_sides = (
self.page_border_trim_sides[1],
self.page_border_trim_sides[2],
self.page_border_trim_sides[3],
self.page_border_trim_sides[0],
)
# extend top, default
elif page_border_width == 0 and page_border_height < 0:
page_border_trim_sides = self.page_border_trim_sides
# bottom only
elif page_border_width == 0 and page_border_height > 0:
# rotate counter clockwise twice to align bottom to top (top is reference)
border_image = np.rot90(border_image, 2)
page_border_trim_sides = (
self.page_border_trim_sides[2],
self.page_border_trim_sides[3],
self.page_border_trim_sides[0],
self.page_border_trim_sides[1],
)
# extend left, default
elif page_border_width < 0 and page_border_height == 0:
page_border_trim_sides = self.page_border_trim_sides
# right only
elif page_border_width > 0 and page_border_height == 0:
# rotate counter clockwise twice to align right to left (left is reference)
border_image = np.rot90(border_image, 2)
page_border_trim_sides = (
self.page_border_trim_sides[2],
self.page_border_trim_sides[3],
self.page_border_trim_sides[0],
self.page_border_trim_sides[1],
)
else:
page_border_trim_sides = self.page_border_trim_sides
# get size before the pruning for same page border
if not yxsize:
rysize, rxsize = border_image.shape[:2]
yxsize.extend([rysize, rxsize])
# for same page border, page border grows internally
if self.same_page_border:
if border_width == 0:
border_image = border_image[border_height:, :]
elif border_height == 0:
border_image = border_image[:, border_width:]
else:
border_image = border_image[border_height:, border_width:]
border_images[i] = border_image
# create background image
if self.same_page_border:
border_image_merged = np.full_like(border_images[0], fill_value=page_border_background_color, dtype="uint8")
else:
border_image_merged = np.full(
(border_images[0].shape[0] + border_height, border_images[1].shape[1] + border_width, 3),
fill_value=page_border_background_color,
dtype="uint8",
)
# interpolate shifting x and y
if border_width == 0:
shifted_value_xs = [0 for _ in range(page_numbers)]
shifted_value_ys = np.linspace(border_height, 0, page_numbers)
elif border_height == 0:
shifted_value_xs = np.linspace(border_width, 0, page_numbers)
shifted_value_ys = [0 for _ in range(page_numbers)]
else:
shifted_value_xs = np.linspace(border_width, 0, page_numbers)
shifted_value_ys = np.linspace(border_height, 0, page_numbers)
# total number of page shifting process
total_shifts = page_numbers
# angle
rotated_angle = random.randint(self.page_rotation_angle_range[0], self.page_rotation_angle_range[1])
rotated_angle_step = rotated_angle / (total_shifts)
image_mask_background = np.full_like(border_image_merged, fill_value=0, dtype="uint8")
# extended size in each edge of image
ext_left, ext_right, ext_top, ext_bottom = abs(page_border_width), 0, abs(page_border_height), 0
# size of initial background
bysize, bxsize = border_image_merged.shape[:2]
for i in reversed(range(total_shifts)):
shifted_value_y = int(shifted_value_ys[i])
shifted_value_x = int(shifted_value_xs[i])
# create a copy of image
border_image_fold = border_images[total_shifts - 1 - i]
# draw borders line on possible folding edges before folding process
if page_border_height != 0:
border_image_fold[0, :] = self.page_border_color
if page_border_width != 0:
border_image_fold[:, 0] = self.page_border_color
# apply curve effect to borders (last image shouldn't apply folding)
if i != 0:
curve_frequency = random.randint(self.curve_frequency[0], self.curve_frequency[1])
for _ in range(curve_frequency):
# top curve
sx, sy, ex, ey = 0, 0, border_image_fold.shape[1], self.curve_height[1] * 2
border_image_fold[sy:ey, sx:ex] = self.random_folding(border_image_fold[sy:ey, sx:ex])
# left curve
sx, sy, ex, ey = 0, 0, self.curve_height[1] * 2, border_image_fold.shape[0]
border_image_fold[sy:ey, sx:ex] = np.rot90(
self.random_folding(np.rot90(border_image_fold[sy:ey, sx:ex], 3)),
)
border_image_single = border_image_fold
# draw the rest of borders line
border_image_single[-1, :] = self.page_border_color
border_image_single[0, :] = self.page_border_color
border_image_single[:, 0] = self.page_border_color
border_image_single[:, -1] = self.page_border_color
image_mask_rotate_single = np.full_like(border_image_single, fill_value=1, dtype="uint8")
# pad image based on shift values
extend_bottom = border_height - shifted_value_y
extend_right = border_width - shifted_value_x
border_image_single = np.pad(
border_image_single,
pad_width=((shifted_value_y, extend_bottom), (shifted_value_x, extend_right), (0, 0)),
mode="constant",
constant_values=0,
)
image_mask_rotate_single = np.pad(
image_mask_rotate_single,
pad_width=((shifted_value_y, extend_bottom), (shifted_value_x, extend_right), (0, 0)),
mode="constant",
constant_values=0,
)
# rotate page
if self.page_rotate_angle_in_order:
# rotation
rotated_angle -= rotated_angle_step
else:
# no rotation on final image
if i == 0:
rotated_angle = 0
else:
rotated_angle = random.randint(self.page_rotation_angle_range[0], self.page_rotation_angle_range[1])
if rotated_angle != 0:
image_mask_rotate_single = rotate_image_PIL(image_mask_rotate_single, rotated_angle, expand=1)
border_image_single = rotate_image_PIL(border_image_single, rotated_angle, expand=1)
border_image_merged, image_mask_background, dy_top, dy_bottom, dx_left, dx_right = self.center_overlay(
border_image_merged,
border_image_single,
image_mask_background,
image_mask_rotate_single,
)
if not self.same_page_border:
# check and compute the extended size
nbysize, nbxsize = border_image_merged.shape[:2]
if nbysize != bysize:
# add new extended size
ext_top += dy_top
ext_bottom += dy_bottom
# update new size
bysize = nbysize
if nbxsize != bxsize:
# add new extended size
ext_left += dx_left
ext_right += dx_right
# update new size
bxsize = nbxsize
# update background value based on mask
image_mask_background[image_mask_background > 0] = 255
image_mask_background_gray = cv2.cvtColor(image_mask_background, cv2.COLOR_BGR2GRAY)
indices = image_mask_background_gray == 0
border_image_merged[indices] = page_border_background_color
border_image_merged = cv2.GaussianBlur(border_image_merged, (3, 3), 0)
# merge each pages into a main page
if self.same_page_border or sum(page_border_trim_sides) > 0:
ysize, xsize = yxsize
bysize, bxsize = border_image_merged.shape[:2]
if bysize > ysize or bxsize > xsize:
# y
ycenter = int(ysize / 2)
bycenter = int(bysize / 2)
dy_top = abs(ycenter - bycenter)
dy_bottom = abs(abs(ysize - ycenter) - abs(bysize - bycenter))
# x
xcenter = int(xsize / 2)
bxcenter = int(bxsize / 2)
dx_left = abs(xcenter - bxcenter)
dx_right = abs(abs(xsize - xcenter) - abs(bxsize - bxcenter))
if not self.same_page_border and sum(page_border_trim_sides) > 0:
half_border_width = int(np.ceil(border_width / 2))
half_border_height = int(np.ceil(border_height / 2))
dy_top += half_border_height
dy_bottom -= half_border_height
dx_left += half_border_width
dx_right -= half_border_width
if bysize > ysize and bxsize > xsize:
if self.same_page_border:
border_image_merged = border_image_merged[dy_top:-dy_bottom, dx_left:-dx_right]
else:
if page_border_trim_sides[0]:
start_x = dx_left
else:
start_x = 0
if page_border_trim_sides[1]:
start_y = dy_top
else:
start_y = 0
if page_border_trim_sides[2] and dx_right != 0:
end_x = -dx_right
else:
end_x = bxsize
if page_border_trim_sides[3] and dy_bottom != 0:
end_y = -dy_bottom
else:
end_y = bysize
border_image_merged = border_image_merged[start_y:end_y, start_x:end_x]
elif bysize > ysize:
if self.same_page_border:
border_image_merged = border_image_merged[dy_top:-dy_bottom, :]
else:
if page_border_trim_sides[1]:
start_y = dy_top
else:
start_y = 0
if page_border_trim_sides[3]:
end_y = -dy_bottom
else:
end_y = bysize
border_image_merged = border_image_merged[start_y:end_y, :]
elif bxsize > xsize:
if self.same_page_border:
border_image_merged = border_image_merged[:, dx_left:-dx_right]
else:
if page_border_trim_sides[0]:
start_x = dx_left
else:
start_x = 0
if page_border_trim_sides[2]:
end_x = -dx_right
else:
end_x = bxsize
border_image_merged = border_image_merged[:, start_x:end_x]
# rotate back to original extended value and trimming sides
# default, extend top left
if page_border_width < 0 and page_border_height < 0:
pass
# bottom left
elif page_border_width < 0 and page_border_height > 0:
# rotate counter clockwise once from topleft (topleft is reference) back to bottomleft
ext_left, ext_right, ext_top, ext_bottom = ext_top, ext_bottom, ext_right, ext_left
page_border_trim_sides = [
page_border_trim_sides[1],
page_border_trim_sides[2],
page_border_trim_sides[3],
page_border_trim_sides[0],
]
# bottom right
elif page_border_width > 0 and page_border_height > 0:
# rotate counter clockwise twice from topleft (topleft is reference) back to bottomright
ext_left, ext_right, ext_top, ext_bottom = ext_right, ext_left, ext_bottom, ext_top
page_border_trim_sides = [
page_border_trim_sides[2],
page_border_trim_sides[3],
page_border_trim_sides[0],
page_border_trim_sides[1],
]
# top right
elif page_border_width > 0 and page_border_height < 0:
# rotate counter clockwise 3 times from topleft (topleft is reference) back to topright
ext_left, ext_right, ext_top, ext_bottom = ext_bottom, ext_top, ext_left, ext_right
page_border_trim_sides = [
page_border_trim_sides[3],
page_border_trim_sides[0],
page_border_trim_sides[1],
page_border_trim_sides[2],
]
# top
elif page_border_width == 0 and page_border_height < 0:
pass
# bottom
elif page_border_width == 0 and page_border_height > 0:
# rotate counter clockwise twice from top (top is reference) back to bottom
ext_left, ext_right, ext_top, ext_bottom = ext_right, ext_left, ext_bottom, ext_top
page_border_trim_sides = [
page_border_trim_sides[2],
page_border_trim_sides[3],
page_border_trim_sides[0],
page_border_trim_sides[1],
]
# left
elif page_border_width < 0 and page_border_height == 0:
pass
# right
elif page_border_width > 0 and page_border_height == 0:
# rotate counter clockwise 2 times from left (left is reference) back to right
ext_left, ext_right, ext_top, ext_bottom = ext_right, ext_left, ext_bottom, ext_top
page_border_trim_sides = [
page_border_trim_sides[2],
page_border_trim_sides[3],
page_border_trim_sides[0],
page_border_trim_sides[1],
]
if mask is not None:
# for same page border, remove part of the mask
if self.same_page_border:
mask[:ext_top, :] = 0
mask[mask.shape[0] - ext_bottom :, :] = 0
mask[:, :ext_left] = 0
mask[:, mask.shape[1] - ext_right :] = 0
# padding and followed trim for not same page border
else:
# padding
pad_x = [ext_left, ext_right]
pad_y = [ext_top, ext_bottom]
mask = np.pad(
mask,
pad_width=(pad_y, pad_x),
mode="constant",
constant_values=0,
)
# trim
if sum(page_border_trim_sides) > 0:
# trim left
if page_border_trim_sides[0] and ext_left > 0:
mask = mask[:, ext_left:]
# trim top
if page_border_trim_sides[1] and ext_top > 0:
mask = mask[ext_top:, :]
# trim right
if page_border_trim_sides[2] and ext_right > 0:
mask = mask[:, : mask.shape[1] - ext_right]
# trim bottom
if page_border_trim_sides[3] and ext_bottom > 0:
mask = mask[: mask.shape[0] - ext_bottom, :]
if keypoints is not None:
for name, points in keypoints.items():
remove_indices = []
for i, (xpoint, ypoint) in enumerate(points):
# remove points if it is outside boundaries
if self.same_page_border:
if (
(xpoint < ext_left)
or (xpoint >= image.shape[1] - ext_right)
or (ypoint < ext_top)
or (ypoint >= image.shape[0] - ext_bottom)
):
remove_indices.append(i)
# add offset for padded area
else:
# check if trim left, add offset only if there's no trim left
if not page_border_trim_sides[0]:
xpoint = xpoint + ext_left
# check if trim top, add offset only if there's no trim top
if not page_border_trim_sides[1]:
ypoint = ypoint + ext_top
points[i] = [xpoint, ypoint]
# remove out of boundaries points
while remove_indices:
points.pop(remove_indices.pop())
if bounding_boxes is not None:
remove_indices = []
for i, bounding_box in enumerate(bounding_boxes):
xspoint, yspoint, xepoint, yepoint = bounding_box
# for same page border, reduce or remove box when it is outside boundary
if self.same_page_border:
xend = image.shape[1] - ext_right
yend = image.shape[0] - ext_bottom
# both points are not in boundary
if (xspoint < ext_left or xspoint >= xend or yspoint < ext_top or yspoint >= yend) and (
xepoint < ext_left or xepoint >= xend or yepoint < ext_top or yepoint >= yend
):
remove_indices.append(i)
# start point is not in boundary, but end point is within boundary
elif xspoint < ext_left or xspoint >= xend or yspoint < ext_top or yspoint >= yend:
xspoint = min(max(xspoint, ext_left), xend)
yspoint = min(max(yspoint, ext_top), yend)
bounding_boxes[i] = [xspoint, yspoint, xepoint, yepoint]
# end point is not in boundary, but start point is within boundary
elif xepoint < ext_left or xepoint >= xend or yepoint < ext_top or yepoint >= yend:
xepoint = min(max(xepoint, ext_left), xend)
yepoint = min(max(yepoint, ext_top), yend)
bounding_boxes[i] = [xspoint, yspoint, xepoint, yepoint]
# add offset for padded area
else:
# check if trim left, add offset only if there's no trim left
if not page_border_trim_sides[0]:
xspoint = xspoint + ext_left
xepoint = xepoint + ext_left
# check if trim top, add offset only if there's no trim top
if not page_border_trim_sides[1]:
yspoint = yspoint + ext_top
yepoint = yepoint + ext_top
bounding_boxes[i] = [xspoint, yspoint, xepoint, yepoint]
# remove out of boundaries points
while remove_indices:
bounding_boxes.pop(remove_indices.pop())
# rotate back to original position
# default, extend top left
if page_border_width < 0 and page_border_height < 0:
pass
# bottom left
elif page_border_width < 0 and page_border_height > 0:
# rotate counter clockwise once from topleft (topleft is reference) back to bottomleft
border_image_merged = np.rot90(border_image_merged, 1)
# bottom right
elif page_border_width > 0 and page_border_height > 0:
# rotate counter clockwise twice from topleft (topleft is reference) back to bottomright
border_image_merged = np.rot90(border_image_merged, 2)
# top right
elif page_border_width > 0 and page_border_height < 0:
# rotate counter clockwise 3 times from topleft (topleft is reference) back to topright
border_image_merged = np.rot90(border_image_merged, 3)
# top
elif page_border_width == 0 and page_border_height < 0:
pass
# bottom
elif page_border_width == 0 and page_border_height > 0:
# rotate counter clockwise twice from top (top is reference) back to bottom
border_image_merged = np.rot90(border_image_merged, 2)
# left
elif page_border_width < 0 and page_border_height == 0:
pass
# right
elif page_border_width > 0 and page_border_height == 0:
# rotate counter clockwise 2 times from left (left is reference) back to right
border_image_merged = np.rot90(border_image_merged, 2)
return border_image_merged, mask
def __call__(self, image, layer=None, mask=None, keypoints=None, bounding_boxes=None, force=False):
if force or self.should_run():
# convert and make sure image is color image
has_alpha = 0
if len(image.shape) > 2:
is_gray = 0
if image.shape[2] == 4:
has_alpha = 1
image, image_alpha = image[:, :, :3], image[:, :, 3]
else:
is_gray = 1
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
height, width = image.shape[:2]
# generate width and height
if self.page_border_width_height == "random":
border_width = int(random.uniform(0.01, 0.05) * width) * random.choice([1, -1])
border_height = int(random.uniform(0.01, 0.05) * height) * random.choice([1, -1])
else:
# check if value is flaot and scale them with image width or height
if (
self.page_border_width_height[0] <= 1
and self.page_border_width_height[0] >= -1
and isinstance(self.page_border_width_height[0], float)
):
border_width = int(self.page_border_width_height[0] * width)
else:
border_width = self.page_border_width_height[0]
if (
self.page_border_width_height[1] <= 1
and self.page_border_width_height[1] >= -1
and isinstance(self.page_border_width_height[1], float)
):
border_height = int(self.page_border_width_height[1] * height)
else:
border_height = self.page_border_width_height[1]
image_output, mask = self.create_page_borders(
image.copy(),
border_width,
border_height,
mask,
keypoints,
bounding_boxes,
)
# return image follows the input image color channel
if is_gray:
image_output = cv2.cvtColor(image_output, cv2.COLOR_BGR2GRAY)
if has_alpha:
oysize, oxsize = image_output.shape[:2]
# check and make sure image alpha has a same size with image output
if oysize != height or oxsize != width:
image_alpha = cv2.resize(
image_alpha,
(oxsize, oysize),
interpolation=cv2.INTER_AREA,
)
image_output = np.dstack((image_output, image_alpha))
# check for additional output of mask, keypoints and bounding boxes
outputs_extra = []
if mask is not None or keypoints is not None or bounding_boxes is not None:
outputs_extra = [mask, keypoints, bounding_boxes]
# returns additional mask, keypoints and bounding boxes if there is additional input
if outputs_extra:
# returns in the format of [image, mask, keypoints, bounding_boxes]
return [image_output] + outputs_extra
else:
return image_output