garmentiq.landmark.derivation.mask_intersect
Intersection of a line with the garment mask contour.
1"""Intersection of a line with the garment mask contour.""" 2import numpy as np 3import cv2 4from typing import Optional, List, Tuple, Any 5from shapely.geometry import Point, LineString, Polygon, MultiPoint, MultiLineString 6from shapely.ops import unary_union 7 8 9def _get_mask_boundary(mask: np.ndarray): 10 """ 11 Processes a binary or grayscale mask array and returns the primary boundary as Shapely geometry. 12 13 This function finds contours in the mask, converts them into Shapely Polygon boundaries 14 or LineStrings, and then unions them into a single (potentially Multi-) geometry. 15 16 Args: 17 mask (np.ndarray): The input binary or grayscale mask array. 18 19 Returns: 20 Optional[Any]: A Shapely geometry (Polygon.boundary, LineString, MultiPoint, or MultiLineString) 21 representing the mask boundary, or None if the mask is invalid or no contours are found. 22 """ 23 try: 24 if mask is None or not isinstance(mask, np.ndarray): 25 print("Error: Provided mask is not a valid NumPy array.") 26 return None 27 28 # Ensure binary mask (values 0 or 1) 29 binary_mask = (mask > 0).astype(np.uint8) 30 31 contours, _ = cv2.findContours( 32 binary_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE 33 ) 34 35 if not contours: 36 print("Warning: No contours found in the mask array.") 37 return None 38 39 geometries = [] 40 for contour in contours: 41 points = [tuple(p[0]) for p in contour] 42 if len(points) >= 3: 43 geometries.append(Polygon(points).boundary) 44 elif len(points) == 2: 45 geometries.append(LineString(points)) 46 47 if not geometries: 48 print("Warning: No valid boundary geometries found in the mask.") 49 return None 50 51 return unary_union(geometries) 52 53 except Exception as e: 54 print(f"Error processing mask array: {str(e)}") 55 return None 56 57 58def _find_line_mask_intersections( 59 line_point: Tuple[float, float], 60 line_vector: Tuple[float, float], 61 mask_boundary: Any, # Shapely Geometry 62 line_length_factor: float, 63) -> Optional[List[Tuple[float, float]]]: 64 """ 65 Finds intersection points between a line (defined by a point and vector) and a Shapely mask boundary. 66 67 Constructs a long Shapely LineString from the input line definition and 68 computes its intersection with the provided `mask_boundary` geometry. 69 70 Args: 71 line_point (Tuple[float, float]): A point (x, y) on the line. 72 line_vector (Tuple[float, float]): The direction vector (dx, dy) of the line. 73 mask_boundary (Any): A Shapely Geometry object representing the mask boundary (e.g., from `_get_mask_boundary`). 74 line_length_factor (float): A factor to extend the line segment for intersection testing, 75 ensuring it crosses the entire mask if needed. 76 77 Returns: 78 Optional[List[Tuple[float, float]]]: A list of (x, y) tuples for all unique intersection points, 79 an empty list if no intersections, or None if an error occurs. 80 """ 81 try: 82 # Create a long Shapely line representing the mathematical line 83 norm_v = np.linalg.norm(line_vector) 84 if np.isclose(norm_v, 0): 85 print("Error: Line vector is zero during Shapely line creation.") 86 return None # Cannot create line 87 88 unit_v = (line_vector[0] / norm_v, line_vector[1] / norm_v) 89 90 pt_a = ( 91 line_point[0] - line_length_factor * unit_v[0], 92 line_point[1] - line_length_factor * unit_v[1], 93 ) 94 pt_b = ( 95 line_point[0] + line_length_factor * unit_v[0], 96 line_point[1] + line_length_factor * unit_v[1], 97 ) 98 shapely_line = LineString([pt_a, pt_b]) 99 100 # Calculate intersection 101 intersection = mask_boundary.intersection(shapely_line) 102 103 # Process intersection results 104 if intersection.is_empty: 105 return [] # Return empty list for no intersection 106 107 intersection_points = [] 108 geoms_to_process = [] 109 110 if isinstance(intersection, Point): 111 geoms_to_process.append(intersection) 112 elif isinstance(intersection, (MultiPoint, LineString, MultiLineString)): 113 # Use .geoms for MultiPoint/MultiLineString, .coords for LineString 114 if hasattr(intersection, "geoms"): 115 geoms_to_process.extend(list(intersection.geoms)) 116 elif hasattr(intersection, "coords"): # LineString (boundary coincidence) 117 # Extract points from the LineString coords 118 coords = list(intersection.coords) 119 for coord in coords: 120 intersection_points.append(coord) # Add individual vertices 121 # Avoid processing LineString further as Point below 122 123 # Extract coordinates from Point geometries 124 for geom in geoms_to_process: 125 if isinstance(geom, Point): 126 intersection_points.append((geom.x, geom.y)) 127 128 # Remove duplicates if necessary (e.g., from LineString endpoints) 129 # Using list(dict.fromkeys(intersection_points)) preserves order unlike set 130 unique_intersection_points = list(dict.fromkeys(intersection_points)) 131 132 return unique_intersection_points 133 134 except Exception as e: 135 print(f"Error during Shapely intersection calculation: {str(e)}") 136 return None # Indicate an error occurred