Refactoring: Make single pass rectangle merging stateless
This commit is contained in:
parent
3134021596
commit
3d83489819
@ -14,7 +14,7 @@ from cv_analysis.utils.common import (
|
||||
fill_rectangles,
|
||||
)
|
||||
from cv_analysis.utils.conversion import contour_to_rectangle
|
||||
from cv_analysis.utils.merging import connect_related_rectangles
|
||||
from cv_analysis.utils.merging import merge_related_rectangles
|
||||
from cv_analysis.utils.postprocessing import remove_included, has_no_parent
|
||||
from cv_analysis.utils.rectangle import Rectangle
|
||||
|
||||
@ -23,7 +23,7 @@ def parse_layout(image: np.array) -> List[Rectangle]:
|
||||
rectangles = rcompose(
|
||||
find_segments,
|
||||
remove_included,
|
||||
connect_related_rectangles,
|
||||
merge_related_rectangles,
|
||||
remove_included,
|
||||
)(image)
|
||||
|
||||
|
||||
@ -1,25 +1,52 @@
|
||||
from functools import reduce
|
||||
from itertools import combinations
|
||||
from typing import List
|
||||
from typing import List, Tuple, Set
|
||||
|
||||
from cv_analysis.utils import until, make_merger_sentinel
|
||||
from cv_analysis.utils.rectangle import Rectangle
|
||||
from cv_analysis.utils.spacial import is_related
|
||||
from cv_analysis.utils.spacial import related
|
||||
|
||||
|
||||
def connect_related_rectangles(rectangles: List[Rectangle]) -> List[Rectangle]:
|
||||
def merge_related_rectangles(rectangles: List[Rectangle]) -> List[Rectangle]:
|
||||
"""Merges rectangles that are related to each other, iterating on partial merge results until no more mergers are
|
||||
possible."""
|
||||
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]) -> 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
|
||||
"""Merges rectangles that are related to each other, but does not iterate on the results."""
|
||||
|
||||
def merge_if_related(
|
||||
acc: Tuple[Set[Rectangle], Set[Rectangle]],
|
||||
pair: Tuple[Rectangle, Rectangle],
|
||||
) -> Tuple[Set[Rectangle], Set[Rectangle]]:
|
||||
"""Merges two rectangles if they are related, otherwise returns the accumulator unchanged."""
|
||||
alpha, beta = pair
|
||||
merged, used = acc
|
||||
|
||||
def unused(*args) -> bool:
|
||||
return not used & {*args}
|
||||
|
||||
if unused(alpha) and unused(beta) and related(alpha, beta):
|
||||
return merged | {bounding_rect(alpha, beta)}, used | {alpha, beta}
|
||||
|
||||
else:
|
||||
return merged, used
|
||||
|
||||
rectangles = set(rectangles)
|
||||
merged, used = reduce(merge_if_related, combinations(rectangles, 2), (set(), set()))
|
||||
|
||||
return list(merged | rectangles - used)
|
||||
|
||||
# for alpha, beta in combinations(rectangles, 2):
|
||||
# if related(alpha, beta):
|
||||
# rectangles.remove(alpha)
|
||||
# rectangles.remove(beta)
|
||||
# rectangles.append(bounding_rect(alpha, beta))
|
||||
# return rectangles
|
||||
# return rectangles
|
||||
|
||||
|
||||
def bounding_rect(alpha: Rectangle, beta: Rectangle) -> Rectangle:
|
||||
|
||||
@ -20,6 +20,9 @@ class Rectangle:
|
||||
self.__x2 = nearest_valid(x2)
|
||||
self.__y2 = nearest_valid(y2)
|
||||
|
||||
def __repr__(self):
|
||||
return f"Rectangle({self.x1}, {self.y1}, {self.x2}, {self.y2})"
|
||||
|
||||
@property
|
||||
def x1(self):
|
||||
return self.__x1
|
||||
|
||||
@ -273,7 +273,7 @@ def intersection_along_axis(alpha, beta, axis):
|
||||
return intersection
|
||||
|
||||
|
||||
def is_related(alpha: Rectangle, beta: Rectangle):
|
||||
def related(alpha: Rectangle, beta: Rectangle):
|
||||
return close(alpha, beta) or overlap(alpha, beta)
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user