import cv2 import numpy as np def apply_adaptive_threshold(image: np.ndarray) -> np.ndarray: """ Applies adaptive threshold to the given image """ return cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 0) def is_contour_rectangular(contour: np.ndarray) -> bool: """ Returns whether the given contour is rectangular or not """ num_sides = 4 perimeter = cv2.arcLength(contour, True) approx = cv2.approxPolyDP(contour, 0.01 * perimeter, True) return len(approx) == num_sides def adaptive_vconcat(images: list[np.ndarray], fill_color: tuple[int, int, int] = (255, 255, 255)) -> np.ndarray: max_width = max(img.shape[1] for img in images) # Resize each image to match the largest dimensions resized_images = [] for img in images: resized_img = cv2.copyMakeBorder(img, top=0, bottom=0, left=0, right=max_width - img.shape[1], borderType=cv2.BORDER_CONSTANT, value=fill_color) resized_images.append(resized_img) # Concatenate vertically return np.vstack(resized_images) def adaptive_hconcat(images: list[np.ndarray], fill_color: tuple[int, int, int] = (255, 255, 255)) -> np.ndarray: max_height = max(img.shape[0] for img in images) # Resize each image to match the largest dimensions resized_images = [] for img in images: resized_img = cv2.copyMakeBorder(img, top=0, bottom=max_height - img.shape[0], left=0, right=0, borderType=cv2.BORDER_CONSTANT, value=fill_color) resized_images.append(resized_img) # Concatenate horizontally return np.hstack(resized_images) def group_contours_vertically(contours) -> list[list[np.ndarray]]: """ Groups the given contours vertically """ ERROR_THRESHOLD = 0.05 contours = sorted(contours, key=lambda c: cv2.boundingRect(c)[1]) grouped_contours = [[contours[0]]] for contour in contours[1:]: found_group = False contour_x, contour_y, contour_w, contour_h = cv2.boundingRect(contour) for group in grouped_contours[::-1]: group_x, group_y, group_w, group_h = cv2.boundingRect(group[-1]) y_diff = abs(contour_y - group_y) - group_h if y_diff < 0 or y_diff > min(contour_h, group_h): continue group_x_center = group_x + group_w / 2 contour_x_center = contour_x + contour_w / 2 if abs(group_x_center - contour_x_center) < ERROR_THRESHOLD * min(group_w, contour_w): group.append(contour) found_group = True break if not found_group: grouped_contours.append([contour]) return grouped_contours def group_contours_horizontally(contours) -> list[list[np.ndarray]]: """ Groups the given contours horizontally """ ERROR_THRESHOLD = 0.05 contours = sorted(contours, key=lambda c: cv2.boundingRect(c)[0]) grouped_contours = [[contours[0]]] for contour in contours[1:]: found_group = False contour_x, contour_y, contour_w, contour_h = cv2.boundingRect(contour) for group in grouped_contours[::-1]: group_x, group_y, group_w, group_h = cv2.boundingRect(group[-1]) x_diff = abs(contour_x - group_x) - group_w if x_diff < 0 or x_diff > min(contour_w, group_w): continue group_y_center = group_y + group_h / 2 contour_y_center = contour_y + contour_h / 2 if abs(group_y_center - contour_y_center) < ERROR_THRESHOLD * min(group_h, contour_h): group.append(contour) found_group = True break if not found_group: grouped_contours.append([contour]) return grouped_contours def group_bounding_boxes_vertically(bounding_boxes) -> list[list[tuple[int, int, int, int]]]: """ Groups the given bounding boxes vertically """ ERROR_THRESHOLD = 0.05 bounding_boxes = sorted(bounding_boxes, key=lambda bb: bb[1]) grouped_bounding_boxes = [[bounding_boxes[0]]] for bounding_box in bounding_boxes[1:]: found_group = False bb_x, bb_y, bb_w, bb_h = bounding_box for group in grouped_bounding_boxes[::-1]: group_x, group_y, group_w, group_h = group[-1] y_diff = abs(bb_y - group_y) - group_h if y_diff < 0 or y_diff > min(bb_h, group_h): continue group_x_center = group_x + group_w / 2 bb_x_center = bb_x + bb_w / 2 if abs(group_x_center - bb_x_center) < ERROR_THRESHOLD * min(group_w, bb_w): group.append(bounding_box) found_group = True break if not found_group: grouped_bounding_boxes.append([bounding_box]) return grouped_bounding_boxes def group_bounding_boxes_horizontally(bounding_boxes) -> list[list[tuple[int, int, int, int]]]: """ Groups the given bounding boxes horizontally """ ERROR_THRESHOLD = 0.05 bounding_boxes = sorted(bounding_boxes, key=lambda bb: bb[0]) grouped_bounding_boxes = [[bounding_boxes[0]]] for bounding_box in bounding_boxes[1:]: found_group = False bb_x, bb_y, bb_w, bb_h = bounding_box for group in grouped_bounding_boxes[::-1]: group_x, group_y, group_w, group_h = group[-1] x_diff = abs(bb_x - group_x) - group_w if x_diff < 0 or x_diff > min(bb_w, group_w): continue group_y_center = group_y + group_h / 2 bb_y_center = bb_y + bb_h / 2 if abs(group_y_center - bb_y_center) < ERROR_THRESHOLD * min(group_h, bb_h): group.append(bounding_box) found_group = True break if not found_group: grouped_bounding_boxes.append([bounding_box]) return grouped_bounding_boxes