CandHitDerivative_tool.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 /// \file CandHitDerivative.cc
3 /// \author T. Usher
4 //note for MT: this implementation is not thread-safe
5 ////////////////////////////////////////////////////////////////////////
6 
9 
12 #include "art_root_io/TFileService.h"
13 #include "cetlib_except/exception.h"
15 
16 #include "TProfile.h"
17 
18 #include <cmath>
19 
20 namespace reco_tool {
21 
23  public:
24  explicit CandHitDerivative(const fhicl::ParameterSet& pset);
25 
26  void findHitCandidates(const recob::Wire::RegionsOfInterest_t::datarange_t&,
27  const size_t,
28  const size_t,
29  const size_t,
30  HitCandidateVec&) const override;
31 
32  void MergeHitCandidates(const recob::Wire::RegionsOfInterest_t::datarange_t&,
33  const HitCandidateVec&,
34  MergeHitCandidateVec&) const override;
35 
36  private:
37  // Internal functions
40  const size_t,
41  int,
42  float,
43  HitCandidateVec&) const;
44 
45  // Finding the nearest maximum/minimum from current point
50 
51  // handle finding the "start" and "stop" of a candidate hit
55 
56  // some control variables
57  size_t fPlane; //< Identifies the plane this tool is meant to operate on
58  int fMinDeltaTicks; //< minimum ticks from max to min to consider
59  int fMaxDeltaTicks; //< maximum ticks from max to min to consider
60  float fMinDeltaPeaks; //< minimum maximum to minimum peak distance
61  float fMinHitHeight; //< Drop candidate hits with height less than this
62  size_t fNumInterveningTicks; //< Number ticks between candidate hits to merge
63  bool fOutputHistograms; //< If true will generate a very large file of hists!
64 
65  art::TFileDirectory* fHistDirectory;
66 
67  // Global histograms
71 
72  mutable std::map<size_t, int> fChannelCntMap;
73 
74  // Member variables from the fhicl file
75  std::unique_ptr<reco_tool::IWaveformTool> fWaveformTool;
76 
77  const geo::GeometryCore* fGeometry = lar::providerFrom<geo::Geometry>();
78  };
79 
80  //----------------------------------------------------------------------
81  // Constructor.
83  {
84  fPlane = pset.get<size_t>("Plane", 0);
85  fMinDeltaTicks = pset.get<int>("MinDeltaTicks", 0);
86  fMaxDeltaTicks = pset.get<int>("MaxDeltaTicks", 30);
87  fMinDeltaPeaks = pset.get<float>("MinDeltaPeaks", 0.025);
88  fMinHitHeight = pset.get<float>("MinHitHeight", 2.0);
89  fNumInterveningTicks = pset.get<size_t>("NumInterveningTicks", 6);
90  fOutputHistograms = pset.get<bool>("OutputHistograms", false);
91 
92  // Recover the baseline tool
94  art::make_tool<reco_tool::IWaveformTool>(pset.get<fhicl::ParameterSet>("WaveformAlgs"));
95 
96  // If asked, define the global histograms
97  if (fOutputHistograms) {
98  // Access ART's TFileService, which will handle creating and writing
99  // histograms and n-tuples for us.
101 
102  fHistDirectory = tfs.get();
103 
104  // Make a directory for these histograms
105  art::TFileDirectory dir = fHistDirectory->mkdir(Form("HitPlane_%1zu", fPlane));
106 
108  dir.make<TH1F>(Form("DStopStart_%1zu", fPlane), ";Delta Stop/Start;", 200, 0., 200.);
110  dir.make<TH1F>(Form("DMaxTMinT_%1zu", fPlane), ";Delta Max/Min Tick;", 200, 0., 200.);
112  dir.make<TH1F>(Form("DMaxDMinD_%1zu", fPlane), ";Delta Max/Min Deriv;", 200, 0., 200.);
113  }
114 
115  return;
116  }
117 
118  void
120  const recob::Wire::RegionsOfInterest_t::datarange_t& dataRange,
121  const size_t roiStartTick,
122  const size_t channel,
123  const size_t eventCount,
124  HitCandidateVec& hitCandidateVec) const
125  {
126  // In this case we want to find hit candidates based on the derivative of of the input waveform
127  // We get this from our waveform algs too...
128  Waveform rawDerivativeVec;
129  Waveform derivativeVec;
130 
131  // Recover the actual waveform
132  const Waveform& waveform = dataRange.data();
133 
134  fWaveformTool->firstDerivative(waveform, rawDerivativeVec);
135  fWaveformTool->triangleSmooth(rawDerivativeVec, derivativeVec);
136 
137  std::vector<geo::WireID> wids = fGeometry->ChannelToWire(channel);
138  size_t plane = wids[0].Plane;
139  size_t cryo = wids[0].Cryostat;
140  size_t tpc = wids[0].TPC;
141  size_t wire = wids[0].Wire;
142 
143  // Just make sure the input candidate hit vector has been cleared
144  hitCandidateVec.clear();
145 
146  // Now find the hits
147  findHitCandidates(derivativeVec.begin(),
148  derivativeVec.end(),
149  roiStartTick,
152  hitCandidateVec);
153 
154  if (hitCandidateVec.empty()) {
155  if (plane == 0) {
156  std::cout << "** C/T/P: " << cryo << "/" << tpc << "/" << plane << ", wire: " << wire
157  << " has not hits with input size: " << waveform.size() << std::endl;
158  }
159  }
160 
161  // Reset the hit height from the input waveform
162  for (auto& hitCandidate : hitCandidateVec) {
163  size_t centerIdx = hitCandidate.hitCenter;
164 
165  hitCandidate.hitHeight = waveform.at(centerIdx);
166  }
167 
168  // Keep track of histograms if requested
169  if (fOutputHistograms) {
170  // Recover the details...
171  // std::vector<geo::WireID> wids = fGeometry->ChannelToWire(channel);
172  // size_t plane = wids[0].Plane;
173  // size_t cryo = wids[0].Cryostat;
174  // size_t tpc = wids[0].TPC;
175  // size_t wire = wids[0].Wire;
176 
177  size_t channelCnt = fChannelCntMap[channel]++;
178 
179  // Make a directory for these histograms
180  art::TFileDirectory dir = fHistDirectory->mkdir(
181  Form("HitPlane_%1zu/ev%04zu/c%1zut%1zuwire_%05zu", plane, eventCount, cryo, tpc, wire));
182 
183  size_t waveformSize = waveform.size();
184  int waveStart = roiStartTick;
185  int waveStop = waveStart + waveformSize;
186 
187  TProfile* waveHist = dir.make<TProfile>(
188  Form("HWfm_%03zu_ctw%01zu-%01zu-%01zu-%05zu", channelCnt, cryo, tpc, plane, wire),
189  "Waveform",
190  waveformSize,
191  waveStart,
192  waveStop,
193  -500.,
194  500.);
195  TProfile* derivHist = dir.make<TProfile>(
196  Form("HDer_%03zu_ctw%01zu-%01zu-%01zu-%05zu", channelCnt, cryo, tpc, plane, wire),
197  "Derivative",
198  waveformSize,
199  waveStart,
200  waveStop,
201  -500.,
202  500.);
203  TProfile* candHitHist = dir.make<TProfile>(
204  Form("HCan_%03zu_ctw%01zu-%01zu-%01zu-%05zu", channelCnt, cryo, tpc, plane, wire),
205  "Cand Hits",
206  waveformSize,
207  waveStart,
208  waveStop,
209  -500.,
210  500.);
211  TProfile* maxDerivHist = dir.make<TProfile>(
212  Form("HMax_%03zu_ctw%01zu-%01zu-%01zu-%05zu", channelCnt, cryo, tpc, plane, wire),
213  "Maxima",
214  waveformSize,
215  waveStart,
216  waveStop,
217  -500.,
218  500.);
219 
220  // Fill wave/derivative
221  for (size_t idx = 0; idx < waveform.size(); idx++) {
222  waveHist->Fill(roiStartTick + idx, waveform.at(idx));
223  derivHist->Fill(roiStartTick + idx, derivativeVec.at(idx));
224  }
225 
226  // Fill hits
227  for (const auto& hitCandidate : hitCandidateVec) {
228  candHitHist->Fill(hitCandidate.hitCenter, hitCandidate.hitHeight);
229  maxDerivHist->Fill(hitCandidate.maxTick, hitCandidate.maxDerivative);
230  maxDerivHist->Fill(hitCandidate.minTick, hitCandidate.minDerivative);
231 
232  fDStopStartHist->Fill(hitCandidate.stopTick - hitCandidate.startTick, 1.);
233  fDMaxTickMinTickHist->Fill(hitCandidate.minTick - hitCandidate.maxTick, 1.);
234  fDMaxDerivMinDerivHist->Fill(hitCandidate.maxDerivative - hitCandidate.minDerivative, 1.);
235  }
236  }
237 
238  return;
239  }
240 
241  void
243  Waveform::const_iterator stopItr,
244  const size_t roiStartTick,
245  int dTicksThreshold,
246  float dPeakThreshold,
247  HitCandidateVec& hitCandidateVec) const
248  {
249  // Search for candidate hits...
250  // The idea will be to find the largest deviation in the input derivative waveform as the starting point. Depending
251  // on if a maximum or minimum, we search forward or backward to find the minimum or maximum that our extremum
252  // corresponds to.
253  std::pair<Waveform::const_iterator, Waveform::const_iterator> minMaxPair =
254  std::minmax_element(startItr, stopItr);
255 
256  Waveform::const_iterator maxItr = minMaxPair.second;
257  Waveform::const_iterator minItr = minMaxPair.first;
258 
259  // Use the larger of the two as the starting point and recover the nearest max or min
260  if (std::fabs(*maxItr) > std::fabs(*minItr))
261  minItr = findNearestMin(maxItr, stopItr);
262  else
263  maxItr = findNearestMax(minItr, startItr);
264 
265  int deltaTicks = std::distance(maxItr, minItr);
266  float range = *maxItr - *minItr;
267 
268  // At some point small rolling oscillations on the waveform need to be ignored...
269  if (deltaTicks >= dTicksThreshold && range > dPeakThreshold) {
270  // Need to back up to find zero crossing, this will be the starting point of our
271  // candidate hit but also the endpoint of the pre sub-waveform we'll search next
272  Waveform::const_iterator newEndItr = findStartTick(maxItr, startItr);
273 
274  int startTick = std::distance(startItr, newEndItr);
275 
276  // Now need to go forward to again get close to zero, this will then be the end point
277  // of our candidate hit and the starting point for the post sub-waveform to search
278  Waveform::const_iterator newStartItr = findStopTick(minItr, stopItr);
279 
280  int stopTick = std::distance(startItr, newStartItr);
281 
282  // Find hits in the section of the waveform leading up to this candidate hit
283  if (startTick > dTicksThreshold) {
284  // Special handling for merged hits
285  if (*(newEndItr - 1) > 0.) {
286  dTicksThreshold = 2;
287  dPeakThreshold = 0.;
288  }
289  else {
290  dTicksThreshold = fMinDeltaTicks;
291  dPeakThreshold = fMinDeltaPeaks;
292  }
293 
295  startItr, newEndItr + 1, roiStartTick, dTicksThreshold, dPeakThreshold, hitCandidateVec);
296  }
297 
298  // Create a new hit candidate and store away
299  HitCandidate hitCandidate;
300 
301  Waveform::const_iterator peakItr =
302  std::min_element(maxItr, minItr, [](const auto& left, const auto& right) {
303  return std::fabs(left) < std::fabs(right);
304  });
305 
306  // Check balance
307  if (2 * std::distance(peakItr, minItr) < std::distance(maxItr, peakItr))
308  peakItr--;
309  else if (2 * std::distance(maxItr, peakItr) < std::distance(peakItr, minItr))
310  peakItr++;
311 
312  hitCandidate.startTick = roiStartTick + startTick;
313  hitCandidate.stopTick = roiStartTick + stopTick;
314  hitCandidate.maxTick = roiStartTick + std::distance(startItr, maxItr);
315  hitCandidate.minTick = roiStartTick + std::distance(startItr, minItr);
316  hitCandidate.maxDerivative = maxItr != stopItr ? *maxItr : 0.;
317  hitCandidate.minDerivative = minItr != stopItr ? *minItr : 0.;
318  hitCandidate.hitCenter = roiStartTick + std::distance(startItr, peakItr) + 0.5;
319  hitCandidate.hitSigma = 0.5 * float(hitCandidate.minTick - hitCandidate.maxTick);
320  hitCandidate.hitHeight =
321  hitCandidate.hitSigma * (hitCandidate.maxDerivative - hitCandidate.minDerivative) / 1.2130;
322 
323  hitCandidateVec.push_back(hitCandidate);
324 
325  // Finally, search the section of the waveform following this candidate for more hits
326  if (std::distance(newStartItr, stopItr) > dTicksThreshold) {
327  // Special handling for merged hits
328  if (*(newStartItr + 1) < 0.) {
329  dTicksThreshold = 2;
330  dPeakThreshold = 0.;
331  }
332  else {
333  dTicksThreshold = fMinDeltaTicks;
334  dPeakThreshold = fMinDeltaPeaks;
335  }
336 
337  findHitCandidates(newStartItr,
338  stopItr,
339  roiStartTick + stopTick,
340  dTicksThreshold,
341  dPeakThreshold,
342  hitCandidateVec);
343  }
344  }
345 
346  return;
347  }
348 
349  void
351  const recob::Wire::RegionsOfInterest_t::datarange_t& rangeData,
352  const HitCandidateVec& hitCandidateVec,
353  MergeHitCandidateVec& mergedHitsVec) const
354  {
355  // If nothing on the input end then nothing to do
356  if (hitCandidateVec.empty()) return;
357 
358  // The idea is to group hits that "touch" so they can be part of common fit, those that
359  // don't "touch" are fit independently. So here we build the output vector to achieve that
360  // Get a container for the hits...
361  HitCandidateVec groupedHitVec;
362 
363  // Initialize the end of the last hit which we'll set to the first input hit's stop
364  size_t lastStopTick = hitCandidateVec.front().stopTick;
365 
366  // Step through the input hit candidates and group them by proximity
367  for (const auto& hitCandidate : hitCandidateVec) {
368  // Small pulse height hits should not be considered?
369  if (hitCandidate.hitHeight > fMinHitHeight) {
370  // Check condition that we have a new grouping
371  if (hitCandidate.startTick > lastStopTick + fNumInterveningTicks &&
372  !groupedHitVec.empty()) {
373  mergedHitsVec.emplace_back(groupedHitVec);
374 
375  groupedHitVec.clear();
376  }
377 
378  // Add the current hit to the current group
379  groupedHitVec.emplace_back(hitCandidate);
380 
381  lastStopTick = hitCandidate.stopTick;
382  }
383  }
384 
385  // Check end condition
386  if (!groupedHitVec.empty()) mergedHitsVec.emplace_back(groupedHitVec);
387 
388  return;
389  }
390 
393  Waveform::const_iterator stopItr) const
394  {
395  // reset the min iterator and search forward to find the nearest minimum
396  Waveform::const_iterator lastItr = maxItr;
397 
398  // The strategy is simple...
399  // We are at a maximum so we search forward until we find the lowest negative point
400  while ((lastItr + 1) != stopItr) {
401  if (*(lastItr + 1) > *lastItr) break;
402 
403  lastItr++;
404  }
405 
406  // The minimum will be the last iterator value...
407  return lastItr;
408  }
409 
412  Waveform::const_iterator startItr) const
413  {
414  // Set the internal loop variable...
415  Waveform::const_iterator lastItr = minItr;
416 
417  // One extra condition to watch for here, make sure we can actually back up!
418  if (std::distance(startItr, minItr) > 0) {
419  // Similar to searching for a maximum, we loop backward over ticks looking for the waveform to start decreasing
420  while ((lastItr - 1) != startItr) {
421  if (*(lastItr - 1) < *lastItr) break;
422 
423  lastItr--;
424  }
425  }
426 
427  return lastItr;
428  }
429 
432  Waveform::const_iterator startItr) const
433  {
434  Waveform::const_iterator lastItr = maxItr;
435 
436  // If we can't back up then there is nothing to do
437  if (std::distance(startItr, lastItr) > 0) {
438  // In theory, we are starting at a maximum and want to find the "start" of the candidate peak
439  // Ideally we would look to search backward to the point where the (derivative) waveform crosses zero again.
440  // However, the complication is that we need to watch for the case where two peaks are merged together and
441  // we might run through another peak before crossing zero...
442  // So... loop until we hit the startItr...
443  Waveform::const_iterator loopItr = lastItr - 1;
444 
445  while (loopItr != startItr) {
446  // Ideal world case, we cross zero... but we might encounter a minimum... or an inflection point
447  if (*loopItr < 0. || !(*loopItr < *lastItr)) break;
448 
449  lastItr = loopItr--;
450  }
451  }
452  else
453  lastItr = startItr;
454 
455  return lastItr;
456  }
457 
460  Waveform::const_iterator stopItr) const
461  {
462  Waveform::const_iterator lastItr = minItr;
463 
464  // If we can't go forward then there is really nothing to do
465  if (std::distance(minItr, stopItr) > 1) {
466  // Pretty much the same strategy as for finding the start tick...
467  Waveform::const_iterator loopItr = lastItr + 1;
468 
469  while (loopItr != stopItr) {
470  // Ideal case that we have crossed zero coming from a minimum... but watch for a maximum as well
471  if (*loopItr > 0. || !(*loopItr > *lastItr)) break;
472 
473  lastItr = loopItr++;
474  }
475  }
476 
477  return lastItr;
478  }
479 
481 }
#define DEFINE_ART_CLASS_TOOL(tool)
Definition: ToolMacros.h:42
void findHitCandidates(const recob::Wire::RegionsOfInterest_t::datarange_t &, const size_t, const size_t, const size_t, HitCandidateVec &) const override
T * get() const
Definition: ServiceHandle.h:63
This is the interface class for tools/algorithms that perform various operations on waveforms...
Waveform::const_iterator findNearestMax(Waveform::const_iterator, Waveform::const_iterator) const
const geo::GeometryCore * fGeometry
std::vector< geo::WireID > ChannelToWire(raw::ChannelID_t const channel) const
Returns a list of wires connected to the specified TPC channel.
intermediate_table::const_iterator const_iterator
uint8_t channel
Definition: CRTFragment.hh:201
string dir
art framework interface to geometry description
T get(std::string const &key) const
Definition: ParameterSet.h:271
std::unique_ptr< reco_tool::IWaveformTool > fWaveformTool
double distance(double x1, double y1, double z1, double x2, double y2, double z2)
Description of geometry of one entire detector.
std::map< size_t, int > fChannelCntMap
void MergeHitCandidates(const recob::Wire::RegionsOfInterest_t::datarange_t &, const HitCandidateVec &, MergeHitCandidateVec &) const override
Waveform::const_iterator findStopTick(Waveform::const_iterator, Waveform::const_iterator) const
CandHitDerivative(const fhicl::ParameterSet &pset)
This provides an interface for tools which are tasked with finding candidate hits on input waveforms...
Waveform::const_iterator findStartTick(Waveform::const_iterator, Waveform::const_iterator) const
art::TFileDirectory * fHistDirectory
std::vector< HitCandidateVec > MergeHitCandidateVec
QTextStream & endl(QTextStream &s)
Waveform::const_iterator findNearestMin(Waveform::const_iterator, Waveform::const_iterator) const
std::vector< HitCandidate > HitCandidateVec