/// <summary> /// Finds local extrema of a given data set. /// Performs search windowing and thresholding (if enabled). Offline algorithm. /// Clears any previously found extrema from memory before processing data. /// </summary> /// <param name="dataList">Data to operate on.</param> /// <returns>Number of local extrema found.</returns> public int FindLocalExtrema(List <double> dataList) { ExtremaPoint lastExtremaPoint = new ExtremaPoint(); // Clear the maxima fields _maxima.Clear(); _minima.Clear(); // Inspect each element in dataList to determine if its a local maxima/minima for (int i = 0; i < dataList.Count; i++) { ExtremaPoint extremaPoint = this.AnalyseDataPoint(dataList, i, lastExtremaPoint); if (extremaPoint != null) { if (extremaPoint.extremaType == ExtremaPoint.ExtremaType.Maxima) { _maxima.Add(extremaPoint.index); lastExtremaPoint = extremaPoint; } if (extremaPoint.extremaType == ExtremaPoint.ExtremaType.Minima) { _minima.Add(extremaPoint.index); lastExtremaPoint = extremaPoint; } } } // Return total number of extrema found return(_maxima.Count() + _minima.Count()); }
private ExtremaPoint AnalyseDataPoint(List <double> dataList, int index, ExtremaPoint lastExtremaPoint) { List <double> range = dataList; ExtremaPoint currExtremaPoint = new ExtremaPoint(index, ExtremaPoint.ExtremaType.none); // Remove unneeded elements of left-side of window range = range.Skip <double>(lastExtremaPoint.index).ToList <double>(); // Remove unneeded elements of right-side of window. // Need to add 1 because converting from index bounds to number of elements. range = range.Take <double>(index + this._searchWindowRadius - lastExtremaPoint.index + 1).ToList <double>(); AnalyseStates currState = AnalyseStates.CheckIsMostExtremeInSampleWindow; while (true) { switch (currState) { case AnalyseStates.CheckIsMostExtremeInSampleWindow: // Make sure range is not empty and if current dataIList is the maximum/minimum then store index if ((range.Count() > 0) && (dataList[index] == range.Max())) { // If enforcing alternate extrema, quit if this is another maxima if ((_enforceAlternatingExtrema == true) && (lastExtremaPoint.extremaType == ExtremaPoint.ExtremaType.Maxima)) { return(null); } currExtremaPoint.extremaType = ExtremaPoint.ExtremaType.Maxima; currState = AnalyseStates.CheckLeftThreshold; break; } if ((range.Count() > 0) && (dataList[index] == range.Min())) { // If enforcing alternate extrema, quit if this is another minima if ((_enforceAlternatingExtrema == true) && (lastExtremaPoint.extremaType == ExtremaPoint.ExtremaType.Minima)) { return(null); } currExtremaPoint.extremaType = ExtremaPoint.ExtremaType.Minima; currState = AnalyseStates.CheckLeftThreshold; break; } return(null); case AnalyseStates.CheckLeftThreshold: // If thresholding is not enabled, data point has already // met qualifying criteria and return true if (_thresholdingEnabled == false) { return(currExtremaPoint); } // Check for special case. If index is 0, there are no // left elements to check for threshold, therefore // it fails this check automatically if (index == 0) { return(null); } // Check for left threshold. This has to be an iterative process for (int j = index - 1; j >= 0; j--) { if (currExtremaPoint.extremaType == ExtremaPoint.ExtremaType.Maxima) { // Check if data climbs back above the previously found maximum, // and if so, quit to next loop iteration if (dataList[j] > range.Max()) { return(null); } // Check if data exceeds low threshold if (dataList[j] - dataList[index] <= -_thresholdValue) { currState = AnalyseStates.CheckRightThreshold; break; } } else if (currExtremaPoint.extremaType == ExtremaPoint.ExtremaType.Minima) { // Check if data climbs back above the previously found maximum, // and if so, quit to next loop iteration if (dataList[j] < range.Min()) { return(null); } if (dataList[j] - dataList[index] >= _thresholdValue) { currState = AnalyseStates.CheckRightThreshold; break; } } // Check if reached the last data point, and if so, quit if (j == 0) { return(null); } } break; case AnalyseStates.CheckRightThreshold: // Check for special case. If index is 0, there are no // left elements to check for threshold, therefore // it fails this check automatically if (index == dataList.Count - 1) { return(null); } // Check for right threshold. This has to be an iterative process for (int j = index + 1; j <= dataList.Count - 1; j++) { if (currExtremaPoint.extremaType == ExtremaPoint.ExtremaType.Maxima) { // Check if data climbs back above the previously found maximum, // and if so, quit to next loop iteration if (dataList[j] > range.Max()) { return(null); } // Check if data exceeds low threshold if (dataList[j] - dataList[index] <= -_thresholdValue) { currState = AnalyseStates.IsQualifingExtrema; break; } } else if (currExtremaPoint.extremaType == ExtremaPoint.ExtremaType.Minima) { // Check if data climbs back above the previously found maximum, // and if so, quit to next loop iteration if (dataList[j] < range.Min()) { return(null); } if (dataList[j] - dataList[index] >= _thresholdValue) { currState = AnalyseStates.IsQualifingExtrema; break; } } // Check if reached the last data point, and if so, quit if (j == dataList.Count - 1) { return(null); } } break; case AnalyseStates.IsQualifingExtrema: return(currExtremaPoint); } } }