private void ExamineNarrowPeaks(PeakDataContainer peakData, udtSICPeakFinderOptionsType peakFinderOptions) { if (peakData.Peaks.Count <= 0) { // No peaks were found; create a new peak list using the original peak location index as the peak center peakData.Peaks = new List <clsPeak> { new clsPeak(peakData.OriginalPeakLocationIndex) }; return; } if (!peakFinderOptions.ReturnClosestPeak) { return; } // Make sure one of the peaks is within 1 of the original peak location var blnSuccess = false; foreach (var peak in peakData.Peaks) { if (peak.LocationIndex - peakData.OriginalPeakLocationIndex <= 1) { blnSuccess = true; break; } } if (blnSuccess) { // One of the peaks includes data point peakData.OriginalPeakLocationIndex return; } // None of the peaks includes peakData.OriginalPeakLocationIndex var newPeak = new clsPeak(peakData.OriginalPeakLocationIndex) { Area = peakData.YData[peakData.OriginalPeakLocationIndex] }; peakData.Peaks.Add(newPeak); }
/// <summary> /// /// </summary> /// <param name="peakDetector"></param> /// <param name="scanNumbers"></param> /// <param name="peakData"></param> /// <param name="peakFinderOptions"></param> /// <returns>Detected peaks will be in the peakData object</returns> private bool FindPeaksWork( PeakFinder peakDetector, IList <int> scanNumbers, PeakDataContainer peakData, udtSICPeakFinderOptionsType peakFinderOptions) { const float sngPeakMaximum = 0; bool validPeakFound; // Smooth the Y data, and store in peakData.SmoothedYData // Note that if using a Butterworth filter, then we increase peakData.PeakWidthPointsMinimum if too small, compared to 1/SamplingFrequency var peakWidthPointsMinimum = peakData.PeakWidthPointsMinimum; var dataIsSmoothed = SmoothData(peakData.YData, peakData.DataCount, peakFinderOptions, ref peakWidthPointsMinimum, out var smoothedYData, out var errorMessage); // peakWidthPointsMinimum may have been auto-updated peakData.PeakWidthPointsMinimum = peakWidthPointsMinimum; // Store the smoothed data in the data container peakData.SetSmoothedData(smoothedYData); var peakDetectIntensityThresholdPercentageOfMaximum = (int)Math.Round(peakFinderOptions.IntensityThresholdFractionMax * 100); const int peakWidthInSigma = 2; const bool useValleysForPeakWidth = true; const bool movePeakLocationToMaxIntensity = true; if (peakFinderOptions.FindPeaksOnSmoothedData && dataIsSmoothed) { peakData.Peaks = peakDetector.DetectPeaks( peakData.XData, peakData.SmoothedYData, peakFinderOptions.IntensityThresholdAbsoluteMinimum, peakData.PeakWidthPointsMinimum, peakDetectIntensityThresholdPercentageOfMaximum, peakWidthInSigma, useValleysForPeakWidth, movePeakLocationToMaxIntensity); } else { // Look for the peaks, using peakData.PeakWidthPointsMinimum as the minimum peak width peakData.Peaks = peakDetector.DetectPeaks( peakData.XData, peakData.YData, peakFinderOptions.IntensityThresholdAbsoluteMinimum, peakData.PeakWidthPointsMinimum, peakDetectIntensityThresholdPercentageOfMaximum, peakWidthInSigma, useValleysForPeakWidth, movePeakLocationToMaxIntensity); } if (peakData.Peaks == null) { // Fatal error occurred while finding peaks return(false); } if (peakData.PeakWidthPointsMinimum == MINIMUM_PEAK_WIDTH) { // Testing the minimum peak width; run some checks ExamineNarrowPeaks(peakData, peakFinderOptions); } if (peakData.Peaks.Count <= 0) { // No peaks were found return(false); } foreach (var peak in peakData.Peaks) { peak.IsValid = false; // Find the center and boundaries of this peak // Make sure peak.LocationIndex is between peak.LeftEdge and peak.RightEdge if (peak.LeftEdge > peak.LocationIndex) { Console.WriteLine("peak.LeftEdge is > peak.LocationIndex; this is probably a programming error"); peak.LeftEdge = peak.LocationIndex; } if (peak.RightEdge < peak.LocationIndex) { Console.WriteLine("peak.RightEdge is < peak.LocationIndex; this is probably a programming error"); peak.RightEdge = peak.LocationIndex; } // See if the peak boundaries (left and right edges) need to be narrowed or expanded // Do this by stepping left or right while the intensity is decreasing. If an increase is found, but the // next point after the increasing point is less than the current point, then possibly keep stepping; the // test for whether to keep stepping is that the next point away from the increasing point must be less // than the current point. If this is the case, replace the increasing point with the average of the // current point and the point two points away // // Use smoothed data for this step // Determine the smoothing window based on peakData.PeakWidthPointsMinimum // If peakData.PeakWidthPointsMinimum <= 4 then do not filter if (!dataIsSmoothed) { // Need to smooth the data now peakWidthPointsMinimum = peakData.PeakWidthPointsMinimum; dataIsSmoothed = SmoothData( peakData.YData, peakData.DataCount, peakFinderOptions, ref peakWidthPointsMinimum, out smoothedYData, out errorMessage); // peakWidthPointsMinimum may have been auto-updated peakData.PeakWidthPointsMinimum = peakWidthPointsMinimum; // Store the smoothed data in the data container peakData.SetSmoothedData(smoothedYData); } // First see if we need to narrow the peak by looking for decreasing intensities moving toward the peak center // We'll use the unsmoothed data for this while (peak.LeftEdge < peak.LocationIndex - 1) { if (peakData.YData[peak.LeftEdge] > peakData.YData[peak.LeftEdge + 1]) { // OrElse (usedSmoothedDataForPeakDetection AndAlso peakData.SmoothedYData[peak.LeftEdge) < 0) Then peak.LeftEdge += 1; } else { break; } } while (peak.RightEdge > peak.LocationIndex + 1) { if (peakData.YData[peak.RightEdge - 1] < peakData.YData[peak.RightEdge]) { // OrElse (usedSmoothedDataForPeakDetection AndAlso peakData.SmoothedYData[peak.RightEdge) < 0) Then peak.RightEdge -= 1; } else { break; } } // Now see if we need to expand the peak by looking for decreasing intensities moving away from the peak center, // but allowing for small increases // We'll use the smoothed data for this; if we encounter negative values in the smoothed data, we'll keep going until we reach the low point since huge peaks can cause some odd behavior with the Butterworth filter // Keep track of the number of times we step over an increased value ExpandPeakLeftEdge(peakData, peakFinderOptions, peak, sngPeakMaximum, dataIsSmoothed); ExpandPeakRightEdge(peakData, peakFinderOptions, peak, sngPeakMaximum, dataIsSmoothed); peak.IsValid = true; if (!peakFinderOptions.ReturnClosestPeak) { continue; } // If peakData.OriginalPeakLocationIndex is not between peak.LeftEdge and peak.RightEdge, then check // if the scan number for peakData.OriginalPeakLocationIndex is within .MaxDistanceScansNoOverlap scans of // either of the peak edges; if not, then mark the peak as invalid since it does not contain the // scan for the parent ion if (peakData.OriginalPeakLocationIndex < peak.LeftEdge) { if ( Math.Abs(scanNumbers[peakData.OriginalPeakLocationIndex] - scanNumbers[peak.LeftEdge]) > peakFinderOptions.MaxDistanceScansNoOverlap) { peak.IsValid = false; } } else if (peakData.OriginalPeakLocationIndex > peak.RightEdge) { if ( Math.Abs(scanNumbers[peakData.OriginalPeakLocationIndex] - scanNumbers[peak.RightEdge]) > peakFinderOptions.MaxDistanceScansNoOverlap) { peak.IsValid = false; } } } // Find the peak with the largest area that has peakData.PeakIsValid = True peakData.BestPeak = null; var bestPeakArea = double.MinValue; foreach (var peak in peakData.Peaks) { if (peak.IsValid) { if (peak.Area > bestPeakArea) { peakData.BestPeak = peak; bestPeakArea = peak.Area; } } } if (peakData.BestPeak != null) { validPeakFound = true; } else { validPeakFound = false; } return(validPeakFound); }
private void ExpandPeakRightEdge( PeakDataContainer peakData, udtSICPeakFinderOptionsType peakFinderOptions, clsPeak peak, float sngPeakMaximum, bool dataIsSmoothed) { var intStepOverIncreaseCount = 0; while (peak.RightEdge < peakData.DataCount - 1) { if (peakData.SmoothedYData[peak.RightEdge + 1] < peakData.SmoothedYData[peak.RightEdge]) { // The adjacent point is lower than the current point peak.RightEdge += 1; } else if (Math.Abs(peakData.SmoothedYData[peak.RightEdge + 1] - peakData.SmoothedYData[peak.RightEdge]) < double.Epsilon) { // The adjacent point is equal to the current point peak.RightEdge += 1; } else { // The next point to the right is not lower; what about the point after it? if (peak.RightEdge < peakData.DataCount - 2) { if (peakData.SmoothedYData[peak.RightEdge + 2] <= peakData.SmoothedYData[peak.RightEdge]) { // Only allow ignoring an upward spike if the delta from this point to the next is <= .MaxAllowedUpwardSpikeFractionMax of sngPeakMaximum if (peakData.SmoothedYData[peak.RightEdge + 1] - peakData.SmoothedYData[peak.RightEdge] > peakFinderOptions.MaxAllowedUpwardSpikeFractionMax * sngPeakMaximum) { break; } if (dataIsSmoothed) { // Only ignore an upward spike twice if the data is smoothed if (intStepOverIncreaseCount >= 2) { break; } } peak.RightEdge += 1; intStepOverIncreaseCount += 1; } else { break; } } else { break; } } } }
/// <summary> /// /// </summary> /// <param name="peakFinderOptions"></param> /// <param name="xyData"></param> /// <param name="originalPeakLocationIndex"> /// Data point index in the x values that should be a part of the peak /// Used for determining the best peak</param> /// <param name="smoothedYData">Smoothed Y values</param> /// <returns></returns> public List <clsPeak> FindPeaks( udtSICPeakFinderOptionsType peakFinderOptions, List <KeyValuePair <int, double> > xyData, int originalPeakLocationIndex, out List <double> smoothedYData) { if (xyData.Count == 0) { smoothedYData = new List <double>(); return(new List <clsPeak>()); } // Compute the potential peak area for this SIC var udtSICPotentialAreaStatsForPeak = FindMinimumPotentialPeakArea(xyData, peakFinderOptions); // Estimate the noise level var noiseAnalyzer = new NoiseLevelAnalyzer(); const bool ignoreNonPositiveData = false; var intensityData = new double[xyData.Count]; var scanNumbers = new int[xyData.Count]; for (var index = 0; index < xyData.Count; index++) { scanNumbers[index] = xyData[index].Key; intensityData[index] = xyData[index].Value; } noiseAnalyzer.ComputeTrimmedNoiseLevel(intensityData, 0, intensityData.Length - 1, peakFinderOptions.SICBaselineNoiseOptions, ignoreNonPositiveData, out var udtBaselineNoiseStats); // Find maximumPotentialPeakArea and dataPointCountAboveThreshold var maximumPotentialPeakArea = FindMaximumPotentialPeakArea(intensityData, peakFinderOptions, udtBaselineNoiseStats, out var dataPointCountAboveThreshold); if (maximumPotentialPeakArea < 1) { maximumPotentialPeakArea = 1; } var areaBasedSignalToNoise = maximumPotentialPeakArea / udtSICPotentialAreaStatsForPeak.MinimumPotentialPeakArea; if (areaBasedSignalToNoise < 1) { areaBasedSignalToNoise = 1; } var peakDetector = new PeakFinder(); var peakData = new PeakDataContainer(); peakData.SetData(xyData); if (Math.Abs(peakFinderOptions.ButterworthSamplingFrequency) < float.Epsilon) { peakFinderOptions.ButterworthSamplingFrequency = 0.25f; } peakData.PeakWidthPointsMinimum = (int)Math.Round(peakFinderOptions.InitialPeakWidthScansScaler * Math.Log10(Math.Floor(areaBasedSignalToNoise)) * 10); // Assure that .InitialPeakWidthScansMaximum is no greater than .InitialPeakWidthScansMaximum // and no greater than dataPointCountAboveThreshold/2 (rounded up) peakData.PeakWidthPointsMinimum = Math.Min(peakData.PeakWidthPointsMinimum, peakFinderOptions.InitialPeakWidthScansMaximum); peakData.PeakWidthPointsMinimum = Math.Min(peakData.PeakWidthPointsMinimum, (int)Math.Ceiling(dataPointCountAboveThreshold / 2.0)); if (peakData.PeakWidthPointsMinimum > peakData.DataCount * 0.8) { peakData.PeakWidthPointsMinimum = (int)Math.Floor(peakData.DataCount * 0.8); } if (peakData.PeakWidthPointsMinimum < MINIMUM_PEAK_WIDTH) { peakData.PeakWidthPointsMinimum = MINIMUM_PEAK_WIDTH; } peakData.OriginalPeakLocationIndex = originalPeakLocationIndex; var peakFoundContainingOriginalPeakLocation = FindPeaksWork( peakDetector, scanNumbers, peakData, peakFinderOptions); smoothedYData = peakData.SmoothedYData.ToList(); return(peakData.Peaks); }