garmentiq.landmark.derivation.derive_keypoint_coord
Deriving a landmark coordinate from surrounding landmarks and a mask.
1"""Deriving a landmark coordinate from surrounding landmarks and a mask.""" 2from typing import Tuple, Optional 3import numpy as np 4from .line_intersect import _find_line_line_intersection 5from .mask_intersect import _get_mask_boundary, _find_line_mask_intersections 6from .utils import _calculate_line1_vector, _find_closest_point 7 8 9def derive_keypoint_coord( 10 p1_id: int, 11 p2_id: int, 12 p3_id: int, 13 p4_id: int, 14 p5_id: int, 15 direction: str, 16 landmark_coords: np.array, 17 np_mask: np.array, 18 line_length_factor: float = 10000.0, 19) -> Optional[Tuple[float, float]]: 20 """ 21 Derives the coordinate of a new keypoint based on intersections of lines defined by 22 existing predefined keypoints and the segmentation mask boundary. 23 24 This function implements a geometric method to derive a new landmark: 25 1. It uses `p1_id` as a point on the first line. 26 2. The direction of the first line is derived from `p2_id`, `p3_id`, and `direction` 27 (parallel or perpendicular to the line formed by p2 and p3). 28 3. The second line is defined by `p4_id` and `p5_id`. 29 4. An initial intersection point is found between Line 1 and Line 2. 30 5. The boundary of the `np_mask` is extracted. 31 6. Intersections between Line 1 and the `np_mask` boundary are found. 32 7. The final derived point is the mask intersection point closest to the line-line intersection, 33 or the line-line intersection itself if no mask intersections are found or an error occurs. 34 35 Args: 36 p1_id (int): ID of the first keypoint used to define Line 1. 37 p2_id (int): ID of the second keypoint used to define Line 1's direction. 38 p3_id (int): ID of the third keypoint used to define Line 1's direction. 39 p4_id (int): ID of the fourth keypoint used to define Line 2. 40 p5_id (int): ID of the fifth keypoint used to define Line 2. 41 direction (str): Specifies the direction of Line 1 relative to (p2, p3). 42 Can be "parallel" or "perpendicular". 43 landmark_coords (np.array): NumPy array of all detected landmark coordinates. 44 Shape: (1, N, 2) where N is the total number of landmarks. 45 np_mask (np.array): NumPy array of the segmentation mask. 46 line_length_factor (float, optional): Factor to extend lines for intersection calculations. 47 Defaults to 10000.0. 48 49 Returns: 50 Optional[Tuple[float, float]]: The (x, y) coordinates of the derived keypoint, or None if 51 derivation fails (e.g., parallel lines, no valid mask boundary). 52 """ 53 p1_coord = landmark_coords[:, p1_id - 1, :].reshape(2) 54 p2_coord = landmark_coords[:, p2_id - 1, :].reshape(2) 55 p3_coord = landmark_coords[:, p3_id - 1, :].reshape(2) 56 p4_coord = landmark_coords[:, p4_id - 1, :].reshape(2) 57 p5_coord = landmark_coords[:, p5_id - 1, :].reshape(2) 58 59 # 3. Define Line 1 Vector (v1) 60 v1 = _calculate_line1_vector(p2_coord, p3_coord, direction) 61 if v1 is None: 62 return None # Error or zero vector detected 63 64 # 4. Define Line 2 Vector (v2) 65 v2 = (p5_coord[0] - p4_coord[0], p5_coord[1] - p4_coord[1]) 66 if np.isclose(v2[0], 0) and np.isclose(v2[1], 0): 67 print( 68 f"Warning: Direction vector for Line 2 is zero (p4_id={p4_id} and p5_id={p5_id} likely coincide)." 69 ) 70 return None # Cannot define Line 2 71 72 # 5. Calculate Line-Line Intersection 73 line_intersection_point = _find_line_line_intersection(p1_coord, v1, p4_coord, v2) 74 if line_intersection_point is None: 75 print( 76 "Info: Line 1 and Line 2 are parallel or collinear. No unique intersection." 77 ) 78 return None 79 80 # 6. Load Mask Boundary 81 mask_boundary_geom = _get_mask_boundary(np_mask) 82 if mask_boundary_geom is None or mask_boundary_geom.is_empty: 83 print( 84 "Warning: No valid mask boundary found or mask is empty. Returning line-line intersection." 85 ) 86 return line_intersection_point 87 88 # 7. Find Intersection(s) between Line 1 and Mask Boundary 89 mask_intersection_points = _find_line_mask_intersections( 90 p1_coord, v1, mask_boundary_geom, line_length_factor 91 ) 92 93 # 8. Determine Final Point 94 if mask_intersection_points is None: 95 # An error occurred during intersection calculation 96 print( 97 "Warning: Error finding mask intersections. Returning line-line intersection as fallback." 98 ) 99 return line_intersection_point 100 elif not mask_intersection_points: 101 # No intersection found between line and mask boundary 102 # print("Info: Line 1 does not intersect the mask boundary. Returning line-line intersection.") 103 return line_intersection_point 104 else: 105 # Found intersection(s), find the one closest to the line-line intersection 106 closest_mask_point = _find_closest_point( 107 mask_intersection_points, line_intersection_point 108 ) 109 # _find_closest_point should always return a point if the list is not empty 110 return closest_mask_point
def
derive_keypoint_coord( p1_id: int, p2_id: int, p3_id: int, p4_id: int, p5_id: int, direction: str, landmark_coords: <built-in function array>, np_mask: <built-in function array>, line_length_factor: float = 10000.0) -> Optional[Tuple[float, float]]:
10def derive_keypoint_coord( 11 p1_id: int, 12 p2_id: int, 13 p3_id: int, 14 p4_id: int, 15 p5_id: int, 16 direction: str, 17 landmark_coords: np.array, 18 np_mask: np.array, 19 line_length_factor: float = 10000.0, 20) -> Optional[Tuple[float, float]]: 21 """ 22 Derives the coordinate of a new keypoint based on intersections of lines defined by 23 existing predefined keypoints and the segmentation mask boundary. 24 25 This function implements a geometric method to derive a new landmark: 26 1. It uses `p1_id` as a point on the first line. 27 2. The direction of the first line is derived from `p2_id`, `p3_id`, and `direction` 28 (parallel or perpendicular to the line formed by p2 and p3). 29 3. The second line is defined by `p4_id` and `p5_id`. 30 4. An initial intersection point is found between Line 1 and Line 2. 31 5. The boundary of the `np_mask` is extracted. 32 6. Intersections between Line 1 and the `np_mask` boundary are found. 33 7. The final derived point is the mask intersection point closest to the line-line intersection, 34 or the line-line intersection itself if no mask intersections are found or an error occurs. 35 36 Args: 37 p1_id (int): ID of the first keypoint used to define Line 1. 38 p2_id (int): ID of the second keypoint used to define Line 1's direction. 39 p3_id (int): ID of the third keypoint used to define Line 1's direction. 40 p4_id (int): ID of the fourth keypoint used to define Line 2. 41 p5_id (int): ID of the fifth keypoint used to define Line 2. 42 direction (str): Specifies the direction of Line 1 relative to (p2, p3). 43 Can be "parallel" or "perpendicular". 44 landmark_coords (np.array): NumPy array of all detected landmark coordinates. 45 Shape: (1, N, 2) where N is the total number of landmarks. 46 np_mask (np.array): NumPy array of the segmentation mask. 47 line_length_factor (float, optional): Factor to extend lines for intersection calculations. 48 Defaults to 10000.0. 49 50 Returns: 51 Optional[Tuple[float, float]]: The (x, y) coordinates of the derived keypoint, or None if 52 derivation fails (e.g., parallel lines, no valid mask boundary). 53 """ 54 p1_coord = landmark_coords[:, p1_id - 1, :].reshape(2) 55 p2_coord = landmark_coords[:, p2_id - 1, :].reshape(2) 56 p3_coord = landmark_coords[:, p3_id - 1, :].reshape(2) 57 p4_coord = landmark_coords[:, p4_id - 1, :].reshape(2) 58 p5_coord = landmark_coords[:, p5_id - 1, :].reshape(2) 59 60 # 3. Define Line 1 Vector (v1) 61 v1 = _calculate_line1_vector(p2_coord, p3_coord, direction) 62 if v1 is None: 63 return None # Error or zero vector detected 64 65 # 4. Define Line 2 Vector (v2) 66 v2 = (p5_coord[0] - p4_coord[0], p5_coord[1] - p4_coord[1]) 67 if np.isclose(v2[0], 0) and np.isclose(v2[1], 0): 68 print( 69 f"Warning: Direction vector for Line 2 is zero (p4_id={p4_id} and p5_id={p5_id} likely coincide)." 70 ) 71 return None # Cannot define Line 2 72 73 # 5. Calculate Line-Line Intersection 74 line_intersection_point = _find_line_line_intersection(p1_coord, v1, p4_coord, v2) 75 if line_intersection_point is None: 76 print( 77 "Info: Line 1 and Line 2 are parallel or collinear. No unique intersection." 78 ) 79 return None 80 81 # 6. Load Mask Boundary 82 mask_boundary_geom = _get_mask_boundary(np_mask) 83 if mask_boundary_geom is None or mask_boundary_geom.is_empty: 84 print( 85 "Warning: No valid mask boundary found or mask is empty. Returning line-line intersection." 86 ) 87 return line_intersection_point 88 89 # 7. Find Intersection(s) between Line 1 and Mask Boundary 90 mask_intersection_points = _find_line_mask_intersections( 91 p1_coord, v1, mask_boundary_geom, line_length_factor 92 ) 93 94 # 8. Determine Final Point 95 if mask_intersection_points is None: 96 # An error occurred during intersection calculation 97 print( 98 "Warning: Error finding mask intersections. Returning line-line intersection as fallback." 99 ) 100 return line_intersection_point 101 elif not mask_intersection_points: 102 # No intersection found between line and mask boundary 103 # print("Info: Line 1 does not intersect the mask boundary. Returning line-line intersection.") 104 return line_intersection_point 105 else: 106 # Found intersection(s), find the one closest to the line-line intersection 107 closest_mask_point = _find_closest_point( 108 mask_intersection_points, line_intersection_point 109 ) 110 # _find_closest_point should always return a point if the list is not empty 111 return closest_mask_point
Derives the coordinate of a new keypoint based on intersections of lines defined by existing predefined keypoints and the segmentation mask boundary.
This function implements a geometric method to derive a new landmark:
- It uses
p1_idas a point on the first line. - The direction of the first line is derived from
p2_id,p3_id, anddirection(parallel or perpendicular to the line formed by p2 and p3). - The second line is defined by
p4_idandp5_id. - An initial intersection point is found between Line 1 and Line 2.
- The boundary of the
np_maskis extracted. - Intersections between Line 1 and the
np_maskboundary are found. - The final derived point is the mask intersection point closest to the line-line intersection, or the line-line intersection itself if no mask intersections are found or an error occurs.
Arguments:
- p1_id (int): ID of the first keypoint used to define Line 1.
- p2_id (int): ID of the second keypoint used to define Line 1's direction.
- p3_id (int): ID of the third keypoint used to define Line 1's direction.
- p4_id (int): ID of the fourth keypoint used to define Line 2.
- p5_id (int): ID of the fifth keypoint used to define Line 2.
- direction (str): Specifies the direction of Line 1 relative to (p2, p3). Can be "parallel" or "perpendicular".
- landmark_coords (np.array): NumPy array of all detected landmark coordinates. Shape: (1, N, 2) where N is the total number of landmarks.
- np_mask (np.array): NumPy array of the segmentation mask.
- line_length_factor (float, optional): Factor to extend lines for intersection calculations. Defaults to 10000.0.
Returns:
Optional[Tuple[float, float]]: The (x, y) coordinates of the derived keypoint, or None if derivation fails (e.g., parallel lines, no valid mask boundary).