def multi_knee(get_knee: typing.Callable, points: np.ndarray, t1: float = 0.99, t2: int = 3) -> np.ndarray: """ Wrapper that convert a single knee point detection into a multi knee point detector. It uses recursion on the left and right parts of the curve after detecting the current knee. Args: get_knee (typing.Callable): method that returns a single knee point points (np.ndarray): numpy array with the points (x, y) t1 (float): the coefficient of determination used as a threshold (default 0.99) t2 (int): the mininum number of points used as a threshold (default 3) Returns: np.ndarray: knee points on the curve """ stack = [(0, len(points))] knees = [] while stack: left, right = stack.pop() pt = points[left:right] if len(pt) > t2: coef = linear_fit_points(pt) if linear_r2_points(pt, coef) < t1: rv = get_knee(pt) if rv is not None: idx = rv + left knees.append(idx) stack.append((left, idx+1)) stack.append((idx+1, right)) knees.sort() return np.array(knees)
def multi_knee(get_knee: typing.Callable, points: np.ndarray, t1: float = 0.01, t2: int = 3, cost: lf.Linear_Metrics = lf.Linear_Metrics.rmspe) -> np.ndarray: """ Wrapper that convert a single knee point detection into a multi knee point detector. It uses recursion on the left and right parts of the curve after detecting the current knee. Args: get_knee (typing.Callable): method that returns a single knee point points (np.ndarray): numpy array with the points (x, y) t1 (float): the coefficient of determination used as a threshold (default 0.01) t2 (int): the mininum number of points used as a threshold (default 3) cost (lf.Linear_Metrics): the cost method used to evaluate a point set (default: lf.Linear_Metrics.rmspe) Returns: np.ndarray: knee points on the curve """ stack = [(0, len(points))] knees = [] while stack: left, right = stack.pop() pt = points[left:right] if len(pt) > t2: if len(pt) <= 2: if cost is lf.Linear_Metrics.rmspe: r = 0.0 else: r = 1.0 else: coef = lf.linear_fit_points(pt) if cost is lf.Linear_Metrics.rmspe: r = lf.rmspe_points(pt, coef) else: r = lf.linear_r2_points(pt, coef) curved = r >= t1 if cost is lf.Linear_Metrics.rmspe else r < t1 #coef = lf.linear_fit_points(pt) # if lf.linear_r2_points(pt, coef) < t1: if curved: rv = get_knee(pt) if rv is not None: idx = rv + left knees.append(idx) stack.append((left, idx+1)) stack.append((idx+1, right)) knees.sort() return np.array(knees)
def rdp(points: np.ndarray, r: float = 0.9) -> tuple: """ Ramer–Douglas–Peucker (RDP) algorithm. Is an algorithm that decimates a curve composed of line segments to a similar curve with fewer points. This version uses the coefficient of determination to decided whenever to keep or remove a line segment. Args: points (np.ndarray): numpy array with the points (x, y) r(float): the coefficient of determination threshold (default 0.9) Returns: tuple: the reduced space, the points that were removed """ if len(points) <= 2: determination = 1.0 else: coef = lf.linear_fit_points(points) determination = lf.linear_r2_points(points, coef) if determination < r: d = perpendicular_distance_points(points, points[0], points[-1]) index = np.argmax(d) left, left_points = rdp(points[0:index + 1], r) right, right_points = rdp(points[index:len(points)], r) points_removed = np.concatenate((left_points, right_points), axis=0) return np.concatenate((left[0:len(left) - 1], right)), points_removed else: rv = np.empty([2, 2]) rv[0] = points[0] rv[1] = points[-1] points_removed = np.array([[points[0][0], len(points) - 2.0]]) return rv, points_removed
def test_r2_two(self): points = np.array([[0.0, 1.0], [1.0, 5.0]]) coef = lf.linear_fit_points(points) result = lf.linear_r2_points(points, coef) desired = 1.0 self.assertEqual(result, desired)
def rdp(points: np.ndarray, t: float = 0.01, cost: lf.Linear_Metrics = lf.Linear_Metrics.rpd, distance: RDP_Distance = RDP_Distance.shortest) -> tuple: """ Ramer–Douglas–Peucker (RDP) algorithm. Is an algorithm that decimates a curve composed of line segments to a similar curve with fewer points. This version uses different cost functions to decided whenever to keep or remove a line segment. Args: points (np.ndarray): numpy array with the points (x, y) t (float): the coefficient of determination threshold (default 0.01) cost (lf.Linear_Metrics): the cost method used to evaluate a point set (default: lf.Linear_Metrics.rmspe) distance (RDP_Distance): the distance metric used to decide the split point (default: RDP_Distance.shortest) Returns: tuple: the index of the reduced space, the points that were removed """ stack = [(0, len(points))] reduced = [] removed = [] # select the distance metric to be used distance_points = None if distance is RDP_Distance.shortest: distance_points = lf.shortest_distance_points elif distance is RDP_Distance.perpendicular: distance_points = lf.perpendicular_distance_points else: distance_points = lf.shortest_distance_points while stack: left, right = stack.pop() pt = points[left:right] if len(pt) <= 2: if cost is lf.Linear_Metrics.r2: r = 1.0 else: r = 0.0 else: coef = lf.linear_fit_points(pt) if cost is lf.Linear_Metrics.r2: r = lf.linear_r2_points(pt, coef) elif cost is lf.Linear_Metrics.rmspe: r = lf.rmspe_points(pt, coef) elif cost is lf.Linear_Metrics.rmsle: r = lf.rmsle_points(pt, coef) else: r = lf.rpd_points(pt, coef) curved = r < t if cost is lf.Linear_Metrics.r2 else r >= t if curved: d = distance_points(pt, pt[0], pt[-1]) index = np.argmax(d) stack.append((left + index, left + len(pt))) stack.append((left, left + index + 1)) else: reduced.append(left) removed.append([left, len(pt) - 2.0]) reduced.append(len(points) - 1) return np.array(reduced), np.array(removed)
def grdp(points: np.ndarray, t: float = 0.01, cost: lf.Linear_Metrics = lf.Linear_Metrics.rpd, distance: RDP_Distance = RDP_Distance.shortest) -> tuple: stack = [(0, len(points))] reduced = [] removed = [] curved = True while curved: _, left, right = stack.pop() pt = points[left:right] d = distance_points(pt, pt[0], pt[-1]) index = np.argmax(d) # add the relevant point to the reduced set reduced.append(left + index) # compute the cost of the left and right parts left_cost = np.max(distance_points(pt[0:index + 1], pt[0], pt[index])) right_cost = np.max(distance_points(pt[index:len(pt)], pt[0], pt[-1])) # Add the points to the stack stack.append((right_cost, left + index, left + len(pt))) stack.append((left_cost, left, left + index + 1)) # Sort the stack based on the cost stack.sort(key=lambda t: t[0]) length -= 1 # compute the cost of the current solution curved = r < t if cost is lf.Linear_Metrics.r2 else r >= t # add first and last points reduced.append(0) reduced.append(len(points) - 1) # sort indexes reduced.sort() return np.array(reduced) while stack: left, right = stack.pop() pt = points[left:right] if len(pt) <= 2: if cost is lf.Linear_Metrics.r2: r = 1.0 else: r = 0.0 else: coef = lf.linear_fit_points(pt) if cost is lf.Linear_Metrics.r2: r = lf.linear_r2_points(pt, coef) elif cost is lf.Linear_Metrics.rmspe: r = lf.rmspe_points(pt, coef) elif cost is lf.Linear_Metrics.rmsle: r = lf.rmsle_points(pt, coef) else: r = lf.rpd_points(pt, coef) curved = r < t if cost is lf.Linear_Metrics.r2 else r >= t if curved: d = distance_points(pt, pt[0], pt[-1]) index = np.argmax(d) stack.append((left + index, left + len(pt))) stack.append((left, left + index + 1)) else: reduced.append(left) removed.append([left, len(pt) - 2.0]) reduced.append(len(points) - 1) return np.array(reduced), np.array(removed)