from itertools import combinations from typing import List from cv_analysis.utils import until from cv_analysis.utils.rectangle import Rectangle from cv_analysis.utils.spacial import overlap def is_near_enough(alpha: Rectangle, beta: Rectangle, max_gap=14): x1, y1, w1, h1 = alpha x2, y2, w2, h2 = beta return any( [ abs(x1 - (x2 + w2)) <= max_gap, abs(x2 - (x1 + w1)) <= max_gap, abs(y2 - (y1 + h1)) <= max_gap, abs(y1 - (y2 + h2)) <= max_gap, ] ) def is_on_same_line(rect_pair): x1, y1, w1, h1 = rect_pair[0] x2, y2, w2, h2 = rect_pair[1] return any( [ any([abs(y1 - y2) <= 10, abs(y1 + h1 - (y2 + h2)) <= 10]), any([y2 <= y1 and y1 + h1 <= y2 + h2, y1 <= y2 and y2 + h2 <= y1 + h1]), ] ) def has_correct_position(alpha: Rectangle, beta: Rectangle): x1, y1, w1, h1 = alpha x2, y2, w2, h2 = beta return any( [ any( [abs(x1 - x2) <= 10, abs(y1 - y2) <= 10, abs(x1 + w1 - (x2 + w2)) <= 10, abs(y1 + h1 - (y2 + h2)) <= 10] ), any( [ y2 <= y1 and y1 + h1 <= y2 + h2, y1 <= y2 and y2 + h2 <= y1 + h1, x2 <= x1 and x1 + w1 <= x2 + w2, x1 <= x2 and x2 + w2 <= x1 + w1, ] ), ] ) def is_related(alpha: Rectangle, beta: Rectangle): return (is_near_enough(alpha, beta) and has_correct_position(alpha, beta)) or overlap(alpha, beta) def bounding_rect(alpha: Rectangle, beta: Rectangle): return Rectangle( min(alpha.x1, beta.x1), min(alpha.y1, beta.y1), max(alpha.x2, beta.x2), max(alpha.y2, beta.y2), ) def rectangles_differ(r): return r[0] != r[1] def connect_related_rectangles(rectangles: List[Rectangle]): assert isinstance(rectangles, list) no_new_merges = make_merger_sentinel() return until(no_new_merges, merge_rectangles_once, rectangles) def merge_rectangles_once(rectangles: List[Rectangle]): for alpha, beta in combinations(rectangles, 2): if is_related(alpha, beta): rectangles.remove(alpha) rectangles.remove(beta) rectangles.append(bounding_rect(alpha, beta)) return rectangles return rectangles def make_merger_sentinel(): def no_new_mergers(records): nonlocal number_of_records_so_far number_of_records_now = len(records) if number_of_records_now == number_of_records_so_far: return True else: number_of_records_so_far = number_of_records_now return False number_of_records_so_far = -1 return no_new_mergers