diff --git a/libs/processing/process_area.py b/libs/processing/process_area.py index 238f472..044818b 100644 --- a/libs/processing/process_area.py +++ b/libs/processing/process_area.py @@ -1,4 +1,5 @@ from Bio import pairwise2 +import numpy as np def check_barcode_area(candidates): @@ -28,16 +29,39 @@ def separate_to_lines(rectangles): Returns: list of list: list of rectangles grouped to lines """ - groups = [[rectangles[0]]] - for rectangle in rectangles[1:]: - aligned = False - for i in range(len(groups)): - if rectangle.is_y_aligned(groups[i][-1]) and not aligned: - groups[i].append(rectangle) - aligned = True - if not aligned: - groups.append([rectangle]) - return groups + rectangles.sort(key=lambda rectangle: rectangle.center_y) + average_height = np.mean([rectangle.height for rectangle in rectangles]) + line_break_threshold = average_height * 0.5 + + # Step 3: Group coordinates into lines + lines = [] + current_line = [] + previous_y = rectangles[0].center_y + + for rectangle in rectangles: + if abs(rectangle.center_y - previous_y) > line_break_threshold: + # A line break is detected + lines.append(current_line) + current_line = [] + + current_line.append(rectangle) + previous_y = rectangle.center_y # Update previous_y to the bottom of the current word + + # Don't forget to add the last line if it's not empty + if current_line: + lines.append(current_line) + + return lines + + +def get_max_words(groups): + max_words = 0 + + for group in groups: + for line in group: + max_words = max(max_words, len(line)) + + return max_words def align_pairwise(string_1, string_2): @@ -181,26 +205,28 @@ def align_lines(candidate_lines): Also sort them by y-coordinate to ensure correct order. - # TODO handle cases when there are more than 3 members in a group - Args: - candidate_lines (_type_): _description_ + candidate_lines (list): identified lines from all services Returns: - _type_: _description_ - """ + list: lines grouped by y-coordinate + """ groups = dict() for lines in candidate_lines: for line in lines: + center = np.mean([rectangle.center_y for rectangle in line]) + bottom = max([rectangle.end_y for rectangle in line]) + top = min([rectangle.start_y for rectangle in line]) + grouped = False - for group_key in groups.keys(): - if group_key.is_y_aligned(line[0]): - groups[group_key].append(line) + for group_center in groups.keys(): + if bottom >= group_center >= top: + groups[group_center].append(line) grouped = True if not grouped: - groups[line[0]] = [line] - sorted_values = [v for _, v in sorted(groups.items(), key=lambda item: item[0].center_y)] - return sorted_values + groups[center] = [line] + + return [v for _, v in groups.items()] def general_text_area(candidates, roi): @@ -225,6 +251,15 @@ def general_text_area(candidates, roi): words = [] aligned_groups = align_lines(candidate_lines) - for group in aligned_groups: - words.append(process_lines(group, roi)) + + max_words = get_max_words(aligned_groups) + + if len(aligned_groups) <= 3 and max_words <= 5: + for group in aligned_groups: + words.append(process_lines(group, roi)) + else: + for group in aligned_groups: + # TODO instead of taking result from first service, decide which result is the best? + words.append(' '.join([rectangle.content for rectangle in group[0]])) + return '\n'.join(words) diff --git a/libs/processing/rtree.py b/libs/processing/rtree.py index f279922..44482b3 100644 --- a/libs/processing/rtree.py +++ b/libs/processing/rtree.py @@ -48,4 +48,7 @@ def delete_rectangles(self, rectangles): def prune_residuals(self, residuals): for residual in residuals: candidates = self.find_intersection(residual.get_coords()) - self.delete_rectangles(candidates) + for candidate in candidates: + x, y = (candidate.bbox[0] + candidate.bbox[2])/2, (candidate.bbox[1] + candidate.bbox[3])/2 + if residual.point_is_inside(x, y): + self.delete_rectangle(candidate.id, candidate.bbox) diff --git a/libs/region.py b/libs/region.py index d8e7f1e..67ffb44 100644 --- a/libs/region.py +++ b/libs/region.py @@ -32,6 +32,9 @@ def __str__(self): def update_expected_content(self, expected_content): self.expected_content = expected_content + def point_is_inside(self, x, y): + return self.start_x <= x <= self.end_x and self.start_y <= y <= self.end_y + class ROI(Region): def __init__(self, start_x, start_y, end_x, end_y, varname=None, content_type=None): @@ -57,6 +60,7 @@ def __init__(self, start_x, start_y, end_x, end_y, content): super().__init__(start_x, start_y, end_x, end_y) self.content = content self.center_x, self.center_y = self.compute_center() + self.height = end_y - start_y def __lt__(self, other): return self.center_x < other.center_x