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