All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
PCAngleKinkFinder_module.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 // Class: PCAngleKinkFinderFinder
3 // Plugin Type: producer (art v3_02_06)
4 // File: PCAngleKinkFinder_module.cc
5 //
6 // Generated at Wed Feb 19 17:38:21 2020 by Gray Putnam using cetskelgen
7 // from cetlib version v3_07_02.
8 ////////////////////////////////////////////////////////////////////////
9 
10 #include "art/Framework/Core/EDProducer.h"
11 #include "art/Framework/Core/ModuleMacros.h"
12 #include "art/Framework/Principal/Event.h"
13 #include "art/Framework/Principal/Handle.h"
14 #include "art/Framework/Principal/Run.h"
15 #include "art/Framework/Principal/SubRun.h"
16 #include "canvas/Utilities/InputTag.h"
17 #include "fhiclcpp/ParameterSet.h"
18 #include "messagefacility/MessageLogger/MessageLogger.h"
20 
22 
23 #include <memory>
24 
34 
35 #include "sbnobj/Common/Reco/PCAnglePlane.h"
36 #include "sbnobj/Common/Reco/PCAngleKink.h"
37 
38 #include "TFitter.h"
39 #include "TMinuit.h"
40 
41 namespace sbn {
42  class PCAngleKinkFinder;
43 }
44 
45 
46 class sbn::PCAngleKinkFinder : public art::EDProducer {
47 public:
48  explicit PCAngleKinkFinder(fhicl::ParameterSet const& p);
49  // The compiler-generated destructor is fine for non-base
50  // classes without bare pointers or other resource use.
51 
52  // Plugins should not be copied or assigned.
53  PCAngleKinkFinder(PCAngleKinkFinder const&) = delete;
57 
58  // Required functions.
59  void produce(art::Event& e) override;
60 
61  struct FitResult {
62  float chi2;
63  float angle;
64  float pitch;
65  };
66 
67 private:
68  // config
69  std::string fPFParticleLabel;
70  std::string fAngleLabel;
72  float fMinDist;
74  float fPCARadius;
76 
77 
78  // private helpers
79  FitResult Fit(const std::vector<sbn::PCAngle> &angles, unsigned lo, unsigned hi, unsigned center, float est_angle, float est_pitch);
80 
81 };
82 
83 // TODO: FIX
84 // TMinuit sux, so we have to define the data as a static global variable
85 // private data
86 static std::vector<float> fFitDataX;
87 static std::vector<float> fFitDataY;
88 static unsigned fFitCenter;
89 
90 // static helper functions
91 sbn::PCAngleKink BuildKink(const sbn::PCAngle &max, const sbn::PCAngle &lo, const sbn::PCAngle &hi, const sbn::PCAngleKinkFinder::FitResult &fit, float est_angle) {
92  sbn::PCAngleKink kink;
93  kink.maxWire = max.wire;
94 
95  kink.fwhm_distance = sqrt((hi.hit_time_cm - lo.hit_time_cm) * (hi.hit_time_cm - lo.hit_time_cm)
96  + (hi.hit_wire_cm - lo.hit_wire_cm) * (hi.hit_wire_cm - lo.hit_wire_cm));
97  kink.max_angle = max.angle;
98  kink.est_angle = est_angle;
99 
100  kink.position_max = {max.hit_time_cm, max.hit_wire_cm};
101  kink.position_lo = {lo.hit_time_cm, lo.hit_wire_cm};
102  kink.position_hi = {hi.hit_time_cm, hi.hit_wire_cm};
103 
104  kink.vec_lo_at_max = max.lo_vector;
105  kink.vec_hi_at_max = max.hi_vector;
106 
107  kink.vec_lo_at_halfmax_lo = lo.lo_vector;
108  kink.vec_hi_at_halfmax_lo = lo.hi_vector;
109 
110  kink.vec_lo_at_halfmax_hi = hi.lo_vector;
111  kink.vec_hi_at_halfmax_hi = hi.hi_vector;
112 
113  kink.fit_chi2 = fit.chi2;
114  kink.fit_angle = fit.angle;
115  kink.fit_pitch = fit.pitch;
116 
117  return kink;
118 }
119 
120 // flatten the structure of each branch
121 std::tuple<std::vector<sbn::PCAngle>, unsigned> FlattenBranch(const sbn::PCAnglePlane &plane, unsigned i_branch, bool allow_incomplete, float min_dist) {
122  std::vector<sbn::PCAngle> ret;
123 
124  std::cout << "Flattening angles for branch: " << i_branch << " plane: " << plane.plane.Plane << " hierarchy: ";
125  for (int id: plane.branchHierarchy[i_branch]) std::cout << id << " ";
126  std::cout << std::endl;
127 
128  // start at the end of the branch and count backwards
129  for (unsigned i_branch_hierarchy = 0; i_branch_hierarchy < plane.branchHierarchy[i_branch].size(); i_branch_hierarchy++) {
130  // look up the index of the current branch
131  unsigned this_branch = std::distance(plane.branchIDs.begin(), std::find(plane.branchIDs.begin(), plane.branchIDs.end(), plane.branchHierarchy[i_branch][i_branch_hierarchy]));
132  if (this_branch == plane.branchIDs.size()) {
133  std::cout << "BAD: UNFOUND BRANCH ID\n";
134  continue;
135  }
136  std::cout << "BRANCH CHECK: " << plane.branchHierarchy[i_branch][i_branch_hierarchy] << " " << this_branch << " " << i_branch << std::endl;
137  for (int i = plane.angles[this_branch].size()-1; i >= 0; i--) {
138  // invalid if incomplete
139  bool is_complete = (plane.angles[this_branch][i].complete || allow_incomplete) &&
140  plane.angles[this_branch][i].dist_to_lo > min_dist && plane.angles[this_branch][i].dist_to_hi > min_dist &&
141  plane.angles[this_branch][i].angle > -99.;
142 
143  if (!is_complete) continue;
144 
145  // Check if this hit is replicated in the following branch
146  bool found_hit = false;
147  if (i_branch_hierarchy > 0) {
148  int i_check_branch_hierarchy = i_branch_hierarchy - 1;
149  unsigned chk_branch = plane.branchIDs.size();
150  int check_hitID = plane.angles[this_branch][i].hitID;
151  while (i_check_branch_hierarchy >= 0 && chk_branch == plane.branchIDs.size()) {
152  chk_branch = std::distance(plane.branchIDs.begin(), std::find(plane.branchIDs.begin(), plane.branchIDs.end(), plane.branchHierarchy[i_branch][i_check_branch_hierarchy]));
153  i_check_branch_hierarchy --;
154  }
155  if (chk_branch < plane.branchIDs.size()) {
156  for (unsigned j = 0; j < plane.angles[chk_branch].size(); j++) {
157  if (plane.angles[chk_branch][j].hitID == check_hitID) {
158  found_hit = true;
159  break;
160  }
161  }
162  }
163  }
164  // invalid if replicated
165  if (found_hit) continue;
166 
167  // Valid!
168  ret.push_back(plane.angles[this_branch][i]);
169  }
170  }
171 
172  // reverse the angles
173  std::reverse(ret.begin(), ret.end());
174 
175  // get the number of valid hits in the main branch
176  unsigned n_branch_hits = 0;
177  for (unsigned i = 0; i < plane.angles[i_branch].size(); i++) {
178  n_branch_hits += (plane.angles[i_branch][i].complete || allow_incomplete) &&
179  plane.angles[i_branch][i].dist_to_lo > min_dist && plane.angles[i_branch][i].dist_to_hi > min_dist &&
180  plane.angles[i_branch][i].angle > -99.;
181  }
182 
183  // get the index of the start of this branch
184  unsigned branch_start = ret.size() - n_branch_hits;
185  return {ret, branch_start};
186 }
187 
188 // Estimate peak height -- try the mean of the three largest points
189 float EstimateHeight(const std::vector<sbn::PCAngle> &angles, unsigned lo, unsigned hi) {
190  static const unsigned NAVG = 3;
191  static const unsigned IAVG = (NAVG - 1) / 2;
192 
193  // if the "peak is too small -- ignore
194  if (hi - lo + 1 < NAVG) return -1.;
195 
196  std::vector<float> avgd;
197  // average out the vector
198  for (unsigned i = IAVG + lo; i <= hi - IAVG; i++) {
199  float sum = 0.;
200  for (unsigned j = i - IAVG; j < i - IAVG + NAVG; j++) {
201  sum += angles[j].angle;
202  }
203 
204  avgd.push_back(sum / NAVG);
205  }
206 
207  return *std::max_element(avgd.begin(), avgd.end());
208 }
209 
210 
211 int FindMaxIndex(const std::vector<sbn::PCAngle> &angles, unsigned lo, unsigned hi) {
212  static const unsigned NAVG = 3;
213  static const unsigned IAVG = (NAVG - 1) / 2;
214 
215  // if the "peak is too small -- ignore
216  if (hi - lo + 1 < (int)NAVG) return -1.;
217 
218  std::vector<float> avgd;
219  // average out the vector
220  for (unsigned i = lo + IAVG; i <= hi - IAVG; i++) {
221  float sum = 0.;
222  for (unsigned j = i - IAVG; j < i - IAVG + NAVG; j++) {
223  sum += angles[j].angle;
224  }
225 
226  avgd.push_back(sum / NAVG);
227  }
228 
229  // index of the averaged
230  unsigned avgd_index = std::distance(avgd.begin(), std::max_element(avgd.begin(), avgd.end()));
231 
232  // convert this to index in angles list
233  unsigned angles_index = avgd_index + IAVG + lo;
234 
235  return angles_index;
236 }
237 
238 int FindFWHMHiIndex(const std::vector<sbn::PCAngle> &angles, unsigned start, unsigned end, float amp, float threshold) {
239  // handle bad amplitude
240  if (amp < 0.) return -1;
241 
242  // go below half-the amplitude
243  int halfmax_ind = -1;
244 
245  // forward track to the end of this branch
246  //
247  // We always identify hits on the last part of any branch, so we don't need
248  // to worry about forward tracking to any other part of the branch
249  for (unsigned i = end; i < angles.size(); i++) {
250  if (angles[i].angle < amp/2.) {
251  halfmax_ind = i;
252  break;
253  }
254  }
255  return halfmax_ind;
256 }
257 
258 int FindFWHMLoIndex(const std::vector<sbn::PCAngle> &angles, unsigned start, unsigned end, float amp, float threshold) {
259  // handle bad amplitude
260  if (amp < 0.) return -1;
261 
262  // backtrack
263  int halfmax_ind = -1;
264 
265  for (int i = start; i >= 0; i--) {
266  if (angles[i].angle < amp / 2.) {
267  halfmax_ind = i;
268  break;
269  }
270  }
271 
272  return halfmax_ind;
273 }
274 
275 sbn::PCAngleKinkFinder::PCAngleKinkFinder(fhicl::ParameterSet const& p)
276  : EDProducer{p},
277  fPFParticleLabel(p.get<std::string>("PFParticleLabel")),
278  fAngleLabel(p.get<std::string>("AngleLabel")),
279  fHitThreshold(p.get<float>("HitThreshold")),
280  fMinDist(p.get<float>("MinDist")),
281  fAllowIncomplete(p.get<bool>("AllowIncomplete")),
282  fPCARadius(p.get<float>("PCARadius")),
283  fFitResolution(p.get<float>("FitResolution", 0.05))
284 {
285  produces<std::vector<sbn::PCAngleKink>>();
286  produces<art::Assns<recob::PFParticle, sbn::PCAngleKink>>();
287 
288 }
289 
290 double ExpectedCosAngle(double dist_to_center, double radius, double mean_distance, double angle) {
291  if (dist_to_center >= radius) return 1.;
292 
293  double theta = angle;
294  double A = dist_to_center;
295  double B = radius - dist_to_center;
296  double R = radius;
297  double l = mean_distance;
298 
299  // double M01 = (B*(B + lB)*(3*A*(A + lA)*lB + (B*B*lA + B*(4*A - lA)*lB + 2*A*lB*lB)*cos(theta))*sin(theta))/(12.*lB*(B*lA + A*lB));
300  //double M00 = (A*(A + lA)*lB*(2*B*lA*(2*A + lA) + A*(A - lA)*lB) + B*(B + lB)*cos(theta)*(6*A*lA*(A + lA)*lB + lA*(B*B*lA + B*(4*A - lA)*lB + 2*A*lB*lB)*cos(theta)))/(12.*lA*lB*(B*lA + A*lB));
301  //double M11 = (B*(B + lB)*(B*B*lA + B*(4*A - lA)*lB + 2*A*lB*lB)*sin(theta)*sin(theta))/(12.*lB*(B*lA + A*lB));
302 
303  double M01 = (B*(B + l)*(-6*A*(A + l)*(A + B - 2*R)*sin(theta) + (3*B*(A + B)*(B + l) - 6*B*(B + l)*R + 2*(2*B + l)*R*R)*sin(2*theta)))/(24.*l*R*R);
304  double M00 = (A*(A + l)*(3*A*(A + B)*(A + l) - 6*A*(A + l)*R + 2*(2*A + l)*R*R) + B*(B + l)*cos(theta)*(-6*A*(A + l)*(A + B - 2*R) + (3*B*(A + B)*(B + l) - 6*B*(B + l)*R + 2*(2*B + l)*R*R)*cos(theta)))/(12.*l*R*R);
305  double M11 = (B*(B + l)*(3*B*(A + B)*(B + l) - 6*B*(B + l)*R + 2*(2*B + l)*R*R)*sin(theta)*sin(theta))/(12.*l*R*R);
306 
307  double trace = M00 + M11;
308  double det = M00 * M11 - M01 * M01;
309  double eigen = 0.5 * (trace + sqrt(trace * trace - 4 *det));
310  double vecA = M01;
311  double vecB = eigen - M00;
312 
313  return vecA / sqrt(vecA * vecA + vecB * vecB);
314 }
315 
316 void FitLikelihood(int &npar, double *g, double &result, double *par, int flag) {
317  double angle = par[0];
318  double res = par[1];
319  double radius = par[2];
320  double mean_distance = par[3];
321 
322  double chi2 = 0.;
323  for (unsigned i = 0; i < fFitDataX.size(); i++) {
324  double expectation = acos(ExpectedCosAngle(fFitDataX[i], radius, mean_distance, angle));
325  chi2 += (expectation - fFitDataY[i]) * (expectation - fFitDataY[i]) / (fFitDataY[i] * fFitDataY[i] * res * res);
326  }
327 
328  result = chi2 / fFitDataX.size();
329 }
330 
331 sbn::PCAngleKinkFinder::FitResult sbn::PCAngleKinkFinder::Fit(const std::vector<sbn::PCAngle> &angles, unsigned lo, unsigned hi, unsigned center, float est_angle, float est_pitch) {
332  // setup the fitdata
333  fFitDataX.clear();
334  fFitDataY.clear();
335  for (unsigned i = lo; i <= hi; i++) {
336  fFitDataX.push_back( sqrt((angles[i].hit_time_cm - angles[center].hit_time_cm) * (angles[i].hit_time_cm - angles[center].hit_time_cm) +
337  (angles[i].hit_wire_cm - angles[center].hit_wire_cm) * (angles[i].hit_wire_cm - angles[center].hit_wire_cm)) );
338  fFitDataY.push_back(angles[i].angle);
339  }
340  fFitCenter = center;
341 
342  // setup the fitter
343  TFitter fitter(5); // 5 param
344  fitter.SetFCN(&FitLikelihood);
345  fitter.SetParameter(0, "angle", est_angle, 0.1, 0, M_PI);
346  fitter.SetParameter(1, "resolution", fFitResolution, 0., fFitResolution, fFitResolution);
347  fitter.SetParameter(2, "radius", fPCARadius, 0., fPCARadius, fPCARadius);
348  fitter.SetParameter(3, "pitch", est_pitch, 0., est_pitch, est_pitch);
349  fitter.GetMinuit()->FixParameter(1);
350  fitter.GetMinuit()->FixParameter(2);
351  fitter.GetMinuit()->FixParameter(3);
352 
353  fitter.ExecuteCommand("MIGRAD", 0, 0);
354 
356 
357  ret.angle = fitter.GetParameter(0);
358  ret.pitch = fitter.GetParameter(3);
359 
360  double chi2;
361  double edm;
362  double errdef;
363  int nvpar;
364  int nparx;
365 
366  fitter.GetStats(chi2, edm, errdef, nvpar, nparx);
367  std::cout << "Fit chi2: " << chi2 << std::endl;
368  std::cout << "Fit angle: " << ret.angle << std::endl;
369 
370  ret.chi2 = chi2;
371 
372  return ret;
373 }
374 
376 {
377  // output stuff
378  std::unique_ptr<std::vector<sbn::PCAngleKink>> outKinks(new std::vector<sbn::PCAngleKink>);
379  std::unique_ptr<art::Assns<recob::PFParticle, sbn::PCAngleKink>> assn(new art::Assns<recob::PFParticle, sbn::PCAngleKink>);
380 
381  art::PtrMaker<sbn::PCAngleKink> kinkPtrMaker {evt};
382 
383  // get the PFParticle's and the associated data
384  art::Handle<std::vector<recob::PFParticle>> pfparticle_handle;
385  evt.getByLabel(fPFParticleLabel, pfparticle_handle);
386 
387  std::vector<art::Ptr<recob::PFParticle>> pfparticles;
388  art::fill_ptr_vector(pfparticles, pfparticle_handle);
389 
390  // organize the PFPlist into a map
391  std::map<unsigned, art::Ptr<recob::PFParticle>> id_to_pfp;
392  for (unsigned i = 0; i < pfparticles.size(); i++) {
393  id_to_pfp[pfparticles[i]->Self()] = pfparticles[i];
394  }
395 
396  art::FindManyP<sbn::PCAnglePlane> pfparticleAngles(pfparticles, evt, fAngleLabel);
397 
398  // iterate over each particle
399  for (unsigned i_part = 0; i_part < pfparticles.size(); i_part++) {
400  art::Ptr<recob::PFParticle> thisPFP = pfparticles[i_part];
401  std::cout << "NEW Particle: " << thisPFP->Self() << std::endl;
402  const std::vector<art::Ptr<sbn::PCAnglePlane>> &thisAngles = pfparticleAngles.at(i_part);
403 
404  if (!thisAngles.size()) continue;
405 
406  std::vector<sbn::PCAngleKink> thisKinks;
407  for (const art::Ptr<sbn::PCAnglePlane> &angle: thisAngles) {
408  for (unsigned i_branch = 0; i_branch < angle->nBranches; i_branch++) {
409  // flatten the structure of this branch
410  auto [branchAngles, branch_start] = FlattenBranch(*angle, i_branch, fAllowIncomplete, fMinDist);
411  bool in_hit = false;
412  int hit_start_ind = -1;
413  for (unsigned i_hit = branch_start; i_hit < branchAngles.size(); i_hit++) {
414  bool this_in_hit = branchAngles[i_hit].angle > fHitThreshold;
415  // New hit! set it up
416  if (!in_hit && this_in_hit) {
417  hit_start_ind = i_hit;
418  }
419  // end of hit! finish it up
420  else if (in_hit && (!this_in_hit || i_hit+1 == branchAngles.size())) {
421  float height = EstimateHeight(branchAngles, hit_start_ind, i_hit);
422 
423  int hi_hit_ind = FindFWHMHiIndex(branchAngles, hit_start_ind, i_hit, height, fHitThreshold);
424  int lo_hit_ind = FindFWHMLoIndex(branchAngles, hit_start_ind, i_hit, height, fHitThreshold);
425  // invalid hit
426  // check if hit is valid
427  if (hi_hit_ind >= 0 && lo_hit_ind >= 0) {
428  std::cout << "NEW KINK: Particle: " << thisPFP->Self() << " Branch Ind: " << i_branch << " Plane: " << angle->plane.Plane << std::endl;
429  std::cout << "EST HEIGHT: " << height << " IND LO: " << lo_hit_ind << " IND HI: " << hi_hit_ind << std::endl;
430 
431  // build out the kink information
432  int max_ind = FindMaxIndex(branchAngles, lo_hit_ind, hi_hit_ind);
433 
434  // estimate the track pitch -- distance between successive hits
435  float dist_lo_sum = 0.;
436  for (int i = lo_hit_ind; i < max_ind; i++) {
437  dist_lo_sum += sqrt((branchAngles[i].hit_time_cm - branchAngles[i+1].hit_time_cm) * (branchAngles[i].hit_time_cm - branchAngles[i+1].hit_time_cm) +
438  (branchAngles[i].hit_wire_cm - branchAngles[i+1].hit_wire_cm) * (branchAngles[i].hit_wire_cm - branchAngles[i+1].hit_wire_cm));
439  }
440  // float pitch_lo = dist_lo_sum / (max_ind - lo_hit_ind);
441 
442  float dist_hi_sum = 0.;
443  for (int i = max_ind; i < hi_hit_ind; i++) {
444  dist_hi_sum += sqrt((branchAngles[i].hit_time_cm - branchAngles[i+1].hit_time_cm) * (branchAngles[i].hit_time_cm - branchAngles[i+1].hit_time_cm) +
445  (branchAngles[i].hit_wire_cm - branchAngles[i+1].hit_wire_cm) * (branchAngles[i].hit_wire_cm - branchAngles[i+1].hit_wire_cm));
446  }
447  // float pitch_hi = dist_hi_sum / (hi_hit_ind - max_ind);
448  float pitch = (dist_lo_sum + dist_hi_sum) / (hi_hit_ind - lo_hit_ind);
449 
450 
451  sbn::PCAngleKinkFinder::FitResult fit = Fit(branchAngles, lo_hit_ind, hi_hit_ind, max_ind, height, pitch);
452 
453  const sbn::PCAngle &max = branchAngles[max_ind];
454  const sbn::PCAngle &lo = branchAngles[lo_hit_ind];
455  const sbn::PCAngle &hi = branchAngles[hi_hit_ind];
456 
457  sbn::PCAngleKink kink = BuildKink(max, lo, hi, fit, height);
458  thisKinks.push_back(kink);
459  // update the hit index to the end of this hit
460  i_hit = hi_hit_ind;
461  }
462  }
463 
464  in_hit = this_in_hit;
465  }
466  }
467  }
468 
469  // save the kinks
470  for (const sbn::PCAngleKink &k: thisKinks) {
471  outKinks->push_back(k);
472  art::Ptr<sbn::PCAngleKink> thisKinkPtr = kinkPtrMaker(outKinks->size()-1);
473  assn->addSingle(thisPFP, thisKinkPtr);
474  }
475  }
476 
477  evt.put(std::move(outKinks));
478  evt.put(std::move(assn));
479 
480 }
481 
482 DEFINE_ART_MODULE(sbn::PCAngleKinkFinder)
PCAngleKinkFinder & operator=(PCAngleKinkFinder const &)=delete
then if[["$THISISATEST"==1]]
Definition: neoSmazza.sh:95
FitResult Fit(const std::vector< sbn::PCAngle > &angles, unsigned lo, unsigned hi, unsigned center, float est_angle, float est_pitch)
then echo unknown compiler flag
PCAngleKinkFinder(fhicl::ParameterSet const &p)
Declaration of signal hit object.
pdgs p
Definition: selectors.fcl:22
int FindFWHMLoIndex(const std::vector< sbn::PCAngle > &angles, unsigned start, unsigned end, float amp, float threshold)
int FindMaxIndex(const std::vector< sbn::PCAngle > &angles, unsigned lo, unsigned hi)
BEGIN_PROLOG g
static std::vector< float > fFitDataY
std::tuple< std::vector< sbn::PCAngle >, unsigned > FlattenBranch(const sbn::PCAnglePlane &plane, unsigned i_branch, bool allow_incomplete, float min_dist)
M::value_type trace(const M &m)
Access the description of detector geometry.
void FitLikelihood(int &npar, double *g, double &result, double *par, int flag)
double distance(geo::Point_t const &point, CathodeDesc_t const &cathode)
Returns the distance of a point from the cathode.
static unsigned fFitCenter
auto end(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:585
Declaration of cluster object.
double ExpectedCosAngle(double dist_to_center, double radius, double mean_distance, double angle)
do i e
Declaration of basic channel signal object.
finds tracks best matching by angle
static std::vector< float > fFitDataX
TCEvent evt
Definition: DataStructs.cxx:8
pdgs k
Definition: selectors.fcl:22
void produce(art::Event &e) override
float A
Definition: dedx.py:137
sbn::PCAngleKink BuildKink(const sbn::PCAngle &max, const sbn::PCAngle &lo, const sbn::PCAngle &hi, const sbn::PCAngleKinkFinder::FitResult &fit, float est_angle)
float EstimateHeight(const std::vector< sbn::PCAngle > &angles, unsigned lo, unsigned hi)
int FindFWHMHiIndex(const std::vector< sbn::PCAngle > &angles, unsigned start, unsigned end, float amp, float threshold)
art framework interface to geometry description
BEGIN_PROLOG could also be cout