import numpy as np from typing import List, Tuple import math def robust_merge_lines(lines: List[Tuple[float, float, float, float]], angle_thresh: float = 5.0, dist_thresh: float = 5.0) -> List[Tuple[float, float, float, float]]: """ Merge similar line segments using angle and distance thresholds. Args: lines: List of line segments [(x1,y1,x2,y2),...] angle_thresh: Maximum angle difference in degrees dist_thresh: Maximum endpoint distance Returns: List of merged line segments """ if not lines: return [] # Convert to numpy array for easier manipulation lines = np.array(lines) # Calculate line angles angles = np.arctan2(lines[:,3] - lines[:,1], lines[:,2] - lines[:,0]) angles = np.degrees(angles) % 180 # Group similar lines merged = [] used = set() for i, line1 in enumerate(lines): if i in used: continue # Find similar lines similar = [] for j, line2 in enumerate(lines): if j in used: continue # Check angle difference angle_diff = abs(angles[i] - angles[j]) angle_diff = min(angle_diff, 180 - angle_diff) if angle_diff > angle_thresh: continue # Check endpoint distances dist1 = np.linalg.norm(line1[:2] - line2[:2]) dist2 = np.linalg.norm(line1[2:] - line2[2:]) if min(dist1, dist2) > dist_thresh: continue similar.append(j) used.add(j) # Merge similar lines if similar: points = lines[similar].reshape(-1, 2) direction = np.array([np.cos(np.radians(angles[i])), np.sin(np.radians(angles[i]))]) # Project points onto line direction proj = points @ direction # Get extreme points min_idx = np.argmin(proj) max_idx = np.argmax(proj) merged_line = np.concatenate([points[min_idx], points[max_idx]]) merged.append(tuple(merged_line)) return merged def compute_line_angle(x1: float, y1: float, x2: float, y2: float) -> float: """Compute angle of line segment in degrees""" return math.degrees(math.atan2(y2 - y1, x2 - x1)) % 180