/// <summary> /// Find inflection point by finding the nearest maximum and fit parabola to it. /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="c"></param> /// <returns></returns> public static double ZeroCrossingLeftInflection(double[] x, double[] y, CrudeZeroCrossing c) { double[] xs = NP.Slice(x, c.FirstTrigger, c.LastTrigger); double[] ys = NP.Slice(y, c.FirstTrigger, c.LastTrigger); int argmax = NP.Argmax(ys); NP.PrintArray(xs); NP.PrintArray(ys); NP.ParabolicFitResult result = NP.ParabolicFit(xs, ys); return (-result.b / (2.0 * result.a)); }
/// <summary> /// Find left inflection with out fitting parabola /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="c"></param> /// <returns></returns> public static double FastZeroCrossingLeftInflection(double[] x, double[] y, CrudeZeroCrossing c) { //this assumes that ys is smooth double[] xs = NP.Slice(x, c.FirstTrigger, c.LastTrigger); double[] ys = NP.Slice(y, c.FirstTrigger, c.LastTrigger); int argmax = NP.Argmax(ys); Console.WriteLine("Argmax {0}", argmax); return xs[argmax]; }
//public static ZeroCrossingSlopeResult CrossingSlope(double[] x, double[] y, CrudeZeroCrossing c, int width = 30) //{ // double[] xs = NP.Slice(x, c.Crossing - width, c.Crossing + width); // double[] ys = NP.Slice(y, c.Crossing - width, c.Crossing + width); // NP.LinearFitResult result = NP.LinearFit(xs, ys); // double bw = (x[c.Crossing + width] - x[c.Crossing - width]) / (2 * width); // ZeroCrossingSlopeResult ret = new ZeroCrossingSlopeResult(result.m, result.c, bw); // return ret; //} /// <summary> /// Perform linear fit to x and y around c.Crossing+-width /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="c"></param> /// <param name="width"></param> /// <returns></returns> public static ZeroCrossingSlopeResult CrossingSlope(double[] x, double[] y, CrudeZeroCrossing c, int width = 10) { //we do want the right hand side to be inclusive double[] xs = NP.Slice(x, c.Crossing - width, c.Crossing + width + 1); double[] ys = NP.Slice(y, c.Crossing - width, c.Crossing + width + 1); NP.LinearFitResult linr = NP.LinearFit(xs, ys); //parabolic fit doesn't help since it's symmetric //NP.ParabolicFitResult parar = NP.ParabolicFit(xs, ys); //improve slope with intercept double cc = linr.c; double xintercept = -linr.c / linr.m; double mm = linr.m; double bw = (xs[xs.Length - 1] - xs[0]) / (xs.Length - 1); //double mm = 2 * parar.a * xintercept + parar.b; //improved slope ZeroCrossingSlopeResult ret = new ZeroCrossingSlopeResult(mm, cc, xintercept, bw); return ret; }
/// <summary> /// Find all zero crossing of s with given trigger. The zero crossing is determined /// by detecting the plot rise above trigger and falls below zero. /// </summary> /// <param name="s"></param> /// <param name="trigger"></param> /// <returns></returns> public static CrudeZeroCrossing[] ZeroCrossings(double[] s, double trigger) { bool triggered = false; int tmpFirstTrigger = 0; int tmpLastTrigger = 0; int tmpZeroCrossing = 0; List<CrudeZeroCrossing> l = new List<CrudeZeroCrossing>(); for (int i = 0; i < s.Length; i++) { double v = s[i]; if (!triggered) { if (v > trigger) { triggered = true; tmpFirstTrigger = i; tmpLastTrigger = i; } } else //in triggered mode { if (v < 0) { tmpZeroCrossing = i; triggered = false; CrudeZeroCrossing c = new CrudeZeroCrossing(i, tmpFirstTrigger, tmpLastTrigger); l.Add(c); } else { if (v > trigger) { tmpLastTrigger = i; } } } } return l.ToArray(); }
/// <summary> /// Construct Initial PeakGuess from CrudeZeroCrossing. Find mu from zero crossing position, sigma from inflection point /// nbkg, nsig is from zero crossing slope. The bound is mu+-n*sigma. /// </summary> /// <param name="cross"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="window">smoothing parameter. indicate the size of piecewise</param> /// <param name="nsigma">window size</param> /// <param name="width">zero crossing fit width</param> /// <returns></returns> public static InitialPeakGuess OfCrudeZeroCrossing(CrudeZeroCrossing cross, double[] x, double[] y, int window = 30, double nsigma = 2.0, int width=30 ) { InitialPeakGuess ret = new InitialPeakGuess(); double[] rawg = NP.Gradient(x, y); //this is a raw gradient double[] g = NP.GlobalParabolicSmooth(y, window); return InitialPeakGuess.OfCrudeZeroCrossingAndGradient(cross, x, y, g, width, nsigma); }
/// <summary> /// Construct initial peak guess from crude zerocrossing. /// </summary> /// <param name="cross"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="sg">Smooth gradient</param> /// <param name="width"></param> /// <param name="nsigma"></param> /// <returns></returns> public static InitialPeakGuess OfCrudeZeroCrossingAndGradient(CrudeZeroCrossing cross, double[] x, double[] y, double[] sg, int width=10, double nsigma = 2.0) { InitialPeakGuess ret = new InitialPeakGuess(); double[] g = sg; //NP.PrintArray(g); ZeroCrossingSlopeResult zerocrossing = PeakLocator.CrossingSlope(x, g, cross, width); ret.mu = zerocrossing.XIntercept; double inflection = PeakLocator.FastZeroCrossingLeftInflection(x, g, cross); Console.WriteLine("Inflection {0}", inflection); double sigma = ret.mu - inflection; ret.sigma = sigma; double lowerbound = Math.Max(0.0, zerocrossing.XIntercept - nsigma * sigma); double upperbound = Math.Min(zerocrossing.XIntercept + nsigma * sigma, x.Last()); ret.lowerboundIndex = Math.Max(0, NP.LowerBound(x, lowerbound)); ret.upperboundIndex = Math.Min(x.Length - 1, NP.UpperBound(x, upperbound)); ret.lowerbound = x[ret.lowerboundIndex]; ret.upperbound = x[ret.upperboundIndex]; double[] xSlice = NP.Slice(x, ret.lowerboundIndex, ret.upperboundIndex + 1);//remember slice is right exclusive double[] ySlice = NP.Slice(y, ret.lowerboundIndex, ret.upperboundIndex + 1); lowerbound = ret.lowerbound; upperbound = ret.upperbound; Console.WriteLine("slope {0}, bw {1}", zerocrossing.Slope, zerocrossing.bw); double ntotalpeak = -zerocrossing.Slope / zerocrossing.bw * Math.Pow(sigma, 3) * Math.Sqrt(2 * Math.PI); double leftnsigma = (zerocrossing.XIntercept - lowerbound) / sigma; double rightnsigma = (upperbound - zerocrossing.XIntercept) / sigma; double npeakfrac = 0.5 * (SpecialFunctions.Erf(leftnsigma / Math.Sqrt(2)) + SpecialFunctions.Erf(rightnsigma / Math.Sqrt(2))); Console.WriteLine("npeakfrac {0}", npeakfrac); Console.WriteLine("ntotalpeak {0}", ntotalpeak); ret.nsig = ntotalpeak * npeakfrac; F3 gaussian = (xx, xmu, xsigma) => 1 / (xsigma * Math.Sqrt(2 * Math.PI)) * Math.Exp(-(xx - xmu) * (xx - xmu) / (2 * xsigma * xsigma)); NP.Func1 rgauss = (xx) => ntotalpeak * zerocrossing.bw * gaussian(xx, ret.mu, ret.sigma); double[] peakamount = NP.Broadcast(rgauss, xSlice); double[] bkgamount = NP.Broadcast((xx, yy) => xx - yy, ySlice, peakamount); //NP.PrintArray(bkgamount); NP.LinearFitResult bkgshape = NP.LinearFit(xSlice, bkgamount); double m = bkgshape.m; double c = bkgshape.c; double bw = zerocrossing.bw; Console.WriteLine("bkg.m {0}, bkg.c {1}", m, c); double nbkg = 1 / bw * (m / 2.0 * (upperbound * upperbound - lowerbound * lowerbound) + c * (upperbound - lowerbound)); ret.m = m; ret.c = c; ret.nbkg = nbkg; return ret; }
public PeakFinder() { Crossings = new CrudeZeroCrossing[0]; Guesses = new InitialPeakGuess[0]; x = new double[0]; y = new double[0]; Edges = new double[0]; Gradient = new double[0]; SmoothGradient = new double[0]; ScaledGradient = new double[0]; TriggerValue = 0.5; }