diff --git a/cv_analysis/utils/connect_rects.py b/cv_analysis/utils/connect_rects.py index 09d48bb..9a438da 100644 --- a/cv_analysis/utils/connect_rects.py +++ b/cv_analysis/utils/connect_rects.py @@ -6,10 +6,14 @@ def is_near_enough(rect_pair, max_gap=14): x1, y1, w1, h1 = rect_pair[0] x2, y2, w2, h2 = rect_pair[1] - 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]) + 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_overlapping(rect_pair): @@ -23,28 +27,36 @@ def is_overlapping(rect_pair): 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])]) + 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_position1(rect_pair): x1, y1, w1, h1 = rect_pair[0] x2, y2, w2, h2 = rect_pair[1] - 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])]) + 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(rect_pair): - return (is_near_enough(rect_pair) and has_correct_position1(rect_pair)) or is_overlapping( - rect_pair) + return (is_near_enough(rect_pair) and has_correct_position1(rect_pair)) or is_overlapping(rect_pair) def fuse_rects(rect1, rect2): diff --git a/cv_analysis/utils/structures.py b/cv_analysis/utils/structures.py index cafbf97..9e10433 100644 --- a/cv_analysis/utils/structures.py +++ b/cv_analysis/utils/structures.py @@ -1,8 +1,8 @@ from json import dumps - from typing import Iterable + import numpy as np -from funcy import identity +from funcy import identity, juxt class Rectangle: @@ -80,24 +80,9 @@ class Rectangle: return any(rect.includes(self) for rect in rectangles if not rect == self) def adjacent(self, rect2: "Rectangle", tolerance=7): - # tolerance=1 was set too low; most lines are 2px wide - def adjacent2d(sixtuple): - g, h, i, j, k, l = sixtuple - return (abs(g - h) <= tolerance) and any(k <= p <= l for p in [i, j]) - if rect2 is None: return False - return any( - map( - adjacent2d, - [ - (self.x2, rect2.x1, rect2.y1, rect2.y2, self.y1, self.y2), - (self.x1, rect2.x2, rect2.y1, rect2.y2, self.y1, self.y2), - (self.y2, rect2.y1, rect2.x1, rect2.x2, self.x1, self.x2), - (self.y1, rect2.y2, rect2.x1, rect2.x2, self.x1, self.x2), - ], - ) - ) + return adjacent(self, rect2, tolerance) @classmethod def from_xyxy(cls, xyxy_tuple, discrete=True): @@ -126,6 +111,87 @@ class Rectangle: return all([self.x1 == rect.x1, self.y1 == rect.y1, self.w == rect.w, self.h == rect.h]) -class Contour: - def __init__(self): - pass +def adjacent(alpha: Rectangle, beta: Rectangle, tolerance=7): + """Check if the two rectangles are adjacent to each other.""" + return any( + juxt( + # +---+ + # | | +---+ + # | a | | b | + # | | +___+ + # +___+ + alpha_is_left_of_beta_within_tolerance_and_beta_overlaps_alphas_y_range, + # +---+ + # +---+ | | + # | b | | a | + # +___+ | | + # +___+ + alpha_is_right_of_beta_within_tolerance_and_beta_overlaps_alphas_y_range, + # +-----------+ + # | a | + # +___________+ + # +-----+ + # | b | + # +_____+ + alpha_is_above_beta_within_tolerance_and_beta_overlaps_alphas_x_range, + # +-----+ + # | b | + # +_____+ + # +-----------+ + # | a | + # +___________+ + alpha_is_below_beta_within_tolerance_and_beta_overlaps_alphas_x_range, + )(alpha, beta, tolerance) + ) + + +def alpha_is_left_of_beta_within_tolerance_and_beta_overlaps_alphas_y_range(alpha: Rectangle, beta: Rectangle, tol): + """Check if the first rectangle is left of the other within a tolerance and also overlaps the other's y range.""" + return adjacent_along_one_axis_and_overlapping_along_perpendicular_axis( + alpha.x2, beta.x1, beta.y1, beta.y2, alpha.y1, alpha.y2, tolerance=tol + ) + + +def alpha_is_right_of_beta_within_tolerance_and_beta_overlaps_alphas_y_range(alpha: Rectangle, beta: Rectangle, tol): + """Check if the first rectangle is right of the other within a tolerance and also overlaps the other's y range.""" + return adjacent_along_one_axis_and_overlapping_along_perpendicular_axis( + alpha.x1, beta.x2, beta.y1, beta.y2, alpha.y1, alpha.y2, tolerance=tol + ) + + +def alpha_is_above_beta_within_tolerance_and_beta_overlaps_alphas_x_range(alpha: Rectangle, beta: Rectangle, tol): + """Check if the first rectangle is above the other within a tolerance and also overlaps the other's x range.""" + return adjacent_along_one_axis_and_overlapping_along_perpendicular_axis( + alpha.y2, beta.y1, beta.x1, beta.x2, alpha.x1, alpha.x2, tolerance=tol + ) + + +def alpha_is_below_beta_within_tolerance_and_beta_overlaps_alphas_x_range(alpha: Rectangle, beta: Rectangle, tol): + """Check if the first rectangle is below the other within a tolerance and also overlaps the other's x range.""" + return adjacent_along_one_axis_and_overlapping_along_perpendicular_axis( + alpha.y1, beta.y2, beta.x1, beta.x2, alpha.x1, alpha.x2, tolerance=tol + ) + + +def adjacent_along_one_axis_and_overlapping_along_perpendicular_axis( + axis_0_point_1, + axis_1_point_2, + axis_1_contained_point_1, + axis_1_contained_point_2, + axis_1_lower_bound, + axis_1_upper_bound, + tolerance, +): + """Check if two points are adjacent along one axis and two other points overlap a range along the perpendicular + axis.""" + return all( + [ + abs(axis_0_point_1 - axis_1_point_2) <= tolerance, + any( + [ + axis_1_lower_bound <= p <= axis_1_upper_bound + for p in [axis_1_contained_point_1, axis_1_contained_point_2] + ] + ), + ] + )