def knees(points: np.ndarray, t: float, cd: Direction, cc: Concavity, sensitivity: float = 1.0, p: PeakDetection = PeakDetection.Kneedle, debug: bool = False) -> np.ndarray: """Returns the index of the knees point based on the Kneedle method. This implementation uses an heuristic to automatically define the direction and rotation of the concavity. Furthermore, it support three different methods to select the relevant knees: 1. Kneedle : classical algorithm 2. Significant: significant knee peak detection 3. ZScore : significant knee peak detection based on zscore Args: points (np.ndarray): numpy array with the points (x, y) t (float): tau of the sliding window used to smooth the curve cd (Direction): direction of the concavity cc (Concavity): rotation of the concavity sensitivity (float): controls the sensitivity of the peak detection (default 1.0) p (PeakDetection): selects the peak detection method (default PeakDetection.Kneedle) debug (bool): debug flag; when True the algorithm returns more information Returns: np.ndarray: the indexes of the knee points """ Ds = ema.linear(points, t) pmin = Ds.min(axis=0) pmax = Ds.max(axis=0) Dn = (Ds - pmin) / (pmax - pmin) Dd = differences(Dn, cd, cc) knees = [] peaks_idx = pd.all_peaks(Dd) if p is PeakDetection.Kneedle: knees = pd.kneedle_peak_detection(Dd, peaks_idx, sensitivity) elif p is PeakDetection.Significant: knees = pd.significant_peaks(Dd, peaks_idx, sensitivity) elif p is PeakDetection.ZScore: knees = pd.significant_zscore_peaks(Dd, peaks_idx, sensitivity) else: knees = peaks_idx if debug is True: return {'knees': knees, 'dd': Dd, 'peaks': pd.all_peaks(Dd)} else: return knees
def single_knee(points: np.ndarray, t: float, cd: Direction, cc: Concavity) -> int: """Returns the index of the knee point based on the Kneedle method. Args: points (np.ndarray): numpy array with the points (x, y) t (float): tau of the side window used to smooth the curve cd (Direction): direction of the concavity cc (Concavity): rotation of the concavity Returns: int: the index of the knee point """ Ds = ema.linear(points, t) pmin = Ds.min(axis=0) pmax = Ds.max(axis=0) diff = pmax - pmin diff[diff == 0] = 1.0 Dn = (Ds - pmin) / diff Dd = differences(Dn, cd, cc) peaks = pd.all_peaks(Dd) idx = pd.highest_peak(points, peaks) if idx == -1: return None else: return idx
def test_all_peaks(self): points = np.array([[1.0, 5.0], [2.0, -2.0], [3.0, 3.0], [4.0, 1.0], [5.0, 1.0], [6.0, 4.0], [7.0, 4.0], [8.0, -3.0], [9.0, -3.0], [10.0, -5.0], [11.0, 6.0], [12.0, 3.0], [13.0, 0.0], [14.0, 4.0], [15.0, -2.0], [16.0, 7.0], [17.0, -7.0], [18.0, -3.0], [19.0, 0.0], [20.0, 5.0]]) result = peak_detection.all_peaks(points) desired = np.array([2, 10, 13, 15]) npt.assert_equal(result, desired)
def test_highest_peak(self): points = np.array([[1.0, 5.0], [2.0, -2.0], [3.0, 3.0], [4.0, 1.0], [5.0, 1.0], [6.0, 4.0], [7.0, 4.0], [8.0, -3.0], [9.0, -3.0], [10.0, -5.0], [11.0, 6.0], [12.0, 3.0], [13.0, 0.0], [14.0, 4.0], [15.0, -2.0], [16.0, 7.0], [17.0, -7.0], [18.0, -3.0], [19.0, 0.0], [20.0, 5.0]]) peaks_idx = peak_detection.all_peaks(points) result = peak_detection.highest_peak(points, peaks_idx) desired = 15 npt.assert_equal(result, desired)
def test_significant_zscore_peaks_iso(self): points = np.array([[1.0, 5.0], [2.0, -2.0], [3.0, 3.0], [4.0, 1.0], [5.0, 1.0], [6.0, 4.0], [7.0, 4.0], [8.0, -3.0], [9.0, -3.0], [10.0, -5.0], [11.0, 6.0], [12.0, 3.0], [13.0, 0.0], [14.0, 4.0], [15.0, -2.0], [16.0, 7.0], [17.0, -7.0], [18.0, -3.0], [19.0, 0.0], [20.0, 5.0]]) peaks_idx = peak_detection.all_peaks(points) result = peak_detection.significant_zscore_peaks_iso(points, peaks_idx) desired = np.array([2, 10, 15]) npt.assert_equal(result, desired)
def test_zscore_peaks_values(self): points = np.array([[1.0, 5.0], [2.0, -2.0], [3.0, 3.0], [4.0, 1.0], [5.0, 1.0], [6.0, 4.0], [7.0, 4.0], [8.0, -3.0], [9.0, -3.0], [10.0, -5.0], [11.0, 6.0], [12.0, 3.0], [13.0, 0.0], [14.0, 4.0], [15.0, -2.0], [16.0, 7.0], [17.0, -7.0], [18.0, -3.0], [19.0, 0.0], [20.0, 5.0]]) peaks_idx = peak_detection.all_peaks(points) result = peak_detection.zscore_peaks_values(points, peaks_idx) #np.array([2.12, 2.07, 1.21, 2.71]) desired = np.array([2.12, 2.08, 1.12, 2.97]) npt.assert_almost_equal(result, desired, decimal=2)
def test_kneedle_peak_detection_00(self): points = np.array([[.1, -.5], [.2, 0], [.3, 1.66], [.4, 2.5], [.5, 3], [.6, 3.33], [.7, 3.57], [.8, 3.75], [.9, 3.88]]) pmin = points.min(axis=0) pmax = points.max(axis=0) Dn = (points - pmin) / (pmax - pmin) x = Dn[:, 0] y = Dn[:, 1] y_d = y - x Dd = np.column_stack((x, y_d)) peaks_idx = peak_detection.all_peaks(Dd) result = peak_detection.kneedle_peak_detection(Dd, peaks_idx) desired = np.array([3]) npt.assert_equal(result, desired)
def knees(points: np.ndarray, t: float, sensitivity: float, cd: Direction, cc: Concavity, p: PeakDetection) -> np.ndarray: """Returns the index of the knees point based on the Kneedle method. This implementation uses an heuristic to automatically define the direction and rotation of the concavity. Furthermore, it support three different methods to select the relevant knees: 1. Kneedle : classical algorithm 2. Significant: significant knee peak detection 3. ZScore : significant knee peak detection based on zscore Args: points (np.ndarray): numpy array with the points (x, y) t (float): tau of the side window used to smooth the curve sensitivity (float): controls the sensitivity of the peak detection cd (Direction): direction of the concavity cc (Concavity): rotation of the concavity p (PeakDetection): selects the peak detection method Returns: np.ndarray: the indexes of the knee points """ Ds = ema.linear(points, t) pmin = Ds.min(axis=0) pmax = Ds.max(axis=0) Dn = (Ds - pmin) / (pmax - pmin) Dd = differences(Dn, cd, cc) knees = [] if p is PeakDetection.Kneedle: idx = [] lmxThresholds = [] detectKneeForLastLmx = False for i in range(1, len(Dd) - 1): y0 = Dd[i - 1][1] y = Dd[i][1] y1 = Dd[i + 1][1] if y0 < y and y > y1: idx.append(i) tlmx = y - sensitivity / (len(Dd) - 1) lmxThresholds.append(tlmx) detectKneeForLastLmx = True if detectKneeForLastLmx: if y1 < lmxThresholds[-1]: knees.append(idx[-1]) detectKneeForLastLmx = False knees = np.array(knees) elif p is PeakDetection.Significant: peaks_idx = pd.all_peaks(Dd) knees = pd.significant_peaks(Dd, peaks_idx, sensitivity) elif p is PeakDetection.ZScore: peaks_idx = pd.all_peaks(Dd) knees = pd.significant_zscore_peaks(Dd, peaks_idx, sensitivity) return knees