LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
PeakFitterGaussian_tool.cc
Go to the documentation of this file.
1 
9 #include "larreco/RecoAlg/GausFitCache.h" // hit::GausFitCache
10 
13 #include "art_root_io/TFileService.h"
15 
16 #include <cassert>
17 #include <fstream>
18 
19 #include "TF1.h"
20 #include "TH1F.h"
21 
22 namespace reco_tool {
23 
28 
29  public:
31  BaselinedGausFitCache(std::string const& new_name = "BaselinedGausFitCache")
32  : hit::GausFitCache(new_name)
33  {}
34 
35  protected:
39  virtual TF1* CreateFunction(size_t nFunc) const
40  {
41  // add the Gaussians first
42  std::string formula;
43  std::size_t iGaus = 0;
44  while (iGaus < nFunc)
45  formula += "gaus(" + std::to_string(3 * (iGaus++)) + ") + ";
46  formula += "[" + std::to_string(3 * nFunc) + "]";
47 
48  std::string const func_name = FunctionName(nFunc);
49  auto* pF = new TF1(func_name.c_str(), formula.c_str());
50  pF->SetParName(iGaus * 3, "baseline");
51  return pF;
52  } // CreateFunction()
53 
54  }; // BaselinedGausFitCache
55 
57  public:
58  explicit PeakFitterGaussian(const fhicl::ParameterSet& pset);
59 
60  void findPeakParameters(const std::vector<float>&,
63  double&,
64  int&) const override;
65 
66  private:
67  // Member variables from the fhicl file
68  const double fMinWidth;
69  const double fMaxWidthMult;
70  const double fPeakRange;
71  const double fAmpRange;
72  const bool fFloatBaseline;
73  const bool fOutputHistograms;
74  const bool fRefit;
75  const double fRefitThreshold;
76  const double fRefitImprovement;
77 
79  TH1F* fROISizeHist;
88 
90 
91  mutable TH1F fHistogram;
92 
93  const geo::GeometryCore* fGeometry = lar::providerFrom<geo::Geometry>();
94 
95  void SetFitParameters(TF1& Gaus,
96  const ICandidateHitFinder::HitCandidateVec& hitCandidateVec,
97  const unsigned int nGaus,
98  const float baseline,
99  const float startTime,
100  const float roiSize) const;
101 
102  void GetFitParameters(const TF1& Gaus,
103  PeakParamsVec& peakParamsVec,
104  const unsigned int nGaus,
105  const float startTime,
106  double& chi2PerNDF,
107  int& NDF) const;
108 
109  ICandidateHitFinder::HitCandidate FindRefitCand(
110  const TF1& fittedGaus,
111  const std::vector<float>& waveform,
112  const int startTime,
113  const int roiSize,
114  const ICandidateHitFinder::HitCandidateVec& hitCandidateVec,
115  const PeakParamsVec& fittedPeakVec) const;
116 
117  std::pair<ICandidateHitFinder::HitCandidate, PeakFitParams_t> FindShiftedGaussian(
118  const ICandidateHitFinder::HitCandidateVec& hitCandidateVec,
119  const PeakParamsVec& fittedPeakVec) const;
120  };
121 
122  //----------------------------------------------------------------------
123  // Constructor.
125  : fMinWidth(pset.get<double>("MinWidth", 0.5))
126  , fMaxWidthMult(pset.get<double>("MaxWidthMult", 3.))
127  , fPeakRange(pset.get<double>("PeakRangeFact", 2.))
128  , fAmpRange(pset.get<double>("PeakAmpRange", 2.))
129  , fFloatBaseline(pset.get<bool>("FloatBaseline", false))
130  , fOutputHistograms(pset.get<bool>("OutputHistograms", false))
131  , fRefit(pset.get<bool>("Refit"))
132  , fRefitThreshold(pset.get<double>("RefitThreshold"))
133  , fRefitImprovement(pset.get<double>("RefitImprovement"))
134  {
135  fHistogram = TH1F("PeakFitterHitSignal", "", 500, 0., 500.);
136 
137  fHistogram.Sumw2();
138 
139  std::string function = "Gaus(0)";
140 
141  // If asked, define the global histograms
142  if (fOutputHistograms) {
143  // Access ART's TFileService, which will handle creating and writing
144  // histograms and n-tuples for us.
146 
147  // Make a directory for these histograms
148  art::TFileDirectory dir = tfs->mkdir("PeakFit");
149 
150  fNumCandHitsHist = dir.make<TH1F>("NumCandHits", "# Candidate Hits", 100, 0., 100.);
151  fROISizeHist = dir.make<TH1F>("ROISize", "ROI Size", 400, 0., 400.);
152  fCandPeakPositionHist = dir.make<TH1F>("CPeakPosition", "Peak Position", 200, 0., 400.);
153  fCandPeakWidHist = dir.make<TH1F>("CPeadWidth", "Peak Width", 100, 0., 25.);
154  fCandPeakAmpitudeHist = dir.make<TH1F>("CPeakAmplitude", "Peak Amplitude", 100, 0., 200.);
155  fCandBaselineHist = dir.make<TH1F>("CBaseline", "Baseline", 200, -25., 25.);
156  fFitPeakPositionHist = dir.make<TH1F>("FPeakPosition", "Peak Position", 200, 0., 400.);
157  fFitPeakWidHist = dir.make<TH1F>("FPeadWidth", "Peak Width", 100, 0., 25.);
158  fFitPeakAmpitudeHist = dir.make<TH1F>("FPeakAmplitude", "Peak Amplitude", 100, 0., 200.);
159  fFitBaselineHist = dir.make<TH1F>("FBaseline", "Baseline", 200, -25., 25.);
160  }
161 
162  return;
163  }
164 
165  // --------------------------------------------------------------------------------------------
167  const std::vector<float>& roiSignalVec,
168  const ICandidateHitFinder::HitCandidateVec& hitCandidateVec,
169  PeakParamsVec& peakParamsVec,
170  double& chi2PerNDF,
171  int& NDF) const
172  {
173  // The following is a translation of the original FitGaussians function in the original
174  // GausHitFinder module originally authored by Jonathan Asaadi
175  //
176  // *** NOTE: this algorithm assumes the reference time for input hit candidates is to
177  // the first tick of the input waveform (ie 0)
178  //
179  if (hitCandidateVec.empty()) return;
180 
181  // in case of a fit failure, set the chi-square to infinity
182  chi2PerNDF = std::numeric_limits<double>::infinity();
183 
184  int startTime = hitCandidateVec.front().startTick;
185  int endTime = hitCandidateVec.back().stopTick;
186  int roiSize = endTime - startTime;
187 
188  // Check to see if we need a bigger histogram for fitting
189  if (roiSize > fHistogram.GetNbinsX()) {
190  std::string histName = "PeakFitterHitSignal_" + std::to_string(roiSize);
191  fHistogram = TH1F(histName.c_str(), "", roiSize, 0., roiSize);
192  fHistogram.Sumw2();
193  }
194 
195  for (int idx = 0; idx < roiSize; idx++)
196  fHistogram.SetBinContent(idx + 1, roiSignalVec[startTime + idx]);
197 
198  // Build the string to describe the fit formula
199  unsigned int const nGaus = hitCandidateVec.size();
200  assert(fFitCache.Get(nGaus));
201  TF1 Gaus = *(fFitCache.Get(nGaus));
202 
203  // Set the baseline if so desired
204  const float baseline(fFloatBaseline ? roiSignalVec[startTime] : 0.f);
205 
206  if (fOutputHistograms) {
207  fNumCandHitsHist->Fill(hitCandidateVec.size(), 1.);
208  fROISizeHist->Fill(roiSize, 1.);
209  fCandBaselineHist->Fill(baseline, 1.);
210  }
211 
212  SetFitParameters(Gaus, hitCandidateVec, nGaus, baseline, startTime, roiSize);
213 
214  int fitResult{-1};
215 
216  try {
217  fitResult = fHistogram.Fit(&Gaus, "QNWB", "", 0., roiSize);
218  }
219  catch (...) {
220  mf::LogWarning("GausHitFinder") << "Fitter failed finding a hit";
221  }
222 
223  // If the fit result is not zero there was an error
224  if (!fitResult) {
225  GetFitParameters(Gaus, peakParamsVec, nGaus, startTime, chi2PerNDF, NDF);
226 
227  // TODO: The refit functionality should be split out into a dedicated tool that will take in
228  // the fit previous fit result, find the new parameters for the refit, and call a
229  // fitting tool to perform the refit. This would enable the use with any fitting tool
230  // rather than being bound within this tool.
231  if (fRefit && chi2PerNDF > fRefitThreshold &&
232  chi2PerNDF < std::numeric_limits<double>::infinity()) {
233 
234  const ICandidateHitFinder::HitCandidate refitParams =
235  FindRefitCand(Gaus, roiSignalVec, startTime, roiSize, hitCandidateVec, peakParamsVec);
236 
237  if (refitParams.hitHeight > 0) {
238 
239  ICandidateHitFinder::HitCandidateVec newHitCandidateVec = hitCandidateVec;
240  newHitCandidateVec.push_back(refitParams);
241 
242  // Build the string to describe the fit formula
243  unsigned int const nGausNew = nGaus + 1;
244  assert(fFitCache.Get(nGausNew));
245  Gaus = *(fFitCache.Get(nGausNew));
246 
247  SetFitParameters(Gaus, newHitCandidateVec, nGausNew, baseline, startTime, roiSize);
248 
249  int newFitResult{-1};
250  try {
251  newFitResult = fHistogram.Fit(&Gaus, "QNWB", "", 0., roiSize);
252  }
253  catch (...) {
254  mf::LogWarning("GausHitFinder") << "Fitter failed finding a hit";
255  }
256 
257  // If the fit result is not zero there was an error
258  if (!newFitResult) {
259  const float newChi2PerNDF = Gaus.GetChisquare() / Gaus.GetNDF();
260  if (newChi2PerNDF * fRefitImprovement < chi2PerNDF || newChi2PerNDF < fRefitThreshold) {
261  peakParamsVec.clear();
262  GetFitParameters(Gaus, peakParamsVec, nGausNew, startTime, chi2PerNDF, NDF);
263  }
264  }
265  }
266  }
267 
268  if (fOutputHistograms) fFitBaselineHist->Fill(Gaus.GetParameter(3 * nGaus), 1.);
269  }
270  return;
271  }
272 
274  TF1& Gaus,
275  const ICandidateHitFinder::HitCandidateVec& hitCandidateVec,
276  const unsigned int nGaus,
277  const float baseline,
278  const float startTime,
279  const float roiSize) const
280  {
281  if (fFloatBaseline) {
282  Gaus.SetParameter(nGaus * 3, baseline);
283  Gaus.SetParLimits(nGaus * 3, baseline - 12., baseline + 12.);
284  }
285  else
286  Gaus.FixParameter(nGaus * 3, baseline); // last parameter is the baseline
287 
288  int parIdx{0};
289  for (auto const& candidateHit : hitCandidateVec) {
290  double const peakMean = candidateHit.hitCenter - startTime;
291  double const peakWidth = candidateHit.hitSigma;
292  double const amplitude = candidateHit.hitHeight - baseline;
293  double const meanLowLim = std::max(peakMean - fPeakRange * peakWidth, 0.);
294  double const meanHiLim = std::min(peakMean + fPeakRange * peakWidth, double(roiSize));
295 
296  if (fOutputHistograms) {
297  fCandPeakPositionHist->Fill(peakMean, 1.);
298  fCandPeakWidHist->Fill(peakWidth, 1.);
299  fCandPeakAmpitudeHist->Fill(amplitude, 1.);
300  }
301 
302  Gaus.SetParameter(parIdx, amplitude);
303  Gaus.SetParameter(1 + parIdx, peakMean);
304  Gaus.SetParameter(2 + parIdx, peakWidth);
305  Gaus.SetParLimits(parIdx, 0.1 * amplitude, fAmpRange * amplitude);
306  Gaus.SetParLimits(1 + parIdx, meanLowLim, meanHiLim);
307  Gaus.SetParLimits(
308  2 + parIdx, std::max(fMinWidth, 0.1 * peakWidth), fMaxWidthMult * peakWidth);
309 
310  parIdx += 3;
311  }
312  }
313 
315  PeakParamsVec& peakParamsVec,
316  const unsigned int nGaus,
317  const float startTime,
318  double& chi2PerNDF,
319  int& NDF) const
320  {
321  chi2PerNDF = (Gaus.GetChisquare() / Gaus.GetNDF());
322  NDF = Gaus.GetNDF();
323 
324  int parIdx{0};
325  for (size_t idx = 0; idx < nGaus; idx++) {
326  PeakFitParams_t peakParams;
327 
328  peakParams.peakAmplitude = Gaus.GetParameter(parIdx);
329  peakParams.peakAmplitudeError = Gaus.GetParError(parIdx);
330  peakParams.peakCenter = Gaus.GetParameter(parIdx + 1) + startTime;
331  peakParams.peakCenterError = Gaus.GetParError(parIdx + 1);
332  peakParams.peakSigma = Gaus.GetParameter(parIdx + 2);
333  peakParams.peakSigmaError = Gaus.GetParError(parIdx + 2);
334 
335  if (fOutputHistograms) {
336  fFitPeakPositionHist->Fill(peakParams.peakCenter, 1.);
337  fFitPeakWidHist->Fill(peakParams.peakSigma, 1.);
338  fFitPeakAmpitudeHist->Fill(peakParams.peakAmplitude, 1.);
339  }
340 
341  peakParamsVec.emplace_back(peakParams);
342 
343  parIdx += 3;
344  }
345  }
346 
348  const TF1& fittedGaus,
349  const std::vector<float>& waveform,
350  const int startTime,
351  const int roiSize,
352  const ICandidateHitFinder::HitCandidateVec& hitCandidateVec,
353  const PeakParamsVec& fittedPeakVec) const
354  {
355 
356  // Find the candidate that was shifted most by the fit
357  std::pair<ICandidateHitFinder::HitCandidate, PeakFitParams_t> shiftedHitCanPair =
358  FindShiftedGaussian(hitCandidateVec, fittedPeakVec);
359 
360  // If the candidate was shifted more than some given amount, place a new candidate on the other side
361  float offset(shiftedHitCanPair.second.peakCenter - shiftedHitCanPair.first.hitCenter);
362 
363  // If we have too many Gaussians it is just a mess
364  if (std::abs(offset) > 1.f && hitCandidateVec.size() == 1) {
365 
366  offset = std::min(offset, roiSize / 8.f);
367 
368  const int candPos(
369  offset > 0 ?
370  std::min(shiftedHitCanPair.first.hitCenter + 4.f * offset,
371  (shiftedHitCanPair.first.hitCenter + shiftedHitCanPair.first.stopTick) / 2.f) :
372  std::max(shiftedHitCanPair.first.hitCenter + 4.f * offset,
373  (shiftedHitCanPair.first.hitCenter + shiftedHitCanPair.first.startTick) / 2.f));
374 
376  0,
377  0,
378  0,
379  0,
380  0,
381  float(candPos + startTime),
382  3.f * std::abs(offset),
383  0.5f * waveform[candPos]};
384  }
385 
386  // If we are not trying a peak that was shifted, find the largest diff between fitted and input
387  int maxDiffPos(std::numeric_limits<int>::max());
388  float maxDiff(0);
389  for (int i = 0; i < roiSize; i++) {
390  float diff(waveform[startTime + i] - fittedGaus.Eval(i));
391 
392  // Prefer excesses over deficits
393  if (diff > 0) diff *= 1.25;
394 
395  diff *= diff;
396 
397  // We want to avoid adding new Gaussians in the tails
398  float peakDist(std::numeric_limits<float>::max());
399  for (auto const& hitCandidate : hitCandidateVec)
400  peakDist = std::min(peakDist, std::abs(hitCandidate.hitCenter - float(startTime) - i));
401 
402  // Or too close to a peak
403  if (peakDist < 3) continue;
404 
405  diff *= std::log(peakDist);
406 
407  if (std::abs(diff) > std::abs(maxDiff)) {
408  maxDiff = diff;
409  maxDiffPos = i;
410  }
411  }
412 
413  if (maxDiffPos == std::numeric_limits<int>::max())
415  0,
416  0,
417  0,
418  0,
419  0,
420  std::numeric_limits<float>::lowest(),
421  std::numeric_limits<float>::lowest(),
422  std::numeric_limits<float>::lowest()};
423 
424  // Recover the actual diff
425  maxDiff = (waveform[startTime + maxDiffPos] - fittedGaus.Eval(maxDiffPos));
426 
427  int lowLim(maxDiffPos);
428  int highLim(maxDiffPos);
429  const bool useMax(maxDiff > 0);
430 
431  // Find the point where the excess crosses the fitted Gaussian
432  if (useMax) {
433  while (lowLim > 0 && waveform[startTime + lowLim] > fittedGaus.Eval(lowLim))
434  lowLim--;
435 
436  while (highLim < roiSize && waveform[startTime + highLim] > fittedGaus.Eval(highLim))
437  highLim++;
438  }
439  else {
440  while (lowLim > 0 && waveform[startTime + lowLim] < fittedGaus.Eval(lowLim))
441  lowLim--;
442 
443  while (highLim < roiSize && waveform[startTime + highLim] < fittedGaus.Eval(highLim))
444  highLim++;
445  }
446 
447  const float amplitude(std::max(std::abs(2 * maxDiff), waveform[startTime + maxDiffPos] / 2.f));
448 
450  0, 0, 0, 0, 0, 0, float(maxDiffPos + startTime), 0.5f * (highLim - lowLim), amplitude};
451  }
452 
453  std::pair<ICandidateHitFinder::HitCandidate, IPeakFitter::PeakFitParams_t>
455  const ICandidateHitFinder::HitCandidateVec& hitCandidateVec,
456  const PeakParamsVec& fittedPeakVec) const
457  {
458  float minDiff(std::numeric_limits<float>::max());
459  ICandidateHitFinder::HitCandidate const* minHit;
460  PeakFitParams_t const* minPeak{nullptr};
461 
462  for (auto const& hitCand : hitCandidateVec) {
463  for (auto const& fittedPeak : fittedPeakVec) {
464  const float offset(hitCand.hitCenter - fittedPeak.peakCenter);
465  if (std::abs(offset) < minDiff) {
466  minDiff = offset;
467  minHit = &hitCand;
468  minPeak = &fittedPeak;
469  }
470  }
471  }
472 
473  assert(minPeak);
474  return std::pair<ICandidateHitFinder::HitCandidate, PeakFitParams_t>(*minHit, *minPeak);
475  }
476 
478 }
#define DEFINE_ART_CLASS_TOOL(tool)
Definition: ToolMacros.h:42
Utilities related to art service access.
const double fPeakRange
set range limits for peak center
const double fMaxWidthMult
multiplier for max width for gaussian fit
const bool fOutputHistograms
If true will generate summary style histograms.
constexpr auto abs(T v)
Returns the absolute value of the argument.
const bool fRefit
If true will attempt to refit with an extra Gaussian.
const double fRefitThreshold
Reduced Chi2 threshold above which to refit.
PeakFitterGaussian(const fhicl::ParameterSet &pset)
std::pair< ICandidateHitFinder::HitCandidate, PeakFitParams_t > FindShiftedGaussian(const ICandidateHitFinder::HitCandidateVec &hitCandidateVec, const PeakParamsVec &fittedPeakVec) const
TFile f
Definition: plotHisto.C:6
A set of TF1 linear sum of base functions (Gaussians)
Definition: GausFitCache.h:45
const double fAmpRange
set range limit for peak amplitude
BaselinedGausFitCache fFitCache
Preallocated ROOT functions for the fits.
ICandidateHitFinder::HitCandidate FindRefitCand(const TF1 &fittedGaus, const std::vector< float > &waveform, const int startTime, const int roiSize, const ICandidateHitFinder::HitCandidateVec &hitCandidateVec, const PeakParamsVec &fittedPeakVec) const
void SetFitParameters(TF1 &Gaus, const ICandidateHitFinder::HitCandidateVec &hitCandidateVec, const unsigned int nGaus, const float baseline, const float startTime, const float roiSize) const
decltype(auto) constexpr to_string(T &&obj)
ADL-aware version of std::to_string.
const double fRefitImprovement
Factor by which the refit must improve the chi2.
BaselinedGausFitCache(std::string const &new_name="BaselinedGausFitCache")
Constructor (see base class constructor).
virtual TF1 * Get(size_t nFunc)
Returns a function sum of nFunc base functions.
Description of geometry of one entire detector.
Definition: GeometryCore.h:119
GausFitCache(std::string new_name="GausFitCache")
Constructor; optionally set the name of the repository.
Definition: GausFitCache.h:48
Detector simulation of raw signals on wires.
virtual TF1 * CreateFunction(size_t nFunc) const
std::vector< PeakFitParams_t > PeakParamsVec
Definition: IPeakFitter.h:34
const bool fFloatBaseline
Allow baseline to "float" away from zero.
decltype(auto) get(T &&obj)
ADL-aware version of std::to_string.
Definition: StdUtils.h:120
void GetFitParameters(const TF1 &Gaus, PeakParamsVec &peakParamsVec, const unsigned int nGaus, const float startTime, double &chi2PerNDF, int &NDF) const
This provides an interface for tools which are tasked with fitting peaks on input waveforms...
TDirectory * dir
Definition: macro.C:5
virtual std::string FunctionName(size_t nFunc) const
Returns a name for the function with nFunc base functions.
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
Provide caches for TF1 functions to be used with ROOT fitters.
void findPeakParameters(const std::vector< float > &, const ICandidateHitFinder::HitCandidateVec &, PeakParamsVec &, double &, int &) const override
art framework interface to geometry description
const double fMinWidth
minimum initial width for gaussian fit
std::vector< HitCandidate > HitCandidateVec