All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
ROIFinderDifferential_tool.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 /// \file ROIFinderDifferential.cc
3 /// \author T. Usher
4 ////////////////////////////////////////////////////////////////////////
5 
6 #include <cmath>
8 #include "art/Utilities/ToolMacros.h"
9 #include "art/Utilities/make_tool.h"
10 #include "art_root_io/TFileService.h"
11 #include "messagefacility/MessageLogger/MessageLogger.h"
12 #include "cetlib_except/exception.h"
15 #include "larcore/CoreUtils/ServiceUtil.h" // lar::providerFrom()
16 #include "icarus_signal_processing/WaveformTools.h"
17 
18 #include "TH1F.h"
19 #include "TH2F.h"
20 #include "TProfile.h"
21 
22 #include <fstream>
23 
24 namespace icarus_tool
25 {
26 
28 {
29 public:
30  explicit ROIFinderDifferential(const fhicl::ParameterSet& pset);
31 
33 
34  void configure(const fhicl::ParameterSet& pset) override;
35  void initializeHistograms(art::TFileDirectory&) const override;
36  size_t plane() const override {return fPlane;}
37 
38  void FindROIs(const Waveform&, size_t, size_t, double, CandidateROIVec&) const override;
39 
40 private:
41  // The actual ROI finding algorithm
42  void findROICandidates(Waveform::const_iterator startItr,
43  Waveform::const_iterator stopItr,
44  size_t channel,
45  size_t roiStartTick,
46  float roiThreshold,
47  CandidateROIVec& roiCandidateVec) const;
48  // Average/Smooth the input waveform
49  void averageInputWaveform(const Waveform&, Waveform&) const;
50  void smoothInputWaveform(const Waveform&, Waveform&) const;
51 
52  // Member variables from the fhicl file
53  size_t fPlane;
54  float fNumSigma; ///< "# sigma" rms noise for ROI threshold
55  int fNumBinsToAve; ///< Controls the averaging
56  int fMax2MinDistance; ///< Maxmimum allow peak to peak distance
57  size_t fMaxTicksGap; ///< Maximum gap between ROI's before merging
58  unsigned short fPreROIPad; ///< ROI padding
59  unsigned short fPostROIPad; ///< ROI padding
60  float fTruncRMSMinFraction; ///< or at least this fraction of time bins
61  bool fOutputHistograms; ///< Output histograms?
62 
63  std::vector<float> fAveWeightVec;
64  float fWeightSum;
65 
66  art::TFileDirectory* fHistDirectory;
67 
68  // Define some useful global histograms here
70  TH1F* fDiffRmsHist;
71 
76 
77  icarus_signal_processing::WaveformTools<float> fWaveformTool;
78 
79  // Services
80  const geo::GeometryCore* fGeometry = lar::providerFrom<geo::Geometry>();
81 };
82 
83 //----------------------------------------------------------------------
84 // Constructor.
85 ROIFinderDifferential::ROIFinderDifferential(const fhicl::ParameterSet& pset)
86 {
87  configure(pset);
88 }
89 
91 {
92 }
93 
94 void ROIFinderDifferential::configure(const fhicl::ParameterSet& pset)
95 {
96  // Start by recovering the parameters
97  std::vector<unsigned short> zin;
98 
99  fPlane = pset.get< size_t > ("Plane" );
100  fNumSigma = pset.get< float > ("NumSigma" );
101  fNumBinsToAve = pset.get< int > ("NumBinsToAve" );
102  fMax2MinDistance = pset.get< int > ("Max2MinDistance" );
103  fMaxTicksGap = pset.get< size_t > ("MaxTicksGap", 50);
104  zin = pset.get< std::vector<unsigned short> >("ROILeadTrailPadding" );
105  fTruncRMSMinFraction = pset.get< float > ("TruncRMSMinFraction", 0.6);
106  fOutputHistograms = pset.get< bool > ("OutputHistograms", false);
107 
108  // put the ROI pad sizes into more convenient vectors
109  fPreROIPad = zin[0];
110  fPostROIPad = zin[1];
111 
112  // The "Max2MinDistance" is input in ticks but needs to be scaled if we are averaging
113 // if (fNumBinsToAve > 1) fMax2MinDistance = std::round(fMax2MinDistance / fNumBinsToAve) + 1;
114 
115  // If asked, define some histograms
116  if (fOutputHistograms)
117  {
118  // Access ART's TFileService, which will handle creating and writing
119  // histograms and n-tuples for us.
120  art::ServiceHandle<art::TFileService> tfs;
121 
122  fHistDirectory = tfs.get();
123 
124  // Make a directory for these histograms
125  art::TFileDirectory dir = fHistDirectory->mkdir(Form("ROIPlane_%1zu",fPlane));
126 
127  fDiffMeanHist = dir.make<TH1F>("DiffMean", ";Diff Mean;", 100, -20., 20.);
128  fDiffRmsHist = dir.make<TH1F>("DiffRms", ";Diff RMS;", 100, 0., 5.);
129 
130  fMinMaxDPeakHist = dir.make<TH1F>("MinMaxDPeak", ";Delta Peak;", 200, 0., 50.);
131  fMinMaxDistHist = dir.make<TH1F>("MinMaxDist", ";Peak Sep;", 50, 0., 50.);
132  fMinMaxSigmaHist = dir.make<TH1F>("MinMaxSigma", ";Peak Sep/rms;", 200, 0., 200.);
133  fDPeakVDistHist = dir.make<TH2F>("DPeakVDist", ";Delta Peak; Peak Sep", 200, 0, 50., 50, 0., 50.);
134  }
135 
136  // precalculate the weight vector to use in the averaging
137  fAveWeightVec.resize(fNumBinsToAve);
138 
139  if (fNumBinsToAve > 1)
140  {
141  for(int idx = 0; idx < fNumBinsToAve/2; idx++)
142  {
143  float weight = idx + 1;
144 
145  fAveWeightVec.at(idx) = weight;
146  fAveWeightVec.at(fNumBinsToAve - idx - 1) = weight;
147  }
148 
149  // Watch for case of fNumBinsToAve being odd
150  if (fNumBinsToAve % 2 > 0) fAveWeightVec.at(fNumBinsToAve/2) = fNumBinsToAve/2 + 1;
151  }
152  else fAveWeightVec.at(0) = 1.;
153 
154  fWeightSum = std::accumulate(fAveWeightVec.begin(),fAveWeightVec.end(), 0.);
155 
156  return;
157 }
158 
159 void ROIFinderDifferential::FindROIs(const Waveform& waveform, size_t channel, size_t cnt, double rmsNoise, CandidateROIVec& roiVec) const
160 {
161  // The idea here is to consider the input waveform - if an induction plane then it is already in differential form,
162  // if a collection plane then we form the differential - then smooth and look for peaks. The technique will be to
163  // consider the peak signature as a maximum followed some bins later by a mininum and those whose difference between
164  // max and min is more than the threshold are kept.
165 
166  // First up, determine what kind of wire we have
167  std::vector<geo::WireID> wids = fGeometry->ChannelToWire(channel);
168  const geo::PlaneID& planeID = wids[0].planeID();
169  geo::SigType_t sigType = fGeometry->SignalType(planeID);
170 
171  // Local copy of the input waveform
172  Waveform waveformDeriv(waveform.size());
173 
174  // If we have a collection plane then take the derivative
175  if (sigType == geo::kCollection) fWaveformTool.firstDerivative(waveform, waveformDeriv);
176 
177  // Otherwise a straight copy since the bipolar pulses are, effectively, derivatives
178  else std::copy(waveform.begin(),waveform.end(),waveformDeriv.begin());
179 
180  // Do the averaging
181  Waveform aveWaveformDeriv;
182 
183 // averageInputWaveform(waveformDeriv, aveWaveformDeriv);
184  smoothInputWaveform(waveformDeriv, aveWaveformDeriv);
185 // fWaveformTool->triangleSmooth(waveform,aveWaveformDeriv);
186 
187  // Scheme for finding a suitable threshold
188  float truncMean(0.);
189  float truncRMS(0.);
190  float fullRMS(0.);
191  float nSig(2.5);
192  int nTrunc(0);
193  int range(0);
194 
195  fWaveformTool.getTruncatedMean(aveWaveformDeriv, truncMean, nTrunc, range);
196  fWaveformTool.getTruncatedRMS(aveWaveformDeriv, nSig, fullRMS, truncRMS, nTrunc);
197 
198  // Put a floor on the value of the truncated RMS...
199  float truncRMSFloor = std::max(truncRMS, float(0.25));
200 
201  // Now find the ROI's
202  findROICandidates(aveWaveformDeriv.begin(),aveWaveformDeriv.end(),channel,0,truncRMSFloor,roiVec);
203 
204  TProfile* roiHist(0);
205 
206  if (fOutputHistograms)
207  {
208  fDiffMeanHist->Fill(truncMean, 1.);
209  fDiffRmsHist->Fill(truncRMS, 1.);
210 
211  // Try to limit to the wire number (since we are already segregated by plane)
212  std::vector<geo::WireID> wids = fGeometry->ChannelToWire(channel);
213  size_t wire = wids[0].Wire;
214 
215  // Make a directory for these histograms
216  art::TFileDirectory dir = fHistDirectory->mkdir(Form("ROIPlane_%1zu/%03zu/wire_%05zu",fPlane,cnt,wire));
217 
218  // We keep track of four histograms:
219  try
220  {
221  TProfile* waveformHist = dir.make<TProfile>(Form("Wfm_%03zu_c%05zu",cnt,wire), "Waveform", waveform.size(), 0, waveform.size(), -500., 500.);
222  TProfile* derivativeHist = dir.make<TProfile>(Form("Der_%03zu_c%05zu",cnt,wire), "Derivative", waveformDeriv.size(), 0, waveformDeriv.size(), -500., 500.);
223  TProfile* aveDerivHist = dir.make<TProfile>(Form("Ave_%03zu_c%05zu",cnt,wire), "AveDeriv", aveWaveformDeriv.size(), 0, aveWaveformDeriv.size(), -500., 500.);
224 
225  roiHist = dir.make<TProfile>(Form("ROI_%03zu_c%05zu",cnt,wire), "ROI", waveform.size(), 0, waveform.size(), -500., 500.);
226 
227  for(size_t binIdx = 0; binIdx < waveform.size(); binIdx++)
228  {
229  waveformHist->Fill(binIdx, waveform.at(binIdx));
230  derivativeHist->Fill(binIdx, waveformDeriv.at(binIdx));
231  }
232 
233  int binIdx(0);
234 
235  for(const auto& val : aveWaveformDeriv) aveDerivHist->Fill(binIdx++, val);
236 
237  } catch(...)
238  {
239  std::cout << "Caught exception trying to make new hists, plane,cnt,wire: " << fPlane << ", " << cnt << ", " << wire << std::endl;
240  }
241  }
242 
243  if (roiVec.empty()) return;
244 
245  int nMultiplier = 1; // fNumBinsToAve
246 
247  // pad the ROIs
248  for(auto& roi : roiVec)
249  {
250  if (roiHist)
251  {
252  roiHist->Fill(int(nMultiplier * roi.first), std::max(3.*truncRMS,1.));
253  roiHist->Fill(int(nMultiplier * roi.second), std::max(3.*truncRMS,1.));
254  }
255 
256  // low ROI end
257  roi.first = std::max(int(nMultiplier * roi.first - fPreROIPad),0);
258  // high ROI end
259  roi.second = std::min(nMultiplier * roi.second + fNumBinsToAve + fPostROIPad, waveform.size() - 1);
260  }
261 
262  // merge overlapping (or touching) ROI's
263  if(roiVec.size() > 1)
264  {
265  // temporary vector for merged ROIs
266  CandidateROIVec tempRoiVec;
267 
268  // Loop through candidate roi's
269  size_t startRoi = roiVec.front().first;
270  size_t stopRoi = startRoi;
271 
272  for(auto& roi : roiVec)
273  {
274  if (roi.first <= stopRoi + fMaxTicksGap) stopRoi = roi.second;
275  else
276  {
277  tempRoiVec.push_back(CandidateROI(startRoi,stopRoi));
278 
279  startRoi = roi.first;
280  stopRoi = roi.second;
281  }
282  }
283 
284  // Make sure to get the last one
285  tempRoiVec.push_back(CandidateROI(startRoi,stopRoi));
286 
287  roiVec = tempRoiVec;
288  }
289 
290  return;
291 }
292 
293 void ROIFinderDifferential::findROICandidates(Waveform::const_iterator startItr,
294  Waveform::const_iterator stopItr,
295  size_t channel,
296  size_t roiStartTick,
297  float truncRMS,
298  CandidateROIVec& roiCandidateVec) const
299 {
300  // Require a minimum length
301  size_t roiLength = std::distance(startItr,stopItr);
302 
303  if (roiLength > 0)
304  {
305  // The idea is to recover the two maxima from the input waveform and center our search for the ROI
306  // around them.
307  std::pair<Waveform::const_iterator,Waveform::const_iterator> minMaxItr = std::minmax_element(startItr, stopItr);
308 
309  Waveform::const_iterator maxItr = minMaxItr.second;
310  Waveform::const_iterator minItr = minMaxItr.first;
311 
312  // It can be that what has been returned are the lobes of two completely separate pulses which may be separated
313  // by a large number of ticks. So we can't simply assume they belong together... unless they are "close" with
314  // the minimum after the maximum
315  if (std::distance(maxItr,minItr) < 0 || std::distance(maxItr,minItr) > fMax2MinDistance)
316  {
317  // Given that, we key off the larger of the lobes and do a forward or backward search to find the minimum/maximum
318  // which we think is associated to the lobe we have chosen.
319  // First consider that the maximum is larger than the minimum...
320  if (*maxItr > std::fabs(*minItr))
321  {
322  // Check distance to the minimum we found
323  if (std::distance(maxItr,stopItr) > 0)
324  {
325  // The maximum is larger so search forward from here for the true minimum
326  minItr = maxItr;
327 
328  float prevValue = *minItr++;
329 
330  while(minItr != stopItr)
331  {
332  // Look for the point where it turns back up
333  if (prevValue < 0. && *minItr > prevValue)
334  {
335  // reset to what was the actual minimum value
336  minItr -= 1;
337  break;
338  }
339 
340  prevValue = *minItr++;
341  }
342  }
343  }
344  else
345  {
346  // Check distance to the max
347  if (std::distance(startItr,minItr) > 0)
348  {
349  // Otherwise, we are starting at the minimum and searching backwards to find the max
350  maxItr = minItr;
351 
352  float prevValue = *maxItr--;
353 
354  while(maxItr != startItr)
355  {
356  // Decreasing for two bins
357  if (prevValue > 0. && *maxItr < prevValue)
358  {
359  // reset to what was the actual minimum value
360  maxItr += 1;
361  break;
362  }
363 
364  prevValue = *maxItr--;
365  }
366  }
367  }
368  }
369 
370  // Check that the range from maximum to minimum is over threshold
371  float maxMinRange = *maxItr - *minItr;
372  int maxMinDistance = std::distance(maxItr,minItr);
373 
374  if (fOutputHistograms && maxMinRange > 2. * truncRMS)
375  {
376  fMinMaxDPeakHist->Fill(maxMinRange, 1.);
377  fMinMaxDistHist->Fill(maxMinDistance, 1.);
378  fMinMaxSigmaHist->Fill(maxMinRange/truncRMS, 1.);
379  fDPeakVDistHist->Fill(maxMinRange, maxMinDistance, 1.);
380  }
381 
382  if (maxMinRange > fNumSigma * truncRMS && maxMinDistance >= 0 && maxMinDistance < fMax2MinDistance)
383  {
384  // To complete the edges of the ROI, search both sides for the point which is essentially back to zero,
385  // or in reality back into the rms level...
386  Waveform::const_reverse_iterator revItr = std::find_if(std::make_reverse_iterator(maxItr), std::make_reverse_iterator(startItr), std::bind(std::less<float>(),std::placeholders::_1,truncRMS));
387 
388  maxItr = revItr.base();
389  minItr = std::find_if(minItr,stopItr,std::bind(std::greater<float>(),std::placeholders::_1,-truncRMS));
390 
391  // Before saving this ROI, look for candidates preceeding this one
392  // Note that preceeding snippet will reference to the current roiStartTick
393  findROICandidates(startItr, maxItr, channel, roiStartTick, truncRMS, roiCandidateVec);
394 
395  // Save this ROI
396  size_t newStartTick = std::distance(startItr,maxItr) + roiStartTick;
397  size_t newStopTick = std::distance(startItr,minItr) + roiStartTick;
398 
399  roiCandidateVec.push_back(CandidateROI(newStartTick, newStopTick));
400 
401  // Now look for candidate ROI's downstream of this one
402  findROICandidates(minItr, stopItr, channel, newStopTick, truncRMS, roiCandidateVec);
403  }
404  }
405 
406  return;
407 }
408 
409 void ROIFinderDifferential::averageInputWaveform(const Waveform& inputWaveform, Waveform& outputWaveform) const
410 {
411  // Vector reduction - take the 10 bin average
412  float aveSum(0.);
413 
414  if (outputWaveform.size() != inputWaveform.size()) outputWaveform.resize(inputWaveform.size());
415 
416  for(size_t idx = 0; idx < inputWaveform.size(); idx++)
417  {
418  aveSum += fAveWeightVec.at(idx % fNumBinsToAve) * inputWaveform.at(idx);
419 
420  if ((idx + 1) % fNumBinsToAve == 0)
421  {
422  outputWaveform[idx/fNumBinsToAve] = aveSum / fWeightSum;
423 
424  aveSum = 0.;
425  }
426  }
427 
428  return;
429 }
430 
431 void ROIFinderDifferential::smoothInputWaveform(const Waveform& inputWaveform, Waveform& outputWaveform) const
432 {
433  // Vector smoothing - take the 10 bin average
434  int halfBins = fNumBinsToAve / 2;
435 
436  outputWaveform.resize(inputWaveform.size());
437 
438  // Set the edge bins which can't be smoothed
439  std::copy(inputWaveform.begin(),inputWaveform.begin()+halfBins,outputWaveform.begin());
440  std::copy(inputWaveform.end()-halfBins,inputWaveform.end(),outputWaveform.end()-halfBins);
441 
442  for(size_t idx = halfBins; idx < inputWaveform.size() - halfBins; idx++)
443  {
444  float weightedSum(0.);
445 
446  for(int wIdx = 0; wIdx < fNumBinsToAve; wIdx++) weightedSum += fAveWeightVec.at(wIdx) * inputWaveform.at(idx - wIdx + halfBins);
447 
448  outputWaveform.at(idx) = weightedSum / fWeightSum;
449  }
450 
451  return;
452 }
453 
454 void ROIFinderDifferential::initializeHistograms(art::TFileDirectory& histDir) const
455 {
456  // It is assumed that the input TFileDirectory has been set up to group histograms into a common
457  // folder at the calling routine's level. Here we create one more level of indirection to keep
458  // histograms made by this tool separate.
459 /*
460  std::string dirName = "ROIFinderPlane_" + std::to_string(fPlane);
461 
462  art::TFileDirectory dir = histDir.mkdir(dirName.c_str());
463 
464  auto const* detprop = lar::providerFrom<detinfo::DetectorPropertiesService>();
465  double samplingRate = detprop->SamplingRate();
466  double numBins = fROIFinderVec.size();
467  double maxFreq = 500. / samplingRate;
468  std::string histName = "ROIFinderPlane_" + std::to_string(fPlane);
469 
470  TH1D* hist = dir.make<TH1D>(histName.c_str(), "ROIFinder;Frequency(MHz)", numBins, 0., maxFreq);
471 
472  for(int bin = 0; bin < numBins; bin++)
473  {
474  double freq = maxFreq * double(bin + 0.5) / double(numBins);
475 
476  hist->Fill(freq, fROIFinderVec.at(bin).Re());
477  }
478 */
479 
480  return;
481 }
482 
483 DEFINE_ART_CLASS_TOOL(ROIFinderDifferential)
484 }
icarus_signal_processing::WaveformTools< float > fWaveformTool
Utilities related to art service access.
void smoothInputWaveform(const Waveform &, Waveform &) const
void FindROIs(const Waveform &, size_t, size_t, double, CandidateROIVec &) const override
The data type to uniquely identify a Plane.
Definition: geo_types.h:472
void configure(const fhicl::ParameterSet &pset) override
std::vector< geo::WireID > ChannelToWire(raw::ChannelID_t const channel) const
Returns a list of wires connected to the specified TPC channel.
float fNumSigma
&quot;# sigma&quot; rms noise for ROI threshold
SigType_t SignalType(geo::PlaneID const &pid) const
Returns the type of signal on the channels of specified TPC plane.
int fMax2MinDistance
Maxmimum allow peak to peak distance.
double distance(geo::Point_t const &point, CathodeDesc_t const &cathode)
Returns the distance of a point from the cathode.
void averageInputWaveform(const Waveform &, Waveform &) const
enum geo::_plane_sigtype SigType_t
void initializeHistograms(art::TFileDirectory &) const override
Description of geometry of one entire detector.
tuple dir
Definition: dropbox.py:28
size_t fMaxTicksGap
Maximum gap between ROI&#39;s before merging.
float fTruncRMSMinFraction
or at least this fraction of time bins
void findROICandidates(Waveform::const_iterator startItr, Waveform::const_iterator stopItr, size_t channel, size_t roiStartTick, float roiThreshold, CandidateROIVec &roiCandidateVec) const
T copy(T const &v)
ROIFinderDifferential(const fhicl::ParameterSet &pset)
art::ServiceHandle< art::TFileService > tfs
art framework interface to geometry description
BEGIN_PROLOG could also be cout
Signal from collection planes.
Definition: geo_types.h:146