kneeliverse.convex_hull
The following module provides optimized methods for the computation of a convex hullfor 2D.
1# coding: utf-8 2 3''' 4The following module provides optimized methods 5for the computation of a convex hullfor 2D. 6''' 7 8__author__ = 'Mário Antunes' 9__version__ = '1.0' 10__email__ = 'mario.antunes@ua.pt' 11__status__ = 'Development' 12__license__ = 'MIT' 13__copyright__ = ''' 14Copyright (c) 2021-2023 Stony Brook University 15Copyright (c) 2021-2023 The Research Foundation of SUNY 16''' 17 18 19import functools 20import numpy as np 21 22 23def _ccw(a:np.ndarray, b:np.ndarray, c:np.ndarray) -> float: 24 """ 25 Check if three points make a counter-clockwise turn. 26 27 Args: 28 a (np.ndarray): numpy array with a single point (x, y) 29 b (np.ndarray): numpy array with a single point (x, y) 30 c (np.ndarray): numpy array with a single point (x, y) 31 32 Returns: 33 float: $ \\gt 0$ if counter-clockwise; $ \\lt 0$ if clockwise; $ = 0$ if collinear 34 """ 35 return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]) 36 37 38def _dist_points(pi, pj) -> np.ndarray: 39 """ 40 Compute the distance between two points ($p_i$, $p_j$) 41 """ 42 return np.linalg.norm(pi - pj) 43 44 45def _compare_points(p0, pi, pj) -> int: 46 o = _ccw(p0, pi, pj) 47 if o == 0: 48 return -1 if _dist_points(p0, pj) >= _dist_points(p0, pi) else 1 49 else: 50 return 1 if o > 0 else -1 51 52 53def _sort_points(points:np.ndarray) -> np.ndarray: 54 """ 55 Sort the points based on the polar angle to the first point. 56 57 The functions does not calculate the angle, instead, it calculate the relative orientation 58 of two points to find out which point makes the larger angle. 59 60 Args: 61 points (np.ndarray): numpy array with the points (x, y) 62 63 Returns: 64 np.ndarray: the sorted points 65 """ 66 67 # find the smaller point and respective index 68 p0 = min(points, key=lambda p: (p[0], p[1])) 69 p0_idx = np.where(np.all(points==p0,axis=1))[0][0] 70 71 # create a mask to select all points except the smaller point 72 mask = np.full(len(points), True) 73 mask[p0_idx] = False 74 temp_list = points[mask] 75 76 # return a copy of the points sorted 77 return [p0] + sorted(temp_list, key=functools.cmp_to_key(lambda x, y: _compare_points(p0, x, y))) 78 79 80def graham_scan(points:np.ndarray) -> np.ndarray: 81 """ 82 Returns an array of indexes that make up the convex hull surrounding the points. 83 84 Args: 85 points (np.ndarray): numpy array with the points (x, y) 86 87 Returns: 88 np.ndarray: the indexes of the hull points 89 """ 90 # convex_hull is a stack of points beginning with the leftmost point. 91 stack = [] 92 sorted_points = _sort_points(points) 93 94 if len(sorted_points) >= 3: 95 stack.append(sorted_points[0]) 96 stack.append(sorted_points[1]) 97 stack.append(sorted_points[2]) 98 99 for i in range(3, len(sorted_points)): 100 p = sorted_points[i] 101 102 while _ccw(stack[-2], stack[-1], p) >= 0: 103 stack.pop() 104 stack.append(p) 105 106 # the stack is now a representation of the convex hull, return it. 107 # convert the points into indexes 108 hull = [np.where(np.all(points==p,axis=1))[0][0] for p in stack] 109 return np.array(hull) 110 111 112def graham_scan_lower(points:np.ndarray) -> np.ndarray: 113 """ 114 Returns an array of indexes that make up the lower convex hull. 115 116 Args: 117 points (np.ndarray): numpy array with the points (x, y) 118 119 Returns: 120 np.ndarray: the indexes of the hull points 121 """ 122 #sorted_points = sort_points(points) 123 # Add p0 and p1 to the stack 124 stack = [0, 1] 125 126 for i in range(2, len(points)): 127 # if we turn clockwise to reach this point, pop the last point from the stack, else, append this point to it. 128 while len(stack) > 1 and ccw(points[stack[-2]], points[stack[-1]], points[i]) <= 0: 129 stack.pop() 130 stack.append(i) 131 # the stack is now a representation of the convex hull, return it. 132 # convert the points into indexes 133 #x = points[:, 0] 134 #hull = [np.where(x == p[0])[0][0] for p in stack] 135 return np.array(stack) 136 137 138def graham_scan_upper(points:np.ndarray) -> np.ndarray: 139 """ 140 Returns an array of indexes that make up the upper convex hull. 141 142 Args: 143 points (np.ndarray): numpy array with the points (x, y) 144 145 Returns: 146 np.ndarray: the indexes of the hull points 147 """ 148 #sorted_points = sort_points(points) 149 # Add p0 and p1 to the stack 150 stack = [0, 1] 151 152 for i in range(2, len(points)): 153 # if we turn clockwise to reach this point, pop the last point from the stack, else, append this point to it. 154 while len(stack) > 1 and ccw(points[i], points[stack[-1]], points[stack[-2]]) <= 0: 155 stack.pop() 156 stack.append(i) 157 # the stack is now a representation of the convex hull, return it. 158 # convert the points into indexes 159 #x = points[:, 0] 160 #hull = [np.where(x == p[0])[0][0] for p in stack] 161 return np.array(stack)
def
graham_scan(points: numpy.ndarray) -> numpy.ndarray:
81def graham_scan(points:np.ndarray) -> np.ndarray: 82 """ 83 Returns an array of indexes that make up the convex hull surrounding the points. 84 85 Args: 86 points (np.ndarray): numpy array with the points (x, y) 87 88 Returns: 89 np.ndarray: the indexes of the hull points 90 """ 91 # convex_hull is a stack of points beginning with the leftmost point. 92 stack = [] 93 sorted_points = _sort_points(points) 94 95 if len(sorted_points) >= 3: 96 stack.append(sorted_points[0]) 97 stack.append(sorted_points[1]) 98 stack.append(sorted_points[2]) 99 100 for i in range(3, len(sorted_points)): 101 p = sorted_points[i] 102 103 while _ccw(stack[-2], stack[-1], p) >= 0: 104 stack.pop() 105 stack.append(p) 106 107 # the stack is now a representation of the convex hull, return it. 108 # convert the points into indexes 109 hull = [np.where(np.all(points==p,axis=1))[0][0] for p in stack] 110 return np.array(hull)
Returns an array of indexes that make up the convex hull surrounding the points.
Arguments:
- points (np.ndarray): numpy array with the points (x, y)
Returns:
np.ndarray: the indexes of the hull points
def
graham_scan_lower(points: numpy.ndarray) -> numpy.ndarray:
113def graham_scan_lower(points:np.ndarray) -> np.ndarray: 114 """ 115 Returns an array of indexes that make up the lower convex hull. 116 117 Args: 118 points (np.ndarray): numpy array with the points (x, y) 119 120 Returns: 121 np.ndarray: the indexes of the hull points 122 """ 123 #sorted_points = sort_points(points) 124 # Add p0 and p1 to the stack 125 stack = [0, 1] 126 127 for i in range(2, len(points)): 128 # if we turn clockwise to reach this point, pop the last point from the stack, else, append this point to it. 129 while len(stack) > 1 and ccw(points[stack[-2]], points[stack[-1]], points[i]) <= 0: 130 stack.pop() 131 stack.append(i) 132 # the stack is now a representation of the convex hull, return it. 133 # convert the points into indexes 134 #x = points[:, 0] 135 #hull = [np.where(x == p[0])[0][0] for p in stack] 136 return np.array(stack)
Returns an array of indexes that make up the lower convex hull.
Arguments:
- points (np.ndarray): numpy array with the points (x, y)
Returns:
np.ndarray: the indexes of the hull points
def
graham_scan_upper(points: numpy.ndarray) -> numpy.ndarray:
139def graham_scan_upper(points:np.ndarray) -> np.ndarray: 140 """ 141 Returns an array of indexes that make up the upper convex hull. 142 143 Args: 144 points (np.ndarray): numpy array with the points (x, y) 145 146 Returns: 147 np.ndarray: the indexes of the hull points 148 """ 149 #sorted_points = sort_points(points) 150 # Add p0 and p1 to the stack 151 stack = [0, 1] 152 153 for i in range(2, len(points)): 154 # if we turn clockwise to reach this point, pop the last point from the stack, else, append this point to it. 155 while len(stack) > 1 and ccw(points[i], points[stack[-1]], points[stack[-2]]) <= 0: 156 stack.pop() 157 stack.append(i) 158 # the stack is now a representation of the convex hull, return it. 159 # convert the points into indexes 160 #x = points[:, 0] 161 #hull = [np.where(x == p[0])[0][0] for p in stack] 162 return np.array(stack)
Returns an array of indexes that make up the upper convex hull.
Arguments:
- points (np.ndarray): numpy array with the points (x, y)
Returns:
np.ndarray: the indexes of the hull points