LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
FFTHitFinder_module.cc
Go to the documentation of this file.
1 //
3 // FFTHitFinder class
4 //
5 // pagebri3@msu.edu
6 //
7 // This algorithm is designed to find hits on wires after deconvolution
8 // with an average shape used as the input response.
10 
11 // C/C++ standard library
12 #include <numeric> // std::accumulate
13 #include <string>
14 
15 // Framework includes
22 
23 // LArSoft Includes
25 #include "larcoreobj/SimpleTypesAndConstants/RawTypes.h" // raw::ChannelID_t
29 
30 // ROOT Includes
31 #include "TDecompSVD.h"
32 #include "TF1.h"
33 #include "TH1D.h"
34 #include "TMath.h"
35 
36 namespace hit {
37 
38  class FFTHitFinder : public art::EDProducer {
39 
40  public:
41  explicit FFTHitFinder(fhicl::ParameterSet const& pset);
42 
43  private:
44  void produce(art::Event& evt) override;
45 
46  std::string fCalDataModuleLabel;
47  double fMinSigInd;
48  double fMinSigCol;
49  double fIndWidth;
50  double fColWidth;
51  double fIndMinWidth;
52  double fColMinWidth;
55  std::vector<double> fAreaNorms;
56 
57  }; // class FFTHitFinder
58 
59  //-------------------------------------------------
61  {
62  fCalDataModuleLabel = pset.get<std::string>("CalDataModuleLabel");
63  fMinSigInd = pset.get<double>("MinSigInd");
64  fMinSigCol = pset.get<double>("MinSigCol");
65  fIndWidth = pset.get<double>("IndWidth");
66  fColWidth = pset.get<double>("ColWidth");
67  fIndMinWidth = pset.get<double>("IndMinWidth");
68  fColMinWidth = pset.get<double>("ColMinWidth");
69  fMaxMultiHit = pset.get<int>("MaxMultiHit");
70  fAreaMethod = pset.get<int>("AreaMethod");
71  fAreaNorms = pset.get<std::vector<double>>("AreaNorms");
72 
73  // let HitCollectionCreator declare that we are going to produce
74  // hits and associations with wires and raw digits
75  // (with no particular product label)
77  }
78 
79  // This algorithm uses the fact that deconvolved signals are very smooth
80  // and looks for hits as areas between local minima that have signal above
81  // threshold.
82  //-------------------------------------------------
84  {
85 
86  // this object contains the hit collection
87  // and its associations to wires and raw digits:
89 
90  // Read in the wire List object(s).
92  evt.getByLabel(fCalDataModuleLabel, wireVecHandle);
94 
95  // also get the raw digits associated with wires
96  art::FindOneP<raw::RawDigit> WireToRawDigits(wireVecHandle, evt, fCalDataModuleLabel);
97 
98  std::vector<int> startTimes; // stores time of 1st local minimum
99  std::vector<int> maxTimes; // stores time of local maximum
100  std::vector<int> endTimes; // stores time of 2nd local minimum
101  int time = 0; // current time bin
102  int minTimeHolder = 0; // current start time
103  raw::ChannelID_t channel = raw::InvalidChannelID; // channel number
104  bool maxFound = false; // Flag for whether a peak > threshold has been found
105  double threshold = 0.; // minimum signal size for id'ing a hit
106  double fitWidth = 0.; // hit fit width initial value
107  double minWidth = 0.; // minimum hit width
108  std::string eqn = "gaus(0)"; // string for equation to fit
109  geo::SigType_t sigType = geo::kInduction; // type of plane we are looking at
110  std::stringstream numConv;
111 
112  //loop over wires
113  for (unsigned int wireIter = 0; wireIter < wireVecHandle->size(); wireIter++) {
114 
115  art::Ptr<recob::Wire> wire(wireVecHandle, wireIter);
116  startTimes.clear();
117  maxTimes.clear();
118  endTimes.clear();
119  std::vector<float> signal(wire->Signal());
120  std::vector<float>::iterator timeIter; // iterator for time bins
121  time = 0;
122  minTimeHolder = 0;
123  maxFound = false;
124  channel = wire->Channel();
125  sigType = geom->SignalType(channel);
126 
127  //Set the appropriate signal widths and thresholds
128  if (sigType == geo::kInduction) {
129  threshold = fMinSigInd;
130  fitWidth = fIndWidth;
131  minWidth = fIndMinWidth;
132  }
133  else if (sigType == geo::kCollection) {
134  threshold = fMinSigCol;
135  fitWidth = fColWidth;
136  minWidth = fColMinWidth;
137  }
138  // loop over signal
139  for (timeIter = signal.begin(); timeIter + 2 < signal.end(); timeIter++) {
140  //test if timeIter+1 is a local minimum
141  if (*timeIter > *(timeIter + 1) && *(timeIter + 1) < *(timeIter + 2)) {
142  //only add points if already found a local max above threshold.
143  if (maxFound) {
144  endTimes.push_back(time + 1);
145  maxFound = false;
146  //keep these in case new hit starts right away
147  minTimeHolder = time + 2;
148  }
149  else
150  minTimeHolder = time + 1;
151  }
152  //if not a minimum, test if we are at a local maximum
153  //if so, and the max value is above threshold, add it and proceed.
154  else if (*timeIter < *(timeIter + 1) && *(timeIter + 1) > *(timeIter + 2) &&
155  *(timeIter + 1) > threshold) {
156  maxFound = true;
157  maxTimes.push_back(time + 1);
158  startTimes.push_back(minTimeHolder);
159  }
160  time++;
161  } //end loop over signal vec
162 
163  //if no inflection found before end, but peak found add end point
164  while (maxTimes.size() > endTimes.size())
165  endTimes.push_back(signal.size() - 1);
166  if (startTimes.size() == 0) continue;
167 
168  //All code below does the fitting, adding of hits
169  //to the hit vector and when all wires are complete
170  //saving them
171  double totSig(0); // stores the total hit signal
172  double startT(0); // stores the start time
173  double endT(0); // stores the end time
174  int numHits(0); // number of consecutive hits being fitted
175  int size(0); // size of data vector for fit
176  int hitIndex(0); // index of current hit in sequence
177  double amplitude(0), position(0), width(0); //fit parameters
178  double amplitudeErr(0), positionErr(0), widthErr(0); //fit errors
179  double goodnessOfFit(0), chargeErr(0); //Chi2/NDF and error on charge
180  double minPeakHeight(0); //lowest peak height in multi-hit fit
181 
182  //stores gaussian paramters first index is the hit number
183  //the second refers to height, position, and width respectively
184  std::vector<double> hitSig;
185 
186  //add found hits to hit vector
187  while (hitIndex < (signed)startTimes.size()) {
188 
189  startT = endT = 0;
190  numHits = 1;
191  minPeakHeight = signal[maxTimes[hitIndex]];
192 
193  //consider adding pulse to group of consecutive hits if:
194  //1 less than max consecutive hits
195  //2 we are not at the last point in the signal vector
196  //3 the height of the dip between the two is greater than threshold/2
197  //4 and there is no gap between them
198  while (numHits < fMaxMultiHit && numHits + hitIndex < (signed)endTimes.size() &&
199  signal[endTimes[hitIndex + numHits - 1]] > threshold / 2.0 &&
200  startTimes[hitIndex + numHits] - endTimes[hitIndex + numHits - 1] < 2) {
201 
202  if (signal[maxTimes[hitIndex + numHits]] < minPeakHeight)
203  minPeakHeight = signal[maxTimes[hitIndex + numHits]];
204 
205  ++numHits;
206  }
207 
208  //finds the first point > 1/2 the smallest peak
209  startT = startTimes[hitIndex];
210 
211  while (signal[(int)startT] < minPeakHeight / 2.0)
212  ++startT;
213 
214  //finds the first point from the end > 1/2 the smallest peak
215  endT = endTimes[hitIndex + numHits - 1];
216 
217  while (signal[(int)endT] < minPeakHeight / 2.0)
218  --endT;
219  size = (int)(endT - startT);
220  TH1D hitSignal("hitSignal", "", size, startT, endT);
221  for (int i = (int)startT; i < (int)endT; ++i)
222  hitSignal.Fill(i, signal[i]);
223 
224  //build the TFormula
225  eqn = "gaus(0)";
226 
227  for (int i = 3; i < numHits * 3; i += 3) {
228  eqn.append("+gaus(");
229  numConv.str("");
230  numConv << i;
231  eqn.append(numConv.str());
232  eqn.append(")");
233  }
234 
235  TF1 gSum("gSum", eqn.c_str(), 0, size);
236 
237  if (numHits > 1) {
238  TArrayD data(numHits * numHits);
239  TVectorD amps(numHits);
240  for (int i = 0; i < numHits; ++i) {
241  amps[i] = signal[maxTimes[hitIndex + i]];
242  for (int j = 0; j < numHits; j++)
243  data[i + numHits * j] =
244  TMath::Gaus(maxTimes[hitIndex + j], maxTimes[hitIndex + i], fitWidth);
245  } //end loop over hits
246 
247  //This section uses a linear approximation in order to get an
248  //initial value of the individual hit amplitudes
249  try {
250  TMatrixD h(numHits, numHits);
251  h.Use(numHits, numHits, data.GetArray());
252  TDecompSVD a(h);
253  a.Solve(amps);
254  }
255  catch (...) {
256  mf::LogInfo("FFTHitFinder") << "TDcompSVD failed";
257  hitIndex += numHits;
258  continue;
259  }
260 
261  for (int i = 0; i < numHits; ++i) {
262  //if the approximation makes a peak vanish
263  //set initial height as average of threshold and
264  //raw peak height
265  if (amps[i] > 0)
266  amplitude = amps[i];
267  else
268  amplitude = 0.5 * (threshold + signal[maxTimes[hitIndex + i]]);
269  gSum.SetParameter(3 * i, amplitude);
270  gSum.SetParameter(1 + 3 * i, maxTimes[hitIndex + i]);
271  gSum.SetParameter(2 + 3 * i, fitWidth);
272  gSum.SetParLimits(3 * i, 0.0, 3.0 * amplitude);
273  gSum.SetParLimits(1 + 3 * i, startT, endT);
274  gSum.SetParLimits(2 + 3 * i, 0.0, 10.0 * fitWidth);
275  } //end loop over hits
276  } //end if numHits > 1
277  else {
278  gSum.SetParameters(signal[maxTimes[hitIndex]], maxTimes[hitIndex], fitWidth);
279  gSum.SetParLimits(0, 0.0, 1.5 * signal[maxTimes[hitIndex]]);
280  gSum.SetParLimits(1, startT, endT);
281  gSum.SetParLimits(2, 0.0, 10.0 * fitWidth);
282  }
283 
285  hitSignal.Fit(&gSum, "QNRW", "", startT, endT);
286  for (int hitNumber = 0; hitNumber < numHits; ++hitNumber) {
287  totSig = 0;
288  if (gSum.GetParameter(3 * hitNumber) > threshold / 2.0 &&
289  gSum.GetParameter(3 * hitNumber + 2) > minWidth) {
290  amplitude = gSum.GetParameter(3 * hitNumber);
291  position = gSum.GetParameter(3 * hitNumber + 1);
292  width = gSum.GetParameter(3 * hitNumber + 2);
293  amplitudeErr = gSum.GetParError(3 * hitNumber);
294  positionErr = gSum.GetParError(3 * hitNumber + 1);
295  widthErr = gSum.GetParError(3 * hitNumber + 2);
296  goodnessOfFit = gSum.GetChisquare() / (double)gSum.GetNDF();
297  int DoF = gSum.GetNDF();
298 
299  //estimate error from area of Gaussian
300  chargeErr = std::sqrt(TMath::Pi()) * (amplitudeErr * width + widthErr * amplitude);
301 
302  hitSig.resize(size);
303 
304  for (int sigPos = 0; sigPos < size; ++sigPos) {
305  hitSig[sigPos] = amplitude * TMath::Gaus(sigPos + startT, position, width);
306  totSig += hitSig[(int)sigPos];
307  }
308 
309  if (fAreaMethod)
310  totSig = std::sqrt(2 * TMath::Pi()) * amplitude * width / fAreaNorms[(size_t)sigType];
311 
312  // get the WireID for this hit
313  std::vector<geo::WireID> wids = geom->ChannelToWire(channel);
315  // for now, just take the first option returned from ChannelToWire
316  geo::WireID wid = wids[0];
317 
318  // make the hit
319  recob::HitCreator hit(*wire, // wire
320  wid, // wireID
321  (int)startT, // start_tick
322  (int)endT, // end_tick
323  width, // rms
324  position, // peak_time
325  positionErr, // sigma_peak_time
326  amplitude, // peak_amplitude
327  amplitudeErr, // sigma_peak_amplitude
328  totSig, // hit_integral
329  chargeErr, // hit_sigma_integral
330  std::accumulate // summedADC
331  (signal.begin() + (int)startT, signal.begin() + (int)endT, 0.),
332  1, // multiplicity
333  -1, // local_index
335  goodnessOfFit, // goodness_of_fit
336  DoF // dof
337  );
338 
339  // get the object associated with the original hit
340  art::Ptr<raw::RawDigit> rawdigits = WireToRawDigits.at(wireIter);
341 
342  hcol.emplace_back(hit.move(), wire, rawdigits);
343  } //end if over threshold
344  } //end loop over hits
345  hitIndex += numHits;
346  } // end while on hitIndex<(signed)startTimes.size()
347 
348  } // while on Wires
349 
350  // put the hit collection and associations into the event
351  hcol.put_into(evt);
352 
353  } // End of produce()
354 
356 
357 } // end of hit namespace
double fIndMinWidth
Minimum induction hit width.
intermediate_table::iterator iterator
FFTHitFinder(fhicl::ParameterSet const &pset)
std::vector< WireID > ChannelToWire(raw::ChannelID_t const channel) const
Returns a list of wires connected to the specified TPC channel.
MaybeLogger_< ELseverityLevel::ELsev_info, false > LogInfo
double fMinSigInd
Induction signal height threshold.
EDProducer(fhicl::ParameterSet const &pset)
Definition: EDProducer.cc:6
double fIndWidth
Initial width for induction fit.
int fMaxMultiHit
maximum hits for multi fit
double fColWidth
Initial width for collection fit.
static void declare_products(art::ProducesCollector &collector, std::string instance_name="", bool doWireAssns=true, bool doRawDigitAssns=true)
Declares the hit products we are going to fill.
Definition: HitCreator.cxx:248
std::vector< double > fAreaNorms
factors for converting area to same units as peak height
decltype(auto) constexpr size(T &&obj)
ADL-aware version of std::size.
Definition: StdUtils.h:101
Class managing the creation of a new recob::Hit object.
Definition: HitCreator.h:87
Helper functions to create a hit.
std::string fCalDataModuleLabel
constexpr ChannelID_t InvalidChannelID
ID of an invalid channel.
Definition: RawTypes.h:31
A class handling a collection of hits and its associations.
Definition: HitCreator.h:481
#define DEFINE_ART_MODULE(klass)
Definition: ModuleMacros.h:65
Signal from induction planes.
Definition: geo_types.h:151
raw::ChannelID_t Channel() const
Returns the ID of the channel (or InvalidChannelID)
Definition: Wire.h:223
enum geo::_plane_sigtype SigType_t
Enumerate the possible plane projections.
double fColMinWidth
Minimum collection hit width.
void emplace_back(recob::Hit &&hit, art::Ptr< recob::Wire > const &wire=art::Ptr< recob::Wire >(), art::Ptr< raw::RawDigit > const &digits=art::Ptr< raw::RawDigit >())
Adds the specified hit to the data collection.
Definition: HitCreator.cxx:286
void produce(art::Event &evt) override
Definition of data types for geometry description.
void put_into(art::Event &)
Moves the data into an event.
Definition: HitCreator.h:614
Detector simulation of raw signals on wires.
std::vector< float > Signal() const
Return a zero-padded full length vector filled with RoI signal.
Definition: Wire.cxx:30
ProducesCollector & producesCollector() noexcept
SigType_t SignalType(PlaneID const &pid) const
Returns the type of signal on the channels of specified TPC plane.
bool getByLabel(std::string const &label, std::string const &instance, Handle< PROD > &result) const
double fMinSigCol
Collection signal height threshold.
Declaration of basic channel signal object.
int fAreaMethod
Type of area calculation.
TCEvent evt
Definition: DataStructs.cxx:8
unsigned int ChannelID_t
Type representing the ID of a readout channel.
Definition: RawTypes.h:28
Definition: fwd.h:26
art framework interface to geometry description
Signal from collection planes.
Definition: geo_types.h:152