LArSoft  v09_93_00
Liquid Argon Software toolkit - https://larsoft.org/
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
ClusterCrawlerAlg.cxx
Go to the documentation of this file.
1 
12 // C/C++ standard libraries
13 #include <algorithm> // std::fill(), std::find(), std::sort()...
14 #include <cmath>
15 #include <iomanip>
16 #include <iostream>
17 
18 // framework libraries
20 #include "fhiclcpp/ParameterSet.h"
22 
23 // LArSoft libraries
26 #include "larcorealg/CoreUtils/NumericUtils.h" // util::absDiff()
35 #include "larevt/CalibrationDBI/Interface/ChannelStatusProvider.h"
36 #include "larevt/CalibrationDBI/Interface/ChannelStatusService.h"
38 
39 namespace {
40  struct CluLen {
41  int index;
42  int length;
43  };
44 
45  bool greaterThan(CluLen c1, CluLen c2)
46  {
47  return c1.length > c2.length;
48  }
49 }
50 
51 namespace cluster {
52 
53  //------------------------------------------------------------------------------
54  ClusterCrawlerAlg::ClusterCrawlerAlg(fhicl::ParameterSet const& pset)
55  {
56  fNumPass = pset.get<unsigned short>("NumPass", 0);
57  fMaxHitsFit = pset.get<std::vector<unsigned short>>("MaxHitsFit");
58  fMinHits = pset.get<std::vector<unsigned short>>("MinHits");
59  fNHitsAve = pset.get<std::vector<unsigned short>>("NHitsAve");
60  fChgCut = pset.get<std::vector<float>>("ChgCut");
61  fChiCut = pset.get<std::vector<float>>("ChiCut");
62  fMaxWirSkip = pset.get<std::vector<unsigned short>>("MaxWirSkip");
63  fMinWirAfterSkip = pset.get<std::vector<unsigned short>>("MinWirAfterSkip");
64  fKinkChiRat = pset.get<std::vector<float>>("KinkChiRat");
65  fKinkAngCut = pset.get<std::vector<float>>("KinkAngCut");
66  fDoMerge = pset.get<std::vector<bool>>("DoMerge");
67  fTimeDelta = pset.get<std::vector<float>>("TimeDelta");
68  fMergeChgCut = pset.get<std::vector<float>>("MergeChgCut");
69  fFindVertices = pset.get<std::vector<bool>>("FindVertices");
70  fLACrawl = pset.get<std::vector<bool>>("LACrawl");
71  fMinAmp = pset.get<std::vector<float>>("MinAmp", {5, 5, 5});
72  fChgNearWindow = pset.get<float>("ChgNearWindow");
73  fChgNearCut = pset.get<float>("ChgNearCut");
74 
75  fChkClusterDS = pset.get<bool>("ChkClusterDS", false);
76  fVtxClusterSplit = pset.get<bool>("VtxClusterSplit", false);
77  fFindStarVertices = pset.get<bool>("FindStarVertices", false);
78  if (pset.has_key("HammerCluster")) {
79  mf::LogWarning("CC")
80  << "fcl setting HammerCluster is replaced by FindHammerClusters. Ignoring...";
81  }
82  fFindHammerClusters = pset.get<bool>("FindHammerClusters", false);
83  fKillGarbageClusters = pset.get<float>("KillGarbageClusters", 0);
84  fRefineVertexClusters = pset.get<bool>("RefineVertexClusters", false);
85  fHitErrFac = pset.get<float>("HitErrFac", 0.2);
86  fHitMinAmp = pset.get<float>("HitMinAmp", 0.2);
87  fClProjErrFac = pset.get<float>("ClProjErrFac", 4);
88  fMinHitFrac = pset.get<float>("MinHitFrac", 0.6);
89 
90  fLAClusAngleCut = pset.get<float>("LAClusAngleCut", 45);
91  fLAClusMaxHitsFit = pset.get<unsigned short>("LAClusMaxHitsFit");
92  fMergeAllHits = pset.get<bool>("MergeAllHits", false);
93  fHitMergeChiCut = pset.get<float>("HitMergeChiCut", 2.5);
94  fMergeOverlapAngCut = pset.get<float>("MergeOverlapAngCut");
95  fAllowNoHitWire = pset.get<unsigned short>("AllowNoHitWire", 0);
96  fVertex2DCut = pset.get<float>("Vertex2DCut", 5);
97  fVertex2DWireErrCut = pset.get<float>("Vertex2DWireErrCut", 5);
98  fVertex3DCut = pset.get<float>("Vertex3DCut", 5);
99 
100  fDebugPlane = pset.get<int>("DebugPlane", -1);
101  fDebugWire = pset.get<int>("DebugWire", -1);
102  fDebugHit = pset.get<int>("DebugHit", -1);
103 
104  // some error checking
105  bool badinput = false;
106  if (fNumPass > fMaxHitsFit.size()) badinput = true;
107  if (fNumPass > fMinHits.size()) badinput = true;
108  if (fNumPass > fNHitsAve.size()) badinput = true;
109  if (fNumPass > fChgCut.size()) badinput = true;
110  if (fNumPass > fChiCut.size()) badinput = true;
111  if (fNumPass > fMaxWirSkip.size()) badinput = true;
112  if (fNumPass > fMinWirAfterSkip.size()) badinput = true;
113  if (fNumPass > fKinkChiRat.size()) badinput = true;
114  if (fNumPass > fKinkAngCut.size()) badinput = true;
115  if (fNumPass > fDoMerge.size()) badinput = true;
116  if (fNumPass > fTimeDelta.size()) badinput = true;
117  if (fNumPass > fMergeChgCut.size()) badinput = true;
118  if (fNumPass > fFindVertices.size()) badinput = true;
119  if (fNumPass > fLACrawl.size()) badinput = true;
120 
121  if (badinput)
123  << "ClusterCrawlerAlg: Bad input from fcl file";
124 
125  } // reconfigure
126 
127  // used for sorting hits on wires
128  bool SortByLowHit(unsigned int i, unsigned int j)
129  {
130  return i > j;
131  }
132 
133  bool ClusterCrawlerAlg::SortByMultiplet(recob::Hit const& a, recob::Hit const& b)
134  {
135  // compare the wire IDs first:
136  int cmp_res = a.WireID().cmp(b.WireID());
137  if (cmp_res != 0) return cmp_res < 0; // order is decided, unless equal
138  // decide by start time
139  if (a.StartTick() != b.StartTick()) return a.StartTick() < b.StartTick();
140  // if still undecided, resolve by local index
141  return a.LocalIndex() < b.LocalIndex(); // if still unresolved, it's a bug!
142  } // ClusterCrawlerAlg::SortByMultiplet()
143 
144  //------------------------------------------------------------------------------
145  void ClusterCrawlerAlg::ClearResults()
146  {
147  fHits.clear();
148  tcl.clear();
149  vtx.clear();
150  vtx3.clear();
151  inClus.clear();
152  } // ClusterCrawlerAlg::ClearResults()
153 
154  //------------------------------------------------------------------------------
155  void ClusterCrawlerAlg::CrawlInit()
156  {
157  prt = false;
158  vtxprt = false;
159  NClusters = 0;
160  clBeginSlp = 0;
161  clBeginSlpErr = 0;
162  clBeginTim = 0;
163  clBeginWir = 0;
164  clBeginChg = 0;
165  clBeginChgNear = 0;
166  clEndSlp = 0;
167  clEndSlpErr = 0;
168  clEndTim = 0;
169  clEndWir = 0;
170  clEndChg = 0;
171  clEndChgNear = 0;
172  clChisq = 0;
173  clStopCode = 0;
174  clProcCode = 0;
175  fFirstWire = 0;
176  fLastWire = 0;
177  fAveChg = 0.;
178  fChgSlp = 0.;
179  pass = 0;
180  fScaleF = 0;
181  WireHitRange.clear();
182 
183  ClearResults();
184  }
185 
186  //------------------------------------------------------------------------------
187  void ClusterCrawlerAlg::ClusterInit()
188  {
189  fcl2hits.clear();
190  chifits.clear();
191  hitNear.clear();
192  chgNear.clear();
193  fAveChg = -1.;
194  fAveHitWidth = -1;
195  clEndChg = -1.;
196  clStopCode = 0;
197  clProcCode = pass;
198  }
199 
200  //------------------------------------------------------------------------------
201  void ClusterCrawlerAlg::RunCrawler(detinfo::DetectorClocksData const& clock_data,
202  detinfo::DetectorPropertiesData const& det_prop,
203  std::vector<recob::Hit> const& srchits)
204  {
205  // Run the ClusterCrawler algorithm - creating seed clusters and crawling upstream.
206 
207  CrawlInit();
208 
209  fHits = srchits; // plain copy of the sources; it's the base of our hit result
210 
211  if (fHits.size() < 3) return;
212  if (fHits.size() > UINT_MAX) {
213  mf::LogWarning("CC") << "Too many hits for ClusterCrawler " << fHits.size();
214  return;
215  }
216 
217  // don't do anything...
218  if (fNumPass == 0) return;
219 
220  // sort it as needed;
221  // that is, sorted by wire ID number,
222  // then by start of the region of interest in time, then by the multiplet
223  std::sort(fHits.begin(), fHits.end(), &SortByMultiplet);
224 
225  inClus.resize(fHits.size());
226  mergeAvailable.resize(fHits.size());
227  for (unsigned int iht = 0; iht < inClus.size(); ++iht) {
228  inClus[iht] = 0;
229  mergeAvailable[iht] = false;
230  }
231 
232  // FIXME (KJK): The 'cstat', 'tpc', and 'plane' class variables should be removed.
233  for (geo::TPCID const& tpcid : geom->Iterate<geo::TPCID>()) {
234  cstat = tpcid.Cryostat;
235  tpc = tpcid.TPC;
236  for (geo::PlaneID const& planeid : geom->Iterate<geo::PlaneID>(tpcid)) {
237  plane = planeid.Plane;
238  WireHitRange.clear();
239  // define a code to ensure clusters are compared within the same plane
240  clCTP = EncodeCTP(tpcid.Cryostat, tpcid.TPC, planeid.Plane);
241  cstat = tpcid.Cryostat;
242  tpc = tpcid.TPC;
243  // fill the WireHitRange vector with first/last hit on each wire
244  // dead wires and wires with no hits are flagged < 0
245  GetHitRange(clCTP);
246 
247  // sanity check
248  if (WireHitRange.empty() || (fFirstWire == fLastWire)) continue;
249  raw::ChannelID_t channel = fHits[fFirstHit].Channel();
250  // get the scale factor to convert dTick/dWire to dX/dU. This is used
251  // to make the kink and merging cuts
252  float wirePitch = geom->WirePitch(geom->View(channel));
253  float tickToDist = det_prop.DriftVelocity(det_prop.Efield(), det_prop.Temperature());
254  tickToDist *= 1.e-3 * sampling_rate(clock_data); // 1e-3 is conversion of 1/us to 1/ns
255  fScaleF = tickToDist / wirePitch;
256  // convert Large Angle Cluster crawling cut to a slope cut
257  if (fLAClusAngleCut > 0)
258  fLAClusSlopeCut = std::tan(3.142 * fLAClusAngleCut / 180.) / fScaleF;
259  fMaxTime = det_prop.NumberTimeSamples();
260  fNumWires = geom->Nwires(planeid);
261  // look for clusters
262  if (fNumPass > 0) ClusterLoop();
263  } // plane
264  if (fVertex3DCut > 0) {
265  // Match vertices in 3 planes
266  VtxMatch(det_prop, tpcid);
267  Vtx3ClusterMatch(det_prop, tpcid);
268  if (fFindHammerClusters) FindHammerClusters(det_prop);
269  // split clusters using 3D vertices
270  Vtx3ClusterSplit(det_prop, tpcid);
271  }
272  if (fDebugPlane >= 0) {
273  mf::LogVerbatim("CC") << "Clustering done in TPC ";
274  PrintClusters();
275  }
276  } // for all tpcs
277 
278  // clean up
279  WireHitRange.clear();
280  fcl2hits.clear();
281  chifits.clear();
282  hitNear.clear();
283  chgNear.clear();
284 
285  // remove the hits that have become obsolete
286  RemoveObsoleteHits();
287 
288  } // RunCrawler
289 
291  void ClusterCrawlerAlg::ClusterLoop()
292  {
293  // looks for seed clusters in a plane and crawls along a trail of hits
294 
295  unsigned int nHitsUsed = 0, iwire, jwire, kwire;
296  bool AllDone = false, SigOK = false, HitOK = false;
297  unsigned int ihit, jhit;
298  for (unsigned short thispass = 0; thispass < fNumPass; ++thispass) {
299  pass = thispass;
300  // look for a starting cluster that spans a block of wires
301  unsigned int span = 3;
302  if (fMinHits[pass] < span) span = fMinHits[pass];
303  for (iwire = fLastWire; iwire > fFirstWire + span; --iwire) {
304  // skip bad wires or no hits on the wire
305  if (WireHitRange[iwire].first < 0) continue;
306  auto ifirsthit = (unsigned int)WireHitRange[iwire].first;
307  auto ilasthit = (unsigned int)WireHitRange[iwire].second;
308  for (ihit = ifirsthit; ihit < ilasthit; ++ihit) {
309  bool ClusterAdded = false;
310  recob::Hit const& hit = fHits[ihit];
311  // skip used hits
312  if (ihit > fHits.size() - 1) {
313  mf::LogError("CC") << "ClusterLoop bad ihit " << ihit << " fHits size " << fHits.size();
314  return;
315  }
316  // skip used and obsolete hits
317  if (inClus[ihit] != 0) continue;
318  // skip narrow hits
319  if (fHits[ihit].PeakAmplitude() < fHitMinAmp) continue;
320  if ((iwire + 1) < span) continue;
321  jwire = iwire - span + 1;
322  if (prt)
323  mf::LogVerbatim("CC") << "Found debug hit " << PrintHit(ihit) << " on pass" << pass;
324  // skip if good wire and no hit
325  if (WireHitRange[jwire].first == -2) continue;
326  if (WireHitRange[jwire].first == -1) {
327  // Found a dead jwire. Keep looking upstream until we find a good wire
328  unsigned int nmissed = 0;
329  while (WireHitRange[jwire].first == -1 && jwire > 1 && nmissed < fMaxWirSkip[pass]) {
330  --jwire;
331  ++nmissed;
332  }
333  if (prt)
334  mf::LogVerbatim("CC")
335  << " new jwire " << jwire << " dead? " << WireHitRange[jwire].first;
336  if (WireHitRange[jwire].first < 0) continue;
337  } // dead jwire
338  // Find the hit on wire jwire that best matches a line between
339  // a nearby vertex and hit ihit. No constraint if useHit < 0
340  unsigned int useHit = 0;
341  bool doConstrain = false;
342  VtxConstraint(iwire, ihit, jwire, useHit, doConstrain);
343  unsigned int jfirsthit = (unsigned int)WireHitRange[jwire].first;
344  unsigned int jlasthit = (unsigned int)WireHitRange[jwire].second;
345  if (jfirsthit > fHits.size() - 1 || jfirsthit > fHits.size() - 1)
347  << "ClusterLoop jwire " << jwire << " bad firsthit " << jfirsthit << " lasthit "
348  << jlasthit << " fhits size " << fHits.size();
349  for (jhit = jfirsthit; jhit < jlasthit; ++jhit) {
350  if (jhit > fHits.size() - 1)
352  << "ClusterLoop bad jhit " << jhit << " firsthit " << jfirsthit << " lasthit "
353  << jlasthit << " fhits size" << fHits.size();
354  // Constraint?
355  if (doConstrain && jhit != useHit) continue;
356  recob::Hit const& other_hit = fHits[jhit];
357  // skip used and obsolete hits
358  if (inClus[jhit] != 0) continue;
359  // skip narrow hits
360  if (fHits[jhit].PeakAmplitude() < fHitMinAmp) continue;
361  // start a cluster with these two hits
362  ClusterInit();
363  fcl2hits.push_back(ihit);
364  chifits.push_back(0.);
365  hitNear.push_back(0);
366  chgNear.push_back(0); // These will be defined if the cluster survives the cuts
367  // enter the jhit
368  fcl2hits.push_back(jhit);
369  chifits.push_back(0.);
370  hitNear.push_back(0);
371  chgNear.push_back(0);
372  clLA = false;
373  clpar[0] = other_hit.PeakTime();
374  clpar[1] = (hit.PeakTime() - other_hit.PeakTime()) / (iwire - jwire);
375  // increase slope errors for large angle clusters
376  clparerr[1] = 0.2 * std::abs(clpar[1]);
377  clpar[2] = fHits[jhit].WireID().Wire;
378  clChisq = 0;
379  // now look for hits to add on the intervening wires
380  bool clok = false;
381  for (kwire = jwire + 1; kwire < iwire; ++kwire) {
382  // ensure this cluster doesn't cross a vertex
383  if (CrawlVtxChk(kwire)) {
384  clok = false;
385  break;
386  }
387  AddHit(kwire, HitOK, SigOK);
388  if (prt)
389  mf::LogVerbatim("CC") << " HitOK " << HitOK << " clChisq " << clChisq << " cut "
390  << fChiCut[pass] << " ClusterHitsOK " << ClusterHitsOK(-1);
391  // No hit found
392  if (!HitOK) break;
393  // This should be a really good chisq
394  if (clChisq > 2) break;
395  // hit widths & overlap not consistent
396  if (!ClusterHitsOK(-1)) continue;
397  clok = true;
398  }
399  // drop it?
400  if (!clok) continue;
401  prt = (fDebugPlane == (int)plane && (int)iwire == fDebugWire &&
402  std::abs((int)hit.PeakTime() - fDebugHit) < 20);
403  if (prt)
404  mf::LogVerbatim("CC")
405  << "ADD >>>>> Starting cluster with hits " << PrintHit(fcl2hits[0]) << " "
406  << PrintHit(fcl2hits[1]) << " " << PrintHit(fcl2hits[2]) << " on pass " << pass;
407  // save the cluster begin info
408  clBeginWir = iwire;
409  clBeginTim = hit.PeakTime();
410  clBeginSlp = clpar[1];
411  // don't do a small angle crawl if the cluster slope is too large
412  // and Large Angle crawling is NOT requested on this pass
413  if (!fLACrawl[pass] && std::abs(clBeginSlp) > fLAClusSlopeCut) continue;
414  // See if we are trying to start a cluster between a vertex
415  // and a cluster that is associated to that vertex. If so, skip it
416  if (CrawlVtxChk2()) continue;
417  clBeginSlpErr = clparerr[1];
418  clBeginChg = 0;
419  // Calculate the average width
420  fAveHitWidth = 0;
421  for (unsigned short kk = 0; kk < fcl2hits.size(); ++kk) {
422  fAveHitWidth += fHits[fcl2hits[kk]].EndTick() - fHits[fcl2hits[kk]].StartTick();
423  }
424  fAveHitWidth /= (float)fcl2hits.size();
425  // decide whether to crawl a large angle cluster. Requirements are:
426  // 1) the user has set the LACluster angle cut > 0, AND
427  // 2) the cluster slope exceeds the cut
428  // Note that if condition 1 is met, normal cluster crawling is done
429  // only if the slope is less than the cut
430  if (fLACrawl[pass] && fLAClusSlopeCut > 0) {
431  // LA cluster crawling requested
432  if (std::abs(clBeginSlp) > fLAClusSlopeCut) { LACrawlUS(); }
433  else {
434  CrawlUS();
435  } // std::abs(clBeginSlp) > fLAClusAngleCut
436  }
437  else {
438  // allow clusters of any angle
439  CrawlUS();
440  } // fLAClusSlopeCut > 0
441  if (fcl2hits.size() >= fMinHits[pass]) {
442  // it's long enough so save it
443  clEndSlp = clpar[1]; // save the slope at the end
444  clEndSlpErr = clparerr[1];
445  // store the cluster
446  if (!TmpStore()) {
447  mf::LogError("CC") << "Failed to store cluster in plane " << plane;
448  continue;
449  }
450  ClusterAdded = true;
451  nHitsUsed += fcl2hits.size();
452  AllDone = (nHitsUsed == fHits.size());
453  break;
454  }
455  else {
456  // abandon it
457  if (prt) mf::LogVerbatim("CC") << "ClusterLoop: dropped the cluster";
458  }
459  if (ClusterAdded || AllDone) break;
460  } // jhit
461  if (AllDone) break;
462  } // ihit
463  if (AllDone) break;
464  } // iwire
465 
466  // try to merge clusters
467  if (fDoMerge[pass]) ChkMerge();
468  // form 2D vertices
469  if (fFindVertices[pass]) FindVertices();
470 
471  if (AllDone) break;
472 
473  } // pass
474 
475  // Kill Garbage clusters
476  if (fKillGarbageClusters > 0 && !tcl.empty()) KillGarbageClusters();
477  // Merge overlapping clusters
478  if (fMergeOverlapAngCut > 0) MergeOverlap();
479  // Check the DS end of clusters
480  if (fChkClusterDS) ChkClusterDS();
481  // split clusters using vertices
482  if (fVtxClusterSplit) {
483  bool didSomething = VtxClusterSplit();
484  // iterate once to handle the case where a cluster crosses two vertices
485  if (didSomething) VtxClusterSplit();
486  }
487  // Look for 2D vertices with star topology - short, back-to-back clusters
488  if (fFindStarVertices) FindStarVertices();
489 
490  if (fDebugPlane == (int)plane) {
491  mf::LogVerbatim("CC") << "Clustering done in plane " << plane;
492  PrintClusters();
493  }
494 
495  CheckHitClusterAssociations();
496 
497  } // ClusterLoop()
498 
500  void ClusterCrawlerAlg::KillGarbageClusters()
501  {
502  // Ghost Clusters:
503 
504  if (tcl.size() < 2) return;
505 
506  unsigned short icl, jcl;
507  // This code preferentially selects icl clusters that were
508  // reconstructed on an early pass (unless they were split)
509  std::vector<float> iHits, jHits;
510  unsigned int indx;
511  // find the average hit width on the first pass and construct
512  // a hit separation cut
513  float sepcut = 0, iFrac, jFrac;
514  bool first = true;
515  unsigned short iLoIndx, jLoIndx, olapSize, iop, ii, jj;
516  unsigned short nclose;
517  float iChg, jChg;
518  // vecrtor of clusters that will be killed after all is done
519  std::vector<unsigned short> killMe;
520  bool doKill;
521  for (icl = 0; icl < tcl.size() - 1; ++icl) {
522  if (tcl[icl].ID < 0) continue;
523  if (tcl[icl].CTP != clCTP) continue;
524  // put the hits into a wire ordered vector
525  iHits.clear();
526  // initialize to a large positive value
527  iHits.resize(tcl[icl].BeginWir - tcl[icl].EndWir + 1, 1000);
528  iChg = 0;
529  for (auto iht : tcl[icl].tclhits) {
530  indx = fHits[iht].WireID().Wire - tcl[icl].EndWir;
531  if (indx > iHits.size() - 1) {
532  mf::LogWarning("CC") << "KillGarbageClusters: icl ID " << tcl[icl].ID << " Bad indx "
533  << indx << " " << iHits.size() << "\n";
534  continue;
535  }
536  iHits[indx] = fHits[iht].PeakTime();
537  iChg += fHits[iht].Integral();
538  if (first) sepcut += fHits[iht].RMS();
539  } // iht
540  if (first) {
541  sepcut /= (float)tcl[icl].tclhits.size();
542  // clusters are consider ghost candidates if many hits
543  // are within sepcut of each other on the same wire
544  sepcut *= 10;
545  first = false;
546  } // first
547  for (jcl = icl + 1; jcl < tcl.size(); ++jcl) {
548  if (tcl[jcl].ID < 0) continue;
549  if (tcl[jcl].CTP != clCTP) continue;
550  // ignore if there is no overlap
551  if (tcl[icl].BeginWir < tcl[jcl].EndWir) continue;
552  if (tcl[icl].EndWir > tcl[jcl].BeginWir) continue;
553  // require similar angle
554  if (std::abs(tcl[icl].BeginAng - tcl[jcl].BeginAng) > fKillGarbageClusters) continue;
555  // find the overlap region
556  if (tcl[icl].EndWir < tcl[jcl].EndWir) {
557  // icl E-----------....
558  // jcl E----------....
559  // olap xxxxxxxxxx...
560  iLoIndx = tcl[jcl].EndWir - tcl[icl].EndWir;
561  jLoIndx = 0;
562  if (tcl[icl].BeginWir < tcl[jcl].BeginWir) {
563  // icl E-----------B
564  // jcl E------------B
565  // olap xxxxxxxxxx
566  olapSize = tcl[icl].BeginWir - tcl[jcl].EndWir + 1;
567  }
568  else {
569  // icl E-----------B
570  // jcl E-----B
571  // olap xxxxxxx
572  olapSize = tcl[jcl].BeginWir - tcl[jcl].EndWir + 1;
573  } // iBegin
574  } // iEnd < jEnd
575  else {
576  // icl E-----------....
577  // jcl E----------....
578  // olap xxxxxxxxxx...
579  iLoIndx = 0;
580  jLoIndx = tcl[icl].EndWir - tcl[icl].EndWir;
581  if (tcl[icl].BeginWir < tcl[jcl].BeginWir) {
582  // icl E-----B
583  // jcl E-----------B
584  // olap xxxxxxx
585  olapSize = tcl[icl].BeginWir - tcl[icl].EndWir + 1;
586  }
587  else {
588  // icl E-----------B
589  // jcl E----------B
590  // olap xxxxxxxxx
591  olapSize = tcl[jcl].BeginWir - tcl[icl].EndWir + 1;
592  }
593  } // iEnd > jEnd
594  jHits.clear();
595  // initialize to a large negative value
596  jHits.resize(tcl[jcl].BeginWir - tcl[jcl].EndWir + 1, -1000);
597  jChg = 0;
598  for (auto jht : tcl[jcl].tclhits) {
599  indx = fHits[jht].WireID().Wire - tcl[jcl].EndWir;
600  if (indx > jHits.size() - 1) {
601  mf::LogWarning("CC") << "KillGarbageClusters: jcl ID " << tcl[jcl].ID << " Bad indx "
602  << indx << " " << jHits.size() << "\n";
603  continue;
604  }
605  jHits[indx] = fHits[jht].PeakTime();
606  jChg += fHits[jht].Integral();
607  } // jht
608  // count the number of close hits
609  nclose = 0;
610  for (iop = 0; iop < olapSize; ++iop) {
611  ii = iLoIndx + iop;
612  if (ii > iHits.size() - 1) continue;
613  jj = jLoIndx + iop;
614  if (jj > jHits.size() - 1) continue;
615  if (std::abs(iHits[ii] - jHits[jj]) < sepcut) ++nclose;
616  } // iop
617  iFrac = (float)nclose / (float)iHits.size();
618  jFrac = (float)nclose / (float)jHits.size();
619  if (iFrac < 0.5 && jFrac < 0.5) continue;
620  doKill = (iFrac < jFrac && iChg > jChg);
621  if (doKill) killMe.push_back(jcl);
622  } // jcl
623  } // icl
624 
625  if (killMe.size() == 0) return;
626  for (auto icl : killMe) {
627  // killing time
628  if (tcl[icl].ID < 0) continue;
629  tcl[icl].ProcCode = 666;
630  MakeClusterObsolete(icl);
631  } // icl
632 
633  } // KillGarbageClusters
634 
637  {
638  // Tries to merge overlapping clusters schematically shown below. The minimal condition is that both
639  // clusters have a length of at least minLen wires with minOvrLap of the minLen wires in the overlap region
640  // End Begin
641  // icl ------
642  // jcl ------
643  // End Begin
644  // This can occur when tracking cosmic rays through delta ray showers.
645  // If successfull the hits in clusters icl and jcl are merged into one long cluster
646  // and a short cluster if there are sufficient remaining hits.
647  // This routine changes the "pass" variable to define cuts and should NOT be used inside any pass loops
648 
649  unsigned short icl, jcl;
650 
651  bool chkprt = (fDebugWire == 666);
652  if (chkprt) mf::LogVerbatim("CC") << "Inside MergeOverlap using clCTP " << clCTP;
653 
654  unsigned short minLen = 6;
655  unsigned short minOvrLap = 2;
656 
657  // use the loosest dTick cut which is probably the last one
658  float maxDTick = fTimeDelta[fTimeDelta.size() - 1];
659 
660  unsigned int overlapSize, ii, indx, bWire, eWire;
661  unsigned int iht, jht;
662  float dang, prtime, dTick;
663  for (icl = 0; icl < tcl.size(); ++icl) {
664  if (tcl[icl].ID < 0) continue;
665  if (tcl[icl].CTP != clCTP) continue;
666  prt = chkprt && fDebugPlane == (int)clCTP;
667  if (tcl[icl].BeginVtx >= 0) continue;
668  if (tcl[icl].tclhits.size() < minLen) continue;
669  for (jcl = 0; jcl < tcl.size(); ++jcl) {
670  if (icl == jcl) continue;
671  if (tcl[jcl].ID < 0) continue;
672  if (tcl[jcl].CTP != clCTP) continue;
673  if (tcl[jcl].EndVtx >= 0) continue;
674  if (tcl[jcl].tclhits.size() < minLen) continue;
675  // icl Begin is not far enough DS from the end of jcl
676  if (tcl[icl].BeginWir < tcl[jcl].EndWir + minOvrLap) continue;
677  // and it doesn't end within the wire boundaries of jcl
678  if (tcl[icl].BeginWir > tcl[jcl].BeginWir - minOvrLap) continue;
679  // jcl End isn't far enough US from the end of icl
680  if (tcl[jcl].EndWir < tcl[icl].EndWir + minOvrLap) continue;
681  dang = std::abs(tcl[icl].BeginAng - tcl[jcl].EndAng);
682  if (prt)
683  mf::LogVerbatim("CC") << "MergeOverlap icl ID " << tcl[icl].ID << " jcl ID "
684  << tcl[jcl].ID << " dang " << dang;
685  if (dang > 0.5) continue;
686  overlapSize = tcl[icl].BeginWir - tcl[jcl].EndWir + 1;
687  eWire = tcl[jcl].EndWir;
688  bWire = tcl[icl].BeginWir;
689  if (prt)
690  mf::LogVerbatim("CC") << " Candidate icl ID " << tcl[icl].ID << " " << tcl[icl].EndWir
691  << "-" << tcl[icl].BeginWir << " jcl ID " << tcl[jcl].ID << " "
692  << tcl[jcl].EndWir << "-" << tcl[jcl].BeginWir << " overlapSize "
693  << overlapSize << " bWire " << bWire << " eWire " << eWire;
694  iht = 0;
695  jht = 0;
696  for (ii = 0; ii < tcl[icl].tclhits.size(); ++ii) {
697  iht = tcl[icl].tclhits[ii];
698  if (fHits[iht].WireID().Wire < eWire) break;
699  } // ii
700  // require that the ends be similar in time
701  dTick = std::abs(fHits[iht].PeakTime() - tcl[jcl].EndTim);
702  if (dTick > maxDTick) continue;
703  if (prt)
704  mf::LogVerbatim("CC") << " dTick icl iht time " << PrintHit(iht) << " jcl EndTim "
705  << tcl[jcl].EndTim << " dTick " << dTick;
706  for (ii = 0; ii < tcl[jcl].tclhits.size(); ++ii) {
707  jht = tcl[jcl].tclhits[tcl[jcl].tclhits.size() - ii - 1];
708  if (fHits[jht].WireID().Wire > bWire) break;
709  } // ii
710  dTick = std::abs(fHits[jht].PeakTime() - tcl[icl].BeginTim);
711  if (dTick > maxDTick) continue;
712  if (prt)
713  mf::LogVerbatim("CC") << " dTick jcl jht time " << PrintHit(jht) << " icl BeginTim "
714  << tcl[icl].BeginTim << " dTick " << dTick;
715  // Calculate the line between iht and jht
716  clpar[0] = fHits[iht].PeakTime();
717  clpar[2] = fHits[iht].WireID().Wire;
718  clpar[1] = (fHits[jht].PeakTime() - fHits[iht].PeakTime()) /
719  ((float)fHits[jht].WireID().Wire - clpar[2]);
720  // put the hits in the overlap region into a vector if they are close to the line
721  std::vector<unsigned int> oWireHits(overlapSize, INT_MAX);
722  std::vector<float> delta(overlapSize, maxDTick);
723  for (ii = 0; ii < tcl[icl].tclhits.size(); ++ii) {
724  iht = tcl[icl].tclhits[ii];
725  if (fHits[iht].WireID().Wire < eWire) break;
726  prtime = clpar[0] + clpar[1] * ((float)fHits[iht].WireID().Wire - clpar[2]);
727  dTick = std::abs(fHits[iht].PeakTime() - prtime);
728  indx = fHits[iht].WireID().Wire - eWire;
729  if (dTick > delta[indx]) continue;
730  delta[indx] = dTick;
731  oWireHits[indx] = iht;
732  } // ii
733  // enter the second set of hits
734  for (ii = 0; ii < tcl[jcl].tclhits.size(); ++ii) {
735  jht = tcl[jcl].tclhits[tcl[jcl].tclhits.size() - ii - 1];
736  if (fHits[jht].WireID().Wire > bWire) break;
737  prtime = clpar[0] + clpar[1] * ((float)fHits[jht].WireID().Wire - clpar[2]);
738  dTick = std::abs(fHits[jht].PeakTime() - prtime);
739  indx = fHits[jht].WireID().Wire - eWire;
740  if (dTick > delta[indx]) continue;
741  delta[indx] = dTick;
742  oWireHits[indx] = jht;
743  } // ii
744  // stuff them into fcl2hits
745  fcl2hits.clear();
746  for (ii = 0; ii < oWireHits.size(); ++ii) {
747  if (oWireHits[ii] == INT_MAX) continue;
748  iht = oWireHits[ii];
749  fcl2hits.push_back(iht);
750  if (prt) mf::LogVerbatim("CC") << "hit " << PrintHit(iht);
751  } // ii
752  if (fcl2hits.size() < 0.5 * overlapSize) continue;
753  if (fcl2hits.size() < 3) continue;
754  std::sort(fcl2hits.begin(), fcl2hits.end(), SortByLowHit);
755  FitCluster();
756  if (prt)
757  mf::LogVerbatim("CC") << " Overlap size " << overlapSize << " fit chisq " << clChisq
758  << " nhits " << fcl2hits.size();
759  if (clChisq > 20) continue;
760  // save these hits so we can paste them back on fcl2hits when merging
761  std::vector<unsigned int> oHits = fcl2hits;
762  // prepare to make a new cluster
763  TmpGet(jcl);
764  // resize it
765  unsigned short jclNewSize;
766  for (jclNewSize = 0; jclNewSize < fcl2hits.size(); ++jclNewSize) {
767  iht = fcl2hits[jclNewSize];
768  if (fHits[iht].WireID().Wire <= bWire) break;
769  } // jclNewSize
770  if (prt) {
771  mf::LogVerbatim("CC") << "jcl old size " << fcl2hits.size() << " newSize " << jclNewSize;
772  iht = fcl2hits[fcl2hits.size() - 1];
773  unsigned int iiht = fcl2hits[jclNewSize - 1];
774  mf::LogVerbatim("CC") << "jcl old last wire " << fHits[iht].WireID().Wire
775  << " After resize last wire " << fHits[iiht].WireID().Wire;
776  }
777  fcl2hits.resize(jclNewSize);
778  // append the hits in the overlap region
779  fcl2hits.insert(fcl2hits.end(), oHits.begin(), oHits.end());
780  // now paste in the icl hits that are US of the overlap region
781  for (ii = 0; ii < tcl[icl].tclhits.size(); ++ii) {
782  iht = tcl[icl].tclhits[ii];
783  if ((unsigned int)fHits[iht].WireID().Wire >= eWire) continue;
784  fcl2hits.insert(fcl2hits.end(), tcl[icl].tclhits.begin() + ii, tcl[icl].tclhits.end());
785  break;
786  }
787  clBeginSlp = tcl[jcl].BeginSlp;
788  clBeginSlpErr = tcl[jcl].BeginSlpErr;
789  clBeginAng = tcl[jcl].BeginAng;
790  clBeginWir = tcl[jcl].BeginWir;
791  clBeginTim = tcl[jcl].BeginTim;
792  clBeginChg = tcl[jcl].BeginChg;
793  clBeginChgNear = tcl[jcl].BeginChgNear;
794  // End info from icl
795  clEndSlp = tcl[icl].EndSlp;
796  clEndSlpErr = tcl[icl].EndSlpErr;
797  clEndAng = tcl[icl].EndAng;
798  clEndWir = tcl[icl].EndWir;
799  clEndTim = tcl[icl].EndTim;
800  clEndChg = tcl[icl].EndChg;
801  clEndChgNear = tcl[icl].EndChgNear;
802  clStopCode = tcl[icl].StopCode;
803  clProcCode = tcl[icl].ProcCode + 500;
804  MakeClusterObsolete(icl);
805  MakeClusterObsolete(jcl);
806  if (!TmpStore()) {
807  // Merged cluster is fubar. Try to recover
808  RestoreObsoleteCluster(icl);
809  RestoreObsoleteCluster(jcl);
810  CheckHitClusterAssociations();
811  continue;
812  }
813  CheckHitClusterAssociations();
814  tcl[tcl.size() - 1].BeginVtx = tcl[jcl].BeginVtx;
815  tcl[tcl.size() - 1].EndVtx = tcl[icl].BeginVtx;
816  // after this point any failure should result in a jcl loop break
817  if (prt)
818  mf::LogVerbatim("CC") << "MergeOverlap new long cluster ID " << tcl[tcl.size() - 1].ID
819  << " in clCTP " << clCTP;
820  // icl cluster made obsolete so we must have done something
821  if (tcl[icl].ID < 0) break;
822  } // jcl
823  } // icl
824 
825  } // MergeOverlap()
826 
828  void ClusterCrawlerAlg::MakeClusterObsolete(unsigned short icl)
829  {
830  short& ID = tcl[icl].ID;
831  if (ID <= 0) {
832  mf::LogError("CC") << "Trying to make already-obsolete cluster obsolete ID = " << ID;
833  return; // already obsolete
834  }
835  ID = -ID; // mark the cluster as obsolete
836 
837  // release the hits
838  for (unsigned int iht = 0; iht < tcl[icl].tclhits.size(); ++iht)
839  inClus[tcl[icl].tclhits[iht]] = 0;
840 
841  } // ClusterCrawlerAlg::MakeClusterObsolete()
842 
844  void ClusterCrawlerAlg::RestoreObsoleteCluster(unsigned short icl)
845  {
846  short& ID = tcl[icl].ID;
847  if (ID > 0) {
848  mf::LogError("CC") << "Trying to restore non-obsolete cluster ID = " << ID;
849  return;
850  }
851  ID = -ID;
852 
853  for (unsigned short iht = 0; iht < tcl[icl].tclhits.size(); ++iht)
854  inClus[tcl[icl].tclhits[iht]] = ID;
855 
856  } // RestoreObsoleteCluster()
857 
859  void ClusterCrawlerAlg::FclTrimUS(unsigned short nTrim)
860  {
861 
862  // Trims nTrim hits off the UpStream end of the fcl2hits, etc vectors.
863  if (nTrim == 0) return;
864 
865  if (nTrim >= fcl2hits.size()) nTrim = fcl2hits.size();
866 
867  // RestoreUnMergedClusterHits((short)nTrim);
868  for (unsigned short ii = 0; ii < nTrim; ++ii) {
869  fcl2hits.pop_back();
870  chifits.pop_back();
871  hitNear.pop_back();
872  chgNear.pop_back();
873  } // ii
874 
875  } // FclTrim
876 
878  void ClusterCrawlerAlg::CheckHitClusterAssociations()
879  {
880  // check hit - cluster associations
881 
882  if (fHits.size() != inClus.size()) {
883  mf::LogError("CC") << "CHCA: Sizes wrong " << fHits.size() << " " << inClus.size();
884  return;
885  }
886 
887  unsigned int iht, nErr = 0;
888  short clID;
889 
890  // check cluster -> hit association
891  for (unsigned short icl = 0; icl < tcl.size(); ++icl) {
892  if (tcl[icl].ID < 0) continue;
893  clID = tcl[icl].ID;
894  for (unsigned short ii = 0; ii < tcl[icl].tclhits.size(); ++ii) {
895  iht = tcl[icl].tclhits[ii];
896  if (iht > fHits.size() - 1) {
897  mf::LogError("CC") << "CHCA: Bad tclhits index " << iht << " fHits size " << fHits.size();
898  return;
899  } // iht > fHits.size() - 1
900  if (inClus[iht] != clID) {
901  mf::LogError("CC") << "CHCA: Bad cluster -> hit association. clID " << clID
902  << " hit inClus " << inClus[iht] << " ProcCode " << tcl[icl].ProcCode
903  << " CTP " << tcl[icl].CTP;
904  ++nErr;
905  if (nErr > 10) return;
906  }
907  } // ii
908  } // icl
909 
910  // check hit -> cluster association
911  unsigned short icl;
912  for (iht = 0; iht < fHits.size(); ++iht) {
913  if (inClus[iht] <= 0) continue;
914  icl = inClus[iht] - 1;
915  // see if the cluster is obsolete
916  if (tcl[icl].ID < 0) {
917  mf::LogError("CC") << "CHCA: Hit associated with an obsolete cluster. hit W:T "
918  << fHits[iht].WireID().Wire << ":" << (int)fHits[iht].PeakTime()
919  << " tcl[icl].ID " << tcl[icl].ID;
920  ++nErr;
921  if (nErr > 10) return;
922  }
923  if (std::find(tcl[icl].tclhits.begin(), tcl[icl].tclhits.end(), iht) ==
924  tcl[icl].tclhits.end()) {
925  mf::LogError("CC") << "CHCA: Bad hit -> cluster association. hit index " << iht << " W:T "
926  << fHits[iht].WireID().Wire << ":" << (int)fHits[iht].PeakTime()
927  << " inClus " << inClus[iht];
928  ++nErr;
929  if (nErr > 10) return;
930  }
931  } // iht
932 
933  } // CheckHitClusterAssociations()
934 
936  void ClusterCrawlerAlg::RemoveObsoleteHits()
937  {
938 
939  unsigned int destHit = 0;
940 
941  if (fHits.size() != inClus.size()) {
942  mf::LogError("CC") << "RemoveObsoleteHits size mis-match " << fHits.size() << " "
943  << inClus.size();
944  return;
945  }
946 
947  unsigned short icl;
948  for (unsigned int srcHit = 0; srcHit < fHits.size(); ++srcHit) {
949  if (inClus[srcHit] < 0) continue;
950  if (srcHit != destHit) {
951  fHits[destHit] = std::move(fHits[srcHit]);
952  inClus[destHit] = inClus[srcHit];
953  if (inClus[destHit] > 0) {
954  // hit is in a cluster. Find it and change the index
955  icl = inClus[destHit] - 1;
956  auto& hits = tcl[icl].tclhits;
957  auto iHitIndex = std::find(hits.begin(), hits.end(), srcHit);
958  if (iHitIndex == hits.end()) {
959  mf::LogError("CC") << "RemoveObsoleteHits: Hit #" << srcHit
960  << " not found in cluster ID " << inClus[destHit];
961  }
962  else {
963  *iHitIndex = destHit; // update the index
964  }
965  } // inClus[destHit] > 0
966  }
967  ++destHit;
968  } // srcHit
969 
970  fHits.resize(destHit);
971  inClus.resize(destHit);
972 
973  } // RemoveObsoleteHits()
974 
976  void ClusterCrawlerAlg::ChkClusterDS()
977  {
978  // Try to extend clusters DS by a few wires.
979  // DS hits may not have been included in a cluster if they have high
980  // multiplicity or high charge.
981  // Ref ClusterLoop cuts for starting a seed cluster.
982 
983  prt = (fDebugPlane == 3);
984 
985  // use the most generous kink angle cut
986  float dThCut = fKinkAngCut[fNumPass - 1];
987 
988  if (prt)
989  mf::LogVerbatim("CC") << "ChkClusterDS clCTP " << clCTP << " kink angle cut " << dThCut;
990 
991  const unsigned short tclsize = tcl.size();
992  bool didMerge, skipit;
993  unsigned short icl, ii, nhm, iv;
994  unsigned int iht;
995 
996  // first merge any hits on the DS end of clusters
997  for (icl = 0; icl < tclsize; ++icl) {
998  if (tcl[icl].ID < 0) continue;
999  if (tcl[icl].CTP != clCTP) continue;
1000  // ignore clusters that have a Begin vertex
1001  if (tcl[icl].BeginVtx >= 0) continue;
1002  // and clusters near a vertex
1003  skipit = false;
1004  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
1005  if (vtx[iv].CTP != clCTP) continue;
1006  if (std::abs(vtx[iv].Wire - tcl[icl].BeginWir) > 4) continue;
1007  if (std::abs(vtx[iv].Time - tcl[icl].BeginTim) > 20) continue;
1008  skipit = true;
1009  break;
1010  }
1011  if (skipit) continue;
1012  // check the first few hits
1013  nhm = 0;
1014  for (ii = 0; ii < 3; ++ii) {
1015  iht = fcl2hits[ii];
1016  if (fHits[iht].Multiplicity() > 1) {
1017  MergeHits(iht, didMerge);
1018  if (didMerge) ++nhm;
1019  }
1020  } // ii
1021  if (nhm > 0) {
1022  // update the Begin parameters in-place
1023  FitClusterMid(icl, 0, 3);
1024  tcl[icl].BeginTim = clpar[0];
1025  tcl[icl].BeginSlp = clpar[1];
1026  tcl[icl].BeginAng = atan(fScaleF * clpar[1]);
1027  tcl[icl].BeginSlpErr = clparerr[1];
1028  tcl[icl].BeginChg = fAveChg;
1029  tcl[icl].ProcCode += 5000;
1030  if (prt) mf::LogVerbatim("CC") << "ChkClusterDS: Merge hits on cluster " << tcl[icl].ID;
1031  } // nhm > 0
1032  } // icl
1033 
1034  float thhits, prevth, hitrms, rmsrat;
1035  bool ratOK;
1036  std::vector<unsigned int> dshits;
1037  for (icl = 0; icl < tclsize; ++icl) {
1038  if (tcl[icl].ID < 0) continue;
1039  if (tcl[icl].CTP != clCTP) continue;
1040  // ignore clusters that have a Begin vertex
1041  if (tcl[icl].BeginVtx >= 0) continue;
1042  // and clusters near a vertex
1043  skipit = false;
1044  for (iv = 0; iv < vtx.size(); ++iv) {
1045  if (vtx[iv].CTP != clCTP) continue;
1046  if (std::abs(vtx[iv].Wire - tcl[icl].BeginWir) > 4) continue;
1047  if (std::abs(vtx[iv].Time - tcl[icl].BeginTim) > 20) continue;
1048  skipit = true;
1049  break;
1050  }
1051  if (skipit) continue;
1052  // find the angle using the first 2 hits
1053  unsigned int ih0 = tcl[icl].tclhits[1];
1054  unsigned int ih1 = tcl[icl].tclhits[0];
1055  const float slp = (fHits[ih1].PeakTime() - fHits[ih0].PeakTime()) /
1056  (fHits[ih1].WireID().Wire - fHits[ih0].WireID().Wire);
1057  prevth = std::atan(fScaleF * slp);
1058  // move the "origin" to the first hit
1059  ih0 = ih1;
1060  unsigned int wire = fHits[ih0].WireID().Wire;
1061  hitrms = fHits[ih0].RMS();
1062  float time0 = fHits[ih0].PeakTime();
1063  float prtime;
1064  dshits.clear();
1065  // follow DS a few wires. Stop if any encountered
1066  // hit is associated with a cluster
1067  for (ii = 0; ii < 4; ++ii) {
1068  ++wire;
1069  if (wire > fLastWire) break;
1070  prtime = time0 + slp;
1071  if (prt)
1072  mf::LogVerbatim("CC") << "ChkClusterDS: Try to extend " << tcl[icl].ID << " to W:T "
1073  << wire << " hitrms " << hitrms << " prevth " << prevth
1074  << " prtime " << (int)prtime;
1075  // stop if no hits on this wire
1076  if (WireHitRange[wire].first == -2) break;
1077  unsigned int firsthit = WireHitRange[wire].first;
1078  unsigned int lasthit = WireHitRange[wire].second;
1079  bool hitAdded = false;
1080  for (ih1 = firsthit; ih1 < lasthit; ++ih1) {
1081  if (inClus[ih1] != 0) continue;
1082  if (prtime < fHits[ih1].PeakTimeMinusRMS(5)) continue;
1083  if (prtime > fHits[ih1].PeakTimePlusRMS(5)) continue;
1084  const float slp = (fHits[ih1].PeakTime() - fHits[ih0].PeakTime()) /
1085  (fHits[ih1].WireID().Wire - fHits[ih0].WireID().Wire);
1086  thhits = std::atan(fScaleF * slp);
1087  if (prt)
1088  mf::LogVerbatim("CC") << " theta " << thhits << " prevth " << prevth << " cut "
1089  << dThCut;
1090  if (std::abs(thhits - prevth) > dThCut) continue;
1091  // make a hit rms cut for small angle clusters
1092  ratOK = true;
1093  if (std::abs(slp) < fLAClusSlopeCut) {
1094  rmsrat = fHits[ih1].RMS() / hitrms;
1095  ratOK = rmsrat > 0.3 && rmsrat < 3;
1096  }
1097  else {
1098  // merge the hits
1099  bool didMerge;
1100  MergeHits(ih1, didMerge);
1101  }
1102  if (prt) mf::LogVerbatim("CC") << " rmsrat " << rmsrat << " OK? " << ratOK;
1103  // require small angle and not wildly different width compared
1104  // to the first hit in the cluster
1105  // TODO handle hit multiplets here
1106  if (ratOK) {
1107  dshits.push_back(ih1);
1108  hitAdded = true;
1109  prevth = thhits;
1110  ih0 = ih1;
1111  if (prt)
1112  mf::LogVerbatim("CC") << " Add hit " << fHits[ih1].WireID().Wire << ":"
1113  << (int)fHits[ih1].PeakTime() << " rmsrat " << rmsrat;
1114  break;
1115  }
1116  } // ih1
1117  // stop looking if no hit was added on this wire
1118  if (!hitAdded) break;
1119  } // ii
1120  // Found hits not associated with a different cluster
1121  if (dshits.size() > 0) {
1122  // put the tcl cluster into the working vectors
1123  TmpGet(icl);
1124  // clobber the hits
1125  fcl2hits.clear();
1126  // sort the DS hits
1127  if (dshits.size() > 1) std::sort(dshits.begin(), dshits.end(), SortByLowHit);
1128  // stuff them into fcl2hits
1129  fcl2hits = dshits;
1130  // Append the existing hits
1131  for (ii = 0; ii < tcl[icl].tclhits.size(); ++ii) {
1132  // un-assign the hits so that TmpStore will re-assign them
1133  iht = tcl[icl].tclhits[ii];
1134  inClus[iht] = 0;
1135  fcl2hits.push_back(iht);
1136  }
1137  clProcCode += 5000;
1138  pass = fNumPass - 1;
1139  FitClusterChg();
1140  clBeginChg = fAveChg;
1141  // declare the old one obsolete
1142  MakeClusterObsolete(icl);
1143  // add the new one
1144  if (!TmpStore()) {
1145  mf::LogError("CC") << "ChkClusterDS TmpStore failed while extending cluster ID "
1146  << tcl[icl].ID;
1147  continue;
1148  }
1149  const size_t newcl = tcl.size() - 1;
1150  if (prt) { mf::LogVerbatim("CC") << " Store " << newcl; }
1151  tcl[newcl].BeginVtx = tcl[icl].BeginVtx;
1152  tcl[newcl].EndVtx = tcl[icl].EndVtx;
1153  } // dshits.size() > 0
1154  } // icl
1155  } // ChkClusterDS
1156 
1158  bool ClusterCrawlerAlg::CrawlVtxChk2()
1159  {
1160  // returns true if the (presumably short) cluster under construction
1161  // resides between a vertex and another cluster that is associated with
1162  // that vertex
1163 
1164  if (vtx.size() == 0) return false;
1165  if (fcl2hits.size() == 0) return false;
1166 
1167  unsigned int iht = fcl2hits.size() - 1;
1168  unsigned short icl;
1169  float wire0 = (0.5 + fHits[fcl2hits[iht]].WireID().Wire);
1170  float dt;
1171  float thclus = std::atan(fScaleF * clpar[1]);
1172 
1173  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
1174  if (vtx[iv].CTP != clCTP) continue;
1175  if (wire0 < vtx[iv].Wire) continue;
1176  if (wire0 > vtx[iv].Wire + 10) continue;
1177  dt = clpar[0] + (vtx[iv].Wire - wire0) * clpar[1] - vtx[iv].Time;
1178  if (std::abs(dt) > 10) continue;
1179  // cluster points to an US vertex. See if the angle is similar to
1180  // cluster associated with this vertex
1181  for (icl = 0; icl < tcl.size(); ++icl) {
1182  if (tcl[icl].CTP != clCTP) continue;
1183  if (tcl[icl].EndVtx != iv) continue;
1184  if (std::abs(tcl[icl].EndAng - thclus) < fKinkAngCut[pass]) return true;
1185  }
1186  }
1187 
1188  return false;
1189 
1190  } // CrawlVtxChk2()
1191 
1193  bool ClusterCrawlerAlg::CrawlVtxChk(unsigned int kwire)
1194  {
1195 
1196  // returns true if the cluster is near a vertex on wire kwire
1197  if (vtx.size() == 0) return false;
1198  unsigned int iht = fcl2hits.size() - 1;
1199  float wire0 = (0.5 + fHits[fcl2hits[iht]].WireID().Wire);
1200  float prtime = clpar[0] + (kwire - wire0) * clpar[1];
1201  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
1202  if (vtx[iv].CTP != clCTP) continue;
1203  if ((unsigned int)(0.5 + vtx[iv].Wire) != kwire) continue;
1204  if (std::abs(prtime - vtx[iv].Time) < 10) return true;
1205  }
1206  return false;
1207  } // CrawlVtxChk()
1208 
1210  void ClusterCrawlerAlg::VtxConstraint(unsigned int iwire,
1211  unsigned int ihit,
1212  unsigned int jwire,
1213  unsigned int& useHit,
1214  bool& doConstrain)
1215  {
1216  // checks hits on wire jwire to see if one is on a line between a US vertex
1217  // and the hit ihit on wire iwire. If one is found, doConstrain is set true
1218  // and the hit index is returned.
1219  doConstrain = false;
1220  if (vtx.size() == 0) return;
1221  // no vertices made yet on the first pass
1222  if (pass == 0) return;
1223  // skip if vertices were not requested to be made on the previous pass
1224  if (!fFindVertices[pass - 1]) return;
1225 
1226  if (jwire > WireHitRange.size() - 1) {
1227  mf::LogError("CC") << "VtxConstraint fed bad jwire " << jwire << " WireHitRange size "
1228  << WireHitRange.size();
1229  return;
1230  }
1231 
1232  unsigned int jfirsthit = WireHitRange[jwire].first;
1233  unsigned int jlasthit = WireHitRange[jwire].second;
1234  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
1235  if (vtx[iv].CTP != clCTP) continue;
1236  // vertex must be US of the cluster
1237  if (vtx[iv].Wire > jwire) continue;
1238  // but not too far US
1239  if (vtx[iv].Wire < jwire - 10) continue;
1240  recob::Hit const& hit = fHits[ihit];
1241  clpar[0] = hit.PeakTime();
1242  clpar[1] = (vtx[iv].Time - hit.PeakTime()) / (vtx[iv].Wire - iwire);
1243  clpar[2] = hit.WireID().Wire;
1244  float prtime = clpar[0] + clpar[1] * (jwire - iwire);
1245  for (unsigned int jhit = jfirsthit; jhit < jlasthit; ++jhit) {
1246  if (inClus[jhit] != 0) continue;
1247  const float tdiff = std::abs(fHits[jhit].TimeDistanceAsRMS(prtime));
1248  if (tdiff < 2.5) {
1249  useHit = jhit;
1250  doConstrain = true;
1251  return;
1252  }
1253  } // jhit
1254  } // iv
1255  } // VtxConstraint()
1256 
1258 
1259  void ClusterCrawlerAlg::RefineVertexClusters(unsigned short iv)
1260  {
1261 
1262  // Try to attach or remove hits on the ends of vertex clusters
1263 
1264  std::vector<unsigned short> begClusters;
1265  std::vector<short> begdW;
1266  std::vector<unsigned short> endClusters;
1267  std::vector<short> enddW;
1268 
1269  unsigned int vWire = (unsigned int)(vtx[iv].Wire + 0.5);
1270  unsigned int vWireErr = (unsigned int)(2 * vtx[iv].WireErr);
1271  unsigned int vWireLo = vWire - vWireErr;
1272  unsigned int vWireHi = vWire + vWireErr;
1273 
1274  unsigned short icl, ii;
1275  short dW;
1276  bool needsWork = false;
1277  short maxdW = -100;
1278  short mindW = 100;
1279  for (icl = 0; icl < tcl.size(); ++icl) {
1280  if (tcl[icl].ID < 0) continue;
1281  if (tcl[icl].CTP != vtx[iv].CTP) continue;
1282  if (tcl[icl].BeginVtx == iv) {
1283  dW = vWire - tcl[icl].BeginWir;
1284  if (dW > maxdW) maxdW = dW;
1285  if (dW < mindW) mindW = dW;
1286  if (std::abs(dW) > 1) needsWork = true;
1287  // TODO: Check dTime also?
1288  begClusters.push_back(icl);
1289  begdW.push_back(dW);
1290  }
1291  if (tcl[icl].EndVtx == iv) {
1292  dW = vWire - tcl[icl].EndWir;
1293  if (dW > maxdW) maxdW = dW;
1294  if (dW < mindW) mindW = dW;
1295  if (std::abs(dW) > 1) needsWork = true;
1296  endClusters.push_back(icl);
1297  enddW.push_back(dW);
1298  }
1299  } // icl
1300 
1301  if (vtxprt)
1302  mf::LogVerbatim("CC") << "RefineVertexClusters: vertex " << iv << " needsWork " << needsWork
1303  << " mindW " << mindW << " maxdW " << maxdW << " vWireErr " << vWireErr;
1304 
1305  if (!needsWork) return;
1306 
1307  // See if we can move the vertex within errors to reconcile the differences
1308  // without altering the clusters
1309  if (((unsigned int)std::abs(mindW) < vWireErr) && ((unsigned int)std::abs(maxdW) < vWireErr) &&
1310  std::abs(maxdW - mindW) < 2) {
1311  if (vtxprt) mf::LogVerbatim("CC") << " Move vtx wire " << vtx[iv].Wire;
1312  vtx[iv].Wire -= (float)(maxdW + mindW) / 2;
1313  if (vtxprt) mf::LogVerbatim("CC") << " to " << vtx[iv].Wire;
1314  // TODO: Fix the vertex time here if necessary
1315  vtx[iv].Fixed = true;
1316  // try to attach other clusters
1317  VertexCluster(iv);
1318  return;
1319  }
1320 
1321  // Check the vertex End clusters
1322  unsigned short newSize;
1323  for (ii = 0; ii < endClusters.size(); ++ii) {
1324  icl = endClusters[ii];
1325  if (vtxprt)
1326  mf::LogVerbatim("CC") << " endCluster " << tcl[icl].ID << " dW " << enddW[ii] << " vWire "
1327  << vWire;
1328  if (tcl[icl].EndWir < vWire) {
1329  // vertex is DS of the cluster end -> remove hits
1330  TmpGet(icl);
1331  newSize = fcl2hits.size();
1332  for (auto hiter = fcl2hits.rbegin(); hiter < fcl2hits.rend(); ++hiter) {
1333  if (fHits[*hiter].WireID().Wire > vWire) break;
1334  --newSize;
1335  }
1336  // release the hits
1337  for (auto hiter = fcl2hits.begin(); hiter < fcl2hits.end(); ++hiter)
1338  inClus[*hiter] = 0;
1339  // shorten the cluster
1340  fcl2hits.resize(newSize);
1341  MakeClusterObsolete(icl);
1342  // fit
1343  FitCluster();
1344  clProcCode += 700;
1345  // store it
1346  TmpStore();
1347  tcl[tcl.size() - 1].EndVtx = iv;
1348  // update the vertex association
1349  if (vtxprt)
1350  mf::LogVerbatim("CC") << " modified cluster " << tcl[icl].ID << " -> "
1351  << tcl[tcl.size() - 1].ID;
1352  } // tcl[icl].EndWir < vWire
1353  else if (tcl[icl].EndWir > vWire) {
1354  mf::LogVerbatim("CC") << "RefineVertexClusters: Write some EndVtx code";
1355  } //
1356  } // ii endClusters
1357 
1358  if (begClusters.size() > 0)
1359  mf::LogVerbatim("CC") << "RefineVertexClusters: Write some BeginVtx code";
1360 
1361  if (mindW < 0 && maxdW > 0) {
1362  // vertex wire is in between the ends of the clusters
1363  // inspect the hits on both clusters near the vertex. The vertex should probably be on the hit
1364  // with the highest charge
1365  int vtxHit = -1;
1366  unsigned short clsBigChg = 0;
1367  float bigChg = 0;
1368  unsigned int iht;
1369  unsigned int ihit;
1370  // check the begClusters
1371  for (ii = 0; ii < begClusters.size(); ++ii) {
1372  icl = begClusters[ii];
1373  for (iht = 0; iht < tcl[icl].tclhits.size(); ++iht) {
1374  ihit = tcl[icl].tclhits[iht];
1375  if (fHits[ihit].Integral() > bigChg) {
1376  bigChg = fHits[ihit].Integral();
1377  vtxHit = ihit;
1378  clsBigChg = icl;
1379  }
1380  if (fHits[ihit].WireID().Wire < vWireLo) break;
1381  } // iht
1382  } // ii
1383  // now check the endClusters
1384  for (ii = 0; ii < endClusters.size(); ++ii) {
1385  icl = endClusters[ii];
1386  for (iht = 0; iht < tcl[icl].tclhits.size(); ++iht) {
1387  ihit = tcl[icl].tclhits[tcl[icl].tclhits.size() - 1 - iht];
1388  if (fHits[ihit].Integral() > bigChg) {
1389  bigChg = fHits[ihit].Integral();
1390  vtxHit = ihit;
1391  clsBigChg = icl;
1392  }
1393  if (fHits[ihit].WireID().Wire > vWireHi) break;
1394  } // iht
1395  } // ii
1396  if (vtxHit > 0) {
1397  if (vtxprt)
1398  mf::LogVerbatim("CC") << " moving vertex location to hit " << fHits[vtxHit].WireID().Wire
1399  << ":" << (int)fHits[vtxHit].PeakTime() << " on cluster "
1400  << tcl[clsBigChg].ID;
1401  vtx[iv].Wire = fHits[vtxHit].WireID().Wire;
1402  vtx[iv].Time = fHits[vtxHit].PeakTime();
1403  vtx[iv].Fixed = true;
1404  } // vtxHit > 0
1405  } // mindW < 0 && maxdW > 0
1406 
1407  FitVtx(iv);
1408 
1409  } // RefineVertexClusters
1410 
1412  bool ClusterCrawlerAlg::VtxClusterSplit()
1413  {
1414 
1415  // split clusters that cross vertices
1416 
1417  vtxprt = (fDebugPlane >= 0 && fDebugWire == 5555);
1418 
1419  if (vtxprt) mf::LogVerbatim("CC") << "VtxClusterSplit ";
1420 
1421  if (vtx.size() == 0) return false;
1422  unsigned short tclsize = tcl.size();
1423  if (tclsize < 2) return false;
1424 
1425  bool didit;
1426  bool didSomething = false;
1427 
1428  for (unsigned short icl = 0; icl < tclsize; ++icl) {
1429  if (tcl[icl].ID < 0) continue;
1430  if (tcl[icl].CTP != clCTP) continue;
1431  // ignore short clusters
1432  if (tcl[icl].tclhits.size() < 5) continue;
1433  // check vertices
1434  didit = false;
1435  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
1436  if (vtx[ivx].CTP != clCTP) continue;
1437  // abandoned vertex?
1438  if (vtx[ivx].NClusters == 0) continue;
1439  // already assigned to this vertex?
1440  if (tcl[icl].BeginVtx == ivx) continue;
1441  if (tcl[icl].EndVtx == ivx) continue;
1442  // vertex wire in-between the cluster ends?
1443  if (vtx[ivx].Wire < tcl[icl].EndWir) continue;
1444  if (vtx[ivx].Wire > tcl[icl].BeginWir) continue;
1445  // vertex time in-between the cluster ends?
1446  float hiTime = tcl[icl].BeginTim;
1447  if (tcl[icl].EndTim > hiTime) hiTime = tcl[icl].EndTim;
1448  if (vtx[ivx].Time > hiTime + 5) continue;
1449  float loTime = tcl[icl].BeginTim;
1450  if (tcl[icl].EndTim < loTime) loTime = tcl[icl].EndTim;
1451  if (vtx[ivx].Time < loTime - 5) continue;
1452  // find the hit on the cluster that is closest to the vertex on the
1453  // DS side
1454  if (vtxprt)
1455  mf::LogVerbatim("CC") << " Chk cluster ID " << tcl[icl].ID << " with vertex " << ivx;
1456  short ihvx = -99;
1457  // nSplit is the index of the hit in the cluster where we will
1458  // split it if all requirements are met
1459  unsigned short nSplit = 0;
1460  unsigned short nLop = 0;
1461  unsigned int iht;
1462  for (unsigned short ii = tcl[icl].tclhits.size() - 1; ii > 0; --ii) {
1463  iht = tcl[icl].tclhits[ii];
1464  ++nLop;
1465  if (fHits[iht].WireID().Wire >= vtx[ivx].Wire) {
1466  nSplit = ii;
1467  if (vtxprt)
1468  mf::LogVerbatim("CC") << "Split cluster " << tcl[icl].ID << " at wire "
1469  << fHits[iht].WireID().Wire << " nSplit " << nSplit;
1470  ihvx = iht;
1471  break;
1472  }
1473  } // ii
1474  // found the wire. Now make a rough time cut
1475  if (ihvx < 0) continue;
1476  if (fabs(fHits[ihvx].PeakTime() - vtx[ivx].Time) > 10) continue;
1477  // check the angle between the crossing cluster icl and the
1478  // clusters that comprise the vertex.
1479  // First decide which end of cluster icl to use to define the angle
1480  float iclAng = 0.;
1481  if (nSplit > tcl[icl].tclhits.size() / 2) { iclAng = tcl[icl].EndAng; }
1482  else {
1483  iclAng = tcl[icl].BeginAng;
1484  }
1485  if (vtxprt) mf::LogVerbatim("CC") << " iclAng " << iclAng;
1486  // check angle wrt the the vertex clusters
1487  bool angOK = false;
1488  for (unsigned short jcl = 0; jcl < tclsize; ++jcl) {
1489  if (tcl[jcl].ID < 0) continue;
1490  if (tcl[jcl].CTP != clCTP) continue;
1491  if (tcl[jcl].BeginVtx == ivx) {
1492  if (fabs(tcl[jcl].BeginAng - iclAng) > 0.4) {
1493  // large angle difference. Set the flag
1494  angOK = true;
1495  break;
1496  }
1497  } // tcl[jcl].BeginVtx == ivx
1498  if (tcl[jcl].EndVtx == ivx) {
1499  if (fabs(tcl[jcl].EndAng - iclAng) > 0.4) {
1500  // large angle difference. Set the flag
1501  angOK = true;
1502  break;
1503  }
1504  } // tcl[jcl].EndVtx == ivx
1505  } // jcl
1506  // time to split or chop
1507  if (angOK) {
1508  if (vtxprt) mf::LogVerbatim("CC") << "Split/Chop at pos " << nLop;
1509  if (nLop < 3) {
1510  // lop off hits at the US end
1511  // Put the cluster in the local arrays
1512  TmpGet(icl);
1513  for (unsigned short ii = 0; ii < nLop; ++ii) {
1514  unsigned int iht = fcl2hits[fcl2hits.size() - 1];
1515  inClus[iht] = 0;
1516  fcl2hits.pop_back();
1517  }
1518  // store it
1519  clProcCode += 1000;
1520  // declare this cluster obsolete
1521  MakeClusterObsolete(icl);
1522  // store the new one
1523  if (!TmpStore()) continue;
1524  unsigned short newcl = tcl.size() - 1;
1525  tcl[newcl].BeginVtx = tcl[icl].BeginVtx;
1526  tcl[newcl].EndVtx = ivx;
1527  }
1528  else {
1529  // split the cluster into two
1530  // correct the split position
1531  ++nSplit;
1532  if (SplitCluster(icl, nSplit, ivx)) {
1533  tcl[tcl.size() - 1].ProcCode += 1000;
1534  tcl[tcl.size() - 2].ProcCode += 1000;
1535  }
1536  }
1537  didit = true;
1538  didSomething = true;
1539  } // angOK
1540  if (didit) break;
1541  } // ivx
1542  } // icl
1543 
1544  return didSomething;
1545 
1546  } // VtxClusterSplit()
1547 
1549  void ClusterCrawlerAlg::MergeHits(const unsigned int theHit, bool& didMerge)
1550  {
1551  // Merge all unused separate hits in the multiplet of which
1552  // theHit is a member into one hit (= theHit).
1553  // Mark the merged hits other than theHit obsolete.
1554  // Hits in the multiplet that are associated with an existing cluster are
1555  // not affected.
1556  // Hit multiplicity is reworked (including all the hits in the multiplet).
1557  // Used hits have the multiplicity and index corrected too; the local
1558  // index reflects the peak time.
1559  // Note that theHit may or may not be marked free (usually, it is not)
1560 
1561  didMerge = false;
1562 
1563  if (theHit > fHits.size() - 1) { return; }
1564 
1565  recob::Hit const& hit = fHits[theHit];
1566 
1567  // don't bother trying to merge an already merged hit
1568  if (fHits[theHit].GoodnessOfFit() == 6666) {
1569  if (prt)
1570  mf::LogVerbatim("CC") << "MergeHits Trying to merge already merged hit "
1571  << hit.WireID().Plane << ":" << hit.WireID().Wire << ":"
1572  << (int)hit.PeakTime() << " Multiplicity " << hit.Multiplicity()
1573  << " theHit " << theHit;
1574  return;
1575  }
1576 
1577  // don't merge if it isn't available
1578  if (!mergeAvailable[theHit]) { return; }
1579 
1580  if (hit.Multiplicity() < 2) return;
1581 
1582  // number of hits in this hit multiplet
1583  std::pair<size_t, size_t> MultipletRange = FindHitMultiplet(theHit);
1584 
1585  // ensure that this is a high multiplicity hit:
1586  if (MultipletRange.second <= MultipletRange.first) return;
1587 
1588  // do a quick check to see how many hits are available to be merged
1589  unsigned short nAvailable = 0;
1590  unsigned short nInClus = 0;
1591  for (size_t jht = MultipletRange.first; jht < MultipletRange.second; ++jht) {
1592  if (jht == theHit) continue;
1593  if (fHits[jht].GoodnessOfFit() == 6666) continue;
1594  if (inClus[jht] != 0) {
1595  ++nInClus;
1596  continue;
1597  }
1598  ++nAvailable;
1599  } // jht
1600  if (nAvailable == 0) return;
1601  // don't merge if any hit is used
1602  if (nInClus > 0) return;
1603 
1604  // calculate the Charge normalization factor using the hit information
1605  // instead of passing CCHitFinder ChgNorms all the way down here
1606  float chgNorm = 2.507 * hit.PeakAmplitude() * hit.RMS() / hit.Integral();
1607 
1608  short loTime = 9999;
1609  short hiTime = 0;
1610  unsigned short nGaus = 1;
1611  float hitSep;
1612  // number of hits that are close to theHit
1613  unsigned short nclose = 0;
1614  // find the time range for the hit multiplet
1615  for (size_t jht = MultipletRange.first; jht < MultipletRange.second; ++jht) {
1616  if (inClus[jht] < 0) continue;
1617  recob::Hit const& other_hit = fHits[jht];
1618  // error checking
1619  if ((other_hit.StartTick() != hit.StartTick()) || (other_hit.WireID() != hit.WireID())) {
1620  return;
1621  }
1622  if (other_hit.Multiplicity() != hit.Multiplicity()) { return; }
1623  // hit is not used by another cluster
1624  if (inClus[jht] != 0) continue;
1625  short arg = (short)(other_hit.PeakTimeMinusRMS(3));
1626  if (arg < loTime) loTime = arg;
1627  arg = (short)(other_hit.PeakTimePlusRMS(3));
1628  if (arg > hiTime) hiTime = arg;
1629  if (jht != theHit) ++nGaus;
1630  hitSep = std::abs(other_hit.PeakTime() - hit.PeakTime()) / other_hit.RMS();
1631  if (jht != theHit && hitSep < 3) ++nclose;
1632  } // jht
1633  // all hits in the multiplet other than this one used?
1634  if (nGaus < 2) return;
1635 
1636  // the hits in this multiplet will have this multiplicity from now on
1637  const short int NewMultiplicity = hit.Multiplicity() + 1 - nGaus;
1638 
1639  if (loTime < 0) loTime = 0;
1640  ++hiTime;
1641  // define a signal shape, fill it with zeros
1642  std::vector<double> signal(hiTime - loTime, 0.);
1643  // now add the Gaussians for each hit
1644  double chgsum = 0.;
1645  for (size_t jht = MultipletRange.first; jht < MultipletRange.second; ++jht) {
1646  recob::Hit const& other_hit = fHits[jht];
1647  if (jht != theHit) {
1648  // hit used in another cluster
1649  if (inClus[jht] != 0) continue;
1650  // declare this hit obsolete
1651  inClus[jht] = -1;
1652  } // jht != theHit
1653  // add up the charge
1654  chgsum += other_hit.Integral();
1655  for (unsigned short time = loTime; time < hiTime; ++time) {
1656  unsigned short indx = time - loTime;
1657  double arg = (other_hit.PeakTime() - (double)time) / other_hit.RMS();
1658  signal[indx] += other_hit.PeakAmplitude() * exp(-0.5 * arg * arg);
1659  } // time
1660  } // jj
1661  // find the average weighted time
1662  double sigsum = 0.;
1663  double sigsumt = 0.;
1664  for (unsigned short time = loTime; time < hiTime; ++time) {
1665  sigsum += signal[time - loTime];
1666  sigsumt += signal[time - loTime] * time;
1667  }
1668  if (sigsum == 0.) {
1669  // mf::LogError("CC")<<"MergeHits: bad sum";
1670  return;
1671  }
1672  double aveTime = sigsumt / sigsum;
1673  // find the RMS
1674  sigsumt = 0.;
1675  for (unsigned short time = loTime; time < hiTime; ++time) {
1676  double dtime = time - aveTime;
1677  sigsumt += signal[time - loTime] * dtime * dtime;
1678  }
1679  const float RMS = std::sqrt(sigsumt / sigsum);
1680  // find the amplitude from the integrated charge and the RMS
1681  const float amplitude = chgsum * chgNorm / (2.507 * RMS);
1682  // modify the hit "in place" (actually completely overwrite it...)
1683  // TODO a lot of these quantities need revamp!!
1684  fHits[theHit] = recob::Hit(hit.Channel(),
1685  hit.StartTick(),
1686  hit.EndTick(),
1687  aveTime, // peak_time
1688  hit.SigmaPeakTime(),
1689  RMS, // rms
1690  amplitude, // peak_amplitude
1691  hit.SigmaPeakAmplitude(),
1692  hit.ROISummedADC(),
1693  hit.HitSummedADC(),
1694  chgsum, // hit_integral
1695  hit.SigmaIntegral(),
1696  NewMultiplicity, // multiplicity
1697  0, // local index
1698  6666, // GoodnessOfFit (flag for merged hit)
1699  hit.DegreesOfFreedom(),
1700  hit.View(),
1701  hit.SignalType(),
1702  hit.WireID());
1703  FixMultipletLocalIndices(MultipletRange.first, MultipletRange.second);
1704  didMerge = true;
1705 
1706  } // MergeHits()
1707 
1709  void ClusterCrawlerAlg::FixMultipletLocalIndices(size_t begin,
1710  size_t end,
1711  short int multiplicity /* = -1 */)
1712  {
1713  //
1714  // Resets multiplicity and local index of the hits in the range.
1715  // All hits are assumed to be in the same multiplet.
1716  // All hits that are not obsolete are given a multiplicity equal to the
1717  // number of non-obsolete hits in the multiplet, and the local index is
1718  // assigned as an increasing number starting from 0 with the first
1719  // non-obsolete hit on.
1720  //
1721 
1722  // first pass: determine the actual number of hits in the multiplet
1723  if (multiplicity < 0) {
1724  multiplicity = 0;
1725  for (size_t iHit = begin; iHit < end; ++iHit) {
1726  if (inClus[iHit] < 0) continue;
1727  ++multiplicity;
1728  } // for
1729  } // if no valid multiplicity is given
1730 
1731  // second pass: assign the correct multiplicity
1732  short int local_index = 0;
1733  for (size_t iHit = begin; iHit < end; ++iHit) {
1734  if (inClus[iHit] < 0) continue;
1735 
1736  // copy everything but overwrite the local index and multiplicity
1737  // TODO use a write wrapper!
1738  recob::Hit const& hit = fHits[iHit];
1739  fHits[iHit] = recob::Hit(hit.Channel(),
1740  hit.StartTick(),
1741  hit.EndTick(),
1742  hit.PeakTime(),
1743  hit.SigmaPeakTime(),
1744  hit.RMS(),
1745  hit.PeakAmplitude(),
1746  hit.SigmaPeakAmplitude(),
1747  hit.ROISummedADC(),
1748  hit.HitSummedADC(),
1749  hit.Integral(),
1750  hit.SigmaIntegral(),
1751  multiplicity, // multiplicity
1752  local_index, // local index
1753  hit.GoodnessOfFit(),
1754  hit.DegreesOfFreedom(),
1755  hit.View(),
1756  hit.SignalType(),
1757  hit.WireID());
1758 
1759  ++local_index;
1760  } // for
1761 
1762  } // FixMultipletLocalIndices()
1763 
1765  void ClusterCrawlerAlg::FindStarVertices()
1766  {
1767  // Make 2D vertices with a star topology with short back-to-back
1768  // clusters. Vertices must reside on the US end of the longest
1769  // cluster, so vertex finding uses the End information only.
1770  // This routine is called after all passes are completed
1771  // in the current CTP
1772  if (tcl.size() < 2) return;
1773 
1774  // This code has some issues...
1775  return;
1776 
1777  vtxprt = (fDebugPlane == (int)plane && fDebugHit < 0);
1778  if (vtxprt) {
1779  mf::LogVerbatim("CC") << "FindStarVertices";
1780  PrintClusters();
1781  }
1782 
1783  unsigned short vtxSizeIn = vtx.size();
1784 
1785  float fvw = 0.;
1786  float fvt = 0.;
1787  float dsl = 0, dth = 0;
1788  float es1 = 0, es2 = 0;
1789  float eth1 = 0, eth2 = 0;
1790  float bt1 = 0, bt2 = 0;
1791  float et1 = 0, et2 = 0;
1792  float bw1 = 0, bw2 = 0;
1793  float ew1 = 0, ew2 = 0;
1794  float lotime = 0, hitime = 0, nwirecut = 0;
1795  unsigned short tclsize = tcl.size();
1796  for (unsigned short it1 = 0; it1 < tclsize - 1; ++it1) {
1797  // ignore obsolete clusters
1798  if (tcl[it1].ID < 0) continue;
1799  if (tcl[it1].CTP != clCTP) continue;
1800  // long dead-straight cluster?
1801  if (tcl[it1].tclhits.size() > 100) {
1802  dth = tcl[it1].BeginAng - tcl[it1].EndAng;
1803  if (std::abs(dth) < 0.1) continue;
1804  }
1805  es1 = tcl[it1].EndSlp;
1806  eth1 = tcl[it1].EndAng;
1807  ew1 = tcl[it1].EndWir;
1808  et1 = tcl[it1].EndTim;
1809  bw1 = tcl[it1].BeginWir;
1810  bt1 = tcl[it1].BeginTim;
1811  for (unsigned short it2 = it1 + 1; it2 < tclsize; ++it2) {
1812  if (tcl[it2].ID < 0) continue;
1813  if (tcl[it2].CTP != clCTP) continue;
1814  // long dead-straight cluster?
1815  if (tcl[it2].tclhits.size() > 100) {
1816  dth = tcl[it2].BeginAng - tcl[it2].EndAng;
1817  if (std::abs(dth) < 0.05) continue;
1818  }
1819  es2 = tcl[it2].EndSlp;
1820  eth2 = tcl[it2].EndAng;
1821  ew2 = tcl[it2].EndWir;
1822  et2 = tcl[it2].EndTim;
1823  bw2 = tcl[it2].BeginWir;
1824  bt2 = tcl[it2].BeginTim;
1825  // require angle difference
1826  dth = std::abs(eth1 - eth2);
1827  if (dth < 0.3) continue;
1828  dsl = es2 - es1;
1829  fvw = (et1 - ew1 * es1 - et2 + ew2 * es2) / dsl;
1830  // intersection within the wire boundaries
1831  if (fvw < ew1 || fvw > bw1) continue;
1832  if (fvw < ew2 || fvw > bw2) continue;
1833  if (vtxprt)
1834  mf::LogVerbatim("CC") << "Chk clusters " << tcl[it1].ID << " " << tcl[it2].ID
1835  << " topo5 vtx wire " << fvw;
1836  // ensure that the intersection is close to the US end of the longer
1837  // cluster if it is more than 20 hits long
1838  if (tcl[it1].tclhits.size() > tcl[it2].tclhits.size()) {
1839  if (tcl[it1].tclhits.size() > 20) {
1840  nwirecut = 0.5 * tcl[it1].tclhits.size();
1841  if ((fvw - ew1) > nwirecut) continue;
1842  }
1843  }
1844  else {
1845  if (tcl[it2].tclhits.size() > 20) {
1846  nwirecut = 0.5 * tcl[it2].tclhits.size();
1847  if ((fvw - ew2) > nwirecut) continue;
1848  }
1849  }
1850  fvt = et1 + (fvw - ew1) * es1;
1851  // and time boundaries
1852  lotime = 9999;
1853  if (et1 < lotime) lotime = et1;
1854  if (bt1 < lotime) lotime = bt1;
1855  if (et2 < lotime) lotime = et2;
1856  if (bt2 < lotime) lotime = bt2;
1857  hitime = 0;
1858  if (et1 > hitime) hitime = et1;
1859  if (bt1 > hitime) hitime = bt1;
1860  if (et2 > hitime) hitime = et2;
1861  if (bt2 > hitime) hitime = bt2;
1862  if (fvt < lotime || fvt > hitime) continue;
1863  if (vtxprt)
1864  mf::LogVerbatim("CC") << " vertex time " << fvt << " lotime " << lotime << " hitime "
1865  << hitime;
1866  unsigned int vw = (0.5 + fvw);
1867  // ensure that the vertex is near a hit on both clusters
1868  unsigned int pos1 = 0;
1869  for (unsigned short ii = 0; ii < tcl[it1].tclhits.size(); ++ii) {
1870  if (fHits[tcl[it1].tclhits[ii]].WireID().Wire <= vw) {
1871  if (std::abs(int(fHits[tcl[it1].tclhits[ii]].PeakTime() - fvt)) < 10) pos1 = ii;
1872  break;
1873  }
1874  } // ii
1875  // vertex is not near a hit on cluster 1
1876  if (pos1 == 0) continue;
1877  unsigned short pos2 = 0;
1878  for (unsigned short ii = 0; ii < tcl[it2].tclhits.size(); ++ii) {
1879  if (fHits[tcl[it2].tclhits[ii]].WireID().Wire <= vw) {
1880  if (std::abs(int(fHits[tcl[it2].tclhits[ii]].PeakTime() - fvt)) < 10) pos2 = ii;
1881  break;
1882  }
1883  } // ii
1884  // vertex is not near a hit on cluster 2
1885  if (pos2 == 0) continue;
1886  // ensure we aren't near an existing vertex
1887  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
1888  if (std::abs(fvw - vtx[iv].Wire) < 2 && std::abs(fvt - vtx[iv].Time) < 10) continue;
1889  }
1890  // make a new vertex
1891  VtxStore newvx;
1892  newvx.Wire = fvw;
1893  newvx.WireErr = 1;
1894  newvx.Time = fvt;
1895  newvx.TimeErr = 1;
1896  newvx.Topo = 5;
1897  newvx.CTP = clCTP;
1898  newvx.Fixed = false;
1899  vtx.push_back(newvx);
1900  unsigned short ivx = vtx.size() - 1;
1901  if (vtxprt)
1902  mf::LogVerbatim("CC") << " new vertex " << ivx << " cluster " << tcl[it1].ID
1903  << " split pos " << pos1;
1904  if (!SplitCluster(it1, pos1, ivx)) continue;
1905  tcl[tcl.size() - 1].ProcCode += 1000;
1906  tcl[tcl.size() - 2].ProcCode += 1000;
1907  if (vtxprt)
1908  mf::LogVerbatim("CC") << " new vertex " << ivx << " cluster " << tcl[it2].ID
1909  << " split pos " << pos2;
1910  if (!SplitCluster(it2, pos2, ivx)) continue;
1911  tcl[tcl.size() - 1].ProcCode += 1000;
1912  tcl[tcl.size() - 2].ProcCode += 1000;
1913  FitVtx(ivx);
1914  break;
1915  } // it2
1916  } // it1
1917 
1918  if (vtx.size() > vtxSizeIn) {
1919  // try to split other clusters
1920  VtxClusterSplit();
1921  // try to attach other clusters to it
1922  VertexCluster(vtx.size() - 1);
1923  FitAllVtx(clCTP);
1924  } // new vertex
1925 
1926  if (vtxprt) {
1927  mf::LogVerbatim("CC") << "Vertices " << vtx.size();
1928  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
1929  if (vtx[iv].CTP != clCTP) continue;
1930  mf::LogVerbatim("CC") << "vtx " << iv << " wire " << vtx[iv].Wire << " time "
1931  << (int)vtx[iv].Time << " NClusters " << vtx[iv].NClusters << " topo "
1932  << vtx[iv].Topo;
1933  }
1934  PrintClusters();
1935  }
1936 
1937  } // FindStarVertices()
1938 
1940  void ClusterCrawlerAlg::VertexCluster(unsigned short iv)
1941  {
1942  // try to attach clusters to the specified vertex
1943  if (vtx[iv].NClusters == 0) return;
1944 
1945  short dwb, dwe, dtb, dte;
1946  bool sigOK;
1947 
1948  for (unsigned short icl = 0; icl < tcl.size(); ++icl) {
1949  if (tcl[icl].ID < 0) continue;
1950  if (tcl[icl].CTP != vtx[iv].CTP) continue;
1951 
1952  dwb = vtx[iv].Wire - tcl[icl].BeginWir;
1953  dtb = vtx[iv].Time - tcl[icl].BeginTim;
1954  dwe = vtx[iv].Wire - tcl[icl].EndWir;
1955  dte = vtx[iv].Time - tcl[icl].EndTim;
1956 
1957  float drb = dwb * dwb + dtb * dtb;
1958  float dre = dwe * dwe + dte * dte;
1959 
1960  bool bCloser = (drb < dre);
1961 
1962  // ignore clusters in showers
1963  if (bCloser) {
1964  if (tcl[icl].BeginChgNear > fChgNearCut) continue;
1965  }
1966  else {
1967  if (tcl[icl].EndChgNear > fChgNearCut) continue;
1968  }
1969 
1970  if (vtxprt)
1971  mf::LogVerbatim("CC") << "VertexCluster: Try icl ID " << tcl[icl].ID << " w vtx " << iv
1972  << " dwb " << dwb << " dwe " << dwe << " drb " << drb << " dre "
1973  << dre << " Begin closer? " << bCloser;
1974 
1975  if (tcl[icl].BeginVtx < 0 && bCloser && dwb > -3 && dwb < 3 && tcl[icl].EndVtx != iv) {
1976  sigOK = ChkSignal(tcl[icl].BeginWir, tcl[icl].BeginTim, vtx[iv].Wire, vtx[iv].Time);
1977  if (vtxprt)
1978  mf::LogVerbatim("CC") << " Attach cluster Begin to vtx? " << iv << " sigOK " << sigOK;
1979  if (sigOK) {
1980  if (vtxprt)
1981  mf::LogVerbatim("CC") << " check ClusterVertexChi " << ClusterVertexChi(icl, 0, iv);
1982  if (ClusterVertexChi(icl, 0, iv) < fVertex2DCut) {
1983  // do a fit and check the vertex error
1984  tcl[icl].BeginVtx = iv;
1985  FitVtx(iv);
1986  if (vtx[iv].ChiDOF > fVertex2DCut || vtx[iv].WireErr > fVertex2DWireErrCut) {
1987  tcl[icl].BeginVtx = -99;
1988  FitVtx(iv);
1989  }
1990  } // good DoCA
1991  } // sigOK
1992  } // check BEGIN
1993 
1994  if (tcl[icl].EndVtx < 0 && !bCloser && dwe > -3 && dwe < 3 && tcl[icl].BeginVtx != iv) {
1995  sigOK = ChkSignal(tcl[icl].EndWir, tcl[icl].EndTim, vtx[iv].Wire, vtx[iv].Time);
1996  if (vtxprt)
1997  mf::LogVerbatim("CC") << " Attach cluster End to vtx? " << iv << " sigOK " << sigOK;
1998  if (sigOK) {
1999  if (vtxprt)
2000  mf::LogVerbatim("CC") << " check ClusterVertexChi " << ClusterVertexChi(icl, 1, iv);
2001  if (ClusterVertexChi(icl, 1, iv) < 3) {
2002  // do a fit and check the vertex error
2003  tcl[icl].EndVtx = iv;
2004  FitVtx(iv);
2005  if (vtx[iv].ChiDOF > fVertex2DCut || vtx[iv].WireErr > fVertex2DWireErrCut) {
2006  tcl[icl].EndVtx = -99;
2007  FitVtx(iv);
2008  }
2009  } // good DoCA
2010  } // sigOK
2011  } // check END
2012  } // icl
2013  } // VertexCluster
2014 
2016  void ClusterCrawlerAlg::FitAllVtx(CTP_t inCTP)
2017  {
2018 
2019  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
2020  if (vtx[iv].CTP != inCTP) continue;
2021  FitVtx(iv);
2022  }
2023 
2024  } // FitAllVtx
2025 
2027  void ClusterCrawlerAlg::FindVertices()
2028  {
2029  // try to make 2D vertices
2030 
2031  if (tcl.size() < 2) return;
2032 
2033  // sort clusters starting with the longest
2034  std::vector<CluLen> clulens;
2035  CluLen clulen;
2036  for (unsigned short icl = 0; icl < tcl.size(); ++icl) {
2037  if (tcl[icl].ID < 0) continue;
2038  if (tcl[icl].CTP != clCTP) continue;
2039  if (tcl[icl].BeginVtx >= 0 && tcl[icl].EndVtx >= 0) continue;
2040  clulen.index = icl;
2041  clulen.length = tcl[icl].tclhits.size();
2042  clulens.push_back(clulen);
2043  }
2044  if (empty(clulens)) return;
2045  std::sort(clulens.begin(), clulens.end(), greaterThan);
2046 
2047  float nwires = fNumWires;
2048  float maxtime = fMaxTime;
2049 
2050  unsigned short vtxSizeIn = vtx.size();
2051 
2052  vtxprt = (fDebugPlane == (short)plane && fDebugHit < 0);
2053  if (vtxprt) {
2054  mf::LogVerbatim("CC") << "FindVertices plane " << plane << " pass " << pass;
2055  PrintClusters();
2056  }
2057 
2058  float es1 = 0, eth1 = 0, ew1 = 0, et1 = 0;
2059  float bs1 = 0, bth1 = 0, bw1 = 0, bt1 = 0;
2060  float es2 = 0, eth2 = 0, ew2 = 0, et2 = 0;
2061  float bs2 = 0, bth2 = 0, bw2 = 0, bt2 = 0;
2062  float dth = 0, dsl = 0, fvw = 0, fvt = 0;
2063  float angcut = 0;
2064  unsigned int vw = 0, pass1, pass2, ii1, it1, ii2, it2;
2065  bool SigOK = false;
2066  for (ii1 = 0; ii1 < clulens.size() - 1; ++ii1) {
2067  it1 = clulens[ii1].index;
2068  es1 = tcl[it1].EndSlp;
2069  eth1 = tcl[it1].EndAng;
2070  ew1 = tcl[it1].EndWir;
2071  et1 = tcl[it1].EndTim;
2072  bs1 = tcl[it1].BeginSlp;
2073  bth1 = tcl[it1].BeginAng;
2074  bw1 = tcl[it1].BeginWir;
2075  bt1 = tcl[it1].BeginTim;
2076  pass1 = tcl[it1].ProcCode - 10 * (tcl[it1].ProcCode / 10);
2077  for (ii2 = ii1 + 1; ii2 < clulens.size(); ++ii2) {
2078  it2 = clulens[ii2].index;
2079  // try to attach cluster to existing vertices at either end
2080  ClusterVertex(it2);
2081  // ignore if both clusters are short
2082  if (tcl[it1].tclhits.size() < 5 && tcl[it2].tclhits.size() < 5) continue;
2083  es2 = tcl[it2].EndSlp;
2084  eth2 = tcl[it2].EndAng;
2085  ew2 = tcl[it2].EndWir;
2086  et2 = tcl[it2].EndTim;
2087  bs2 = tcl[it2].BeginSlp;
2088  bth2 = tcl[it2].BeginAng;
2089  bw2 = tcl[it2].BeginWir;
2090  bt2 = tcl[it2].BeginTim;
2091  pass2 = tcl[it2].ProcCode - 10 * (tcl[it2].ProcCode / 10);
2092  if (pass1 < pass2) { angcut = fKinkAngCut[pass2]; }
2093  else {
2094  angcut = fKinkAngCut[pass1];
2095  }
2096  // topo 1: check for vtx US of the ends of both clusters
2097  dth = fabs(eth1 - eth2);
2098  if (tcl[it1].EndVtx < 0 && tcl[it2].EndVtx < 0 && dth > 0.1) {
2099  dsl = es2 - es1;
2100  // find vertex wire and vertex time in float precision (fvw, fvt)
2101  fvw = (et1 - ew1 * es1 - et2 + ew2 * es2) / dsl;
2102  // vertex wire in the detector?
2103  if (fvw > 0. && fvw < nwires) {
2104  // require vtx in the range of wires with hits AND
2105  vw = (0.5 + fvw);
2106  // vtx US of both clusters AND
2107  // vtx not too far US of both clusters
2108  if (vw >= fFirstWire - 1 && fvw <= ew1 + 3 && fvw <= ew2 + 3 && fvw > (ew1 - 10) &&
2109  fvw > (ew2 - 10)) {
2110  fvt = et1 + (fvw - ew1) * es1;
2111  if (vtxprt)
2112  mf::LogVerbatim("CC")
2113  << "Chk clusters topo1 " << tcl[it1].ID << " " << tcl[it2].ID << " vtx wire "
2114  << vw << " time " << (int)fvt << " dth " << dth;
2115  if (fvt > 0 && fvt < maxtime) {
2116  // Vertex wire US of cluster ends and time in the detector.
2117  // Check for signal at the vertex position and adjust the vertex by 1 wire
2118  // if necessary
2119  SigOK = ChkSignal(vw, fvt, vw, fvt);
2120  if (!SigOK) {
2121  fvw += 1.;
2122  vw = (0.5 + fvw);
2123  SigOK = ChkSignal(vw, fvt, vw, fvt);
2124  }
2125  // Check this against existing vertices and update
2126  if (SigOK) ChkVertex(fvw, fvt, it1, it2, 1);
2127  } // fvt in detector
2128  } // vw topo 1 check
2129  } // fvw in detector
2130  } // topo 1
2131  // topo 2: check for vtx US of it1 and DS of it2
2132  dth = std::abs(eth1 - bth2);
2133  if (tcl[it1].EndVtx < 0 && tcl[it2].BeginVtx < 0 && dth > angcut) {
2134  dsl = bs2 - es1;
2135  if (fabs(ew1 - bw2) < 3 && fabs(et1 - bt2) < 500) { fvw = 0.5 * (ew1 + bw2); }
2136  else {
2137  fvw = (et1 - ew1 * es1 - bt2 + bw2 * bs2) / dsl;
2138  }
2139  if (fvw > 0 && fvw < nwires) {
2140  // vertex wire in the detector
2141  vw = (0.5 + fvw);
2142  // require vtx US of cluster 1 End AND
2143  // vtx DS of cluster 2 Begin
2144  if (fvw <= ew1 + 2 && fvw >= bw2 - 2) {
2145  fvt = et1 + (vw - ew1) * es1;
2146  fvt += bt2 + (vw - bw2) * bs2;
2147  fvt /= 2;
2148  if (vtxprt)
2149  mf::LogVerbatim("CC")
2150  << "Chk clusters topo2 " << tcl[it1].ID << " " << tcl[it2].ID << " vtx wire "
2151  << vw << " time " << (int)fvt << " dth " << dth;
2152  if (fvt > 0. && fvt < maxtime) {
2153  ChkVertex(fvw, fvt, it1, it2, 2);
2154  } // fvt in detector
2155  } // vw topo 2 check
2156  } // fvw in detector
2157  } // topo 2
2158  // topo 3: check for vtx DS of it1 and US of it2
2159  dth = std::abs(bth1 - eth2);
2160  if (tcl[it1].BeginVtx < 0 && tcl[it2].EndVtx < 0 && dth > angcut) {
2161  dsl = bs1 - es2;
2162  if (fabs(bw1 - ew2) < 3 && fabs(bt1 - et2) < 500) { fvw = 0.5 * (bw1 + ew2); }
2163  else {
2164  fvw = (et2 - ew2 * es2 - bt1 + bw1 * bs1) / dsl;
2165  }
2166  if (fvw > 0 && fvw < nwires) {
2167  vw = (0.5 + fvw);
2168  // require vtx US of cluster 2 Begin AND
2169  // vtx DS of cluster 1 End
2170  if (fvw <= ew2 + 2 && fvw >= bw1 - 2) {
2171  fvt = et2 + (fvw - ew2) * es2;
2172  fvt += bt1 + (fvw - bw1) * es1;
2173  fvt /= 2;
2174  if (vtxprt)
2175  mf::LogVerbatim("CC")
2176  << "Chk clusters topo3 " << tcl[it1].ID << " " << tcl[it2].ID << " vtx wire "
2177  << vw << " time " << (int)fvt << " dth " << dth;
2178  if (fvt > 0. && fvt < maxtime) {
2179  ChkVertex(fvw, fvt, it1, it2, 3);
2180  } // fvt in detector
2181  } // vw topo 3 check
2182  } // fvw in detector
2183  } // topo 3
2184  // topo 4: check for vtx DS of it1 and DS of it2
2185  dth = std::abs(bth1 - bth2);
2186  if (tcl[it1].BeginVtx < 0 && tcl[it2].BeginVtx < 0 && dth > 0.1) {
2187  dsl = bs2 - bs1;
2188  // find vertex wire and vertex time in float precision (fvw, fvt)
2189  // convert to integer if within the detector (vw, vt)
2190  fvw = (bt1 - bw1 * bs1 - bt2 + bw2 * bs2) / dsl;
2191  if (vtxprt)
2192  mf::LogVerbatim("CC") << "Chk clusters topo4 " << bw1 << ":" << (int)bt1 << " " << bw2
2193  << ":" << (int)bt2 << " fvw " << fvw << " nwires " << nwires;
2194  if (fvw > 0 && fvw < nwires) {
2195  // vertex wire in the detector
2196  vw = (0.5 + fvw);
2197  // require vtx in the range of wires with hits AND vtx DS of both clusters AND
2198  // vtx not too far DS of both clusters
2199  float dwcut = 10;
2200  if (tcl[it1].tclhits.size() < 2 * dwcut) dwcut = tcl[it1].tclhits.size() / 2;
2201  if (tcl[it2].tclhits.size() < 2 * dwcut) dwcut = tcl[it2].tclhits.size() / 2;
2202  if (fvw <= fLastWire + 1 && fvw >= bw1 - dwcut && fvw <= bw1 + dwcut &&
2203  fvw >= bw2 - dwcut && fvw <= bw1 + dwcut) {
2204  fvt = bt1 + (fvw - bw1) * bs1;
2205  if (vtxprt)
2206  mf::LogVerbatim("CC")
2207  << " vtx wire " << vw << " time " << fvt << " dth " << dth << " dwcut " << dwcut;
2208  if (fvt > 0. && fvt < maxtime) {
2209  // vertex wire US of cluster ends and time in the detector
2210  // Check for signal at the vertex position and adjust the vertex by 1 wire
2211  // if necessary
2212  SigOK = ChkSignal(vw, fvt, vw, fvt);
2213  if (!SigOK) {
2214  fvw -= 1.;
2215  vw = (0.5 + fvw);
2216  SigOK = ChkSignal(vw, fvt, vw, fvt);
2217  }
2218  // Check this against existing vertices and update
2219  if (SigOK) ChkVertex(fvw, fvt, it1, it2, 4);
2220  } // fvt in detector
2221  } // vw topo 4 check
2222  } // fvw in detector
2223  } // topo4
2224  } // it2
2225  } // it1
2226 
2227  // Fix vertices where both ends of a cluster are assigned to the
2228  // same vertex. This can happen with short clusters
2229  for (unsigned short it = 0; it < tcl.size(); ++it) {
2230  if (tcl[it].ID < 0) continue;
2231  if (tcl[it].CTP != clCTP) continue;
2232  if (tcl[it].BeginVtx > -1 && tcl[it].BeginVtx == tcl[it].EndVtx) {
2233  unsigned short iv = tcl[it].BeginVtx;
2234  float dwir = tcl[it].BeginWir - vtx[iv].Wire;
2235  float dtim = tcl[it].BeginTim - vtx[iv].Time;
2236  float rbeg = dwir * dwir + dtim * dtim;
2237  dwir = tcl[it].EndWir - vtx[iv].Wire;
2238  dtim = tcl[it].EndTim - vtx[iv].Time;
2239  float rend = dwir * dwir + dtim * dtim;
2240  if (rend < rbeg) { tcl[it].EndVtx = -99; }
2241  else {
2242  tcl[it].BeginVtx = -99;
2243  }
2244  } // tcl[it].BeginVtx == tcl[it].EndVtx
2245  } // it
2246 
2247  if (vtx.size() > vtxSizeIn) FitAllVtx(clCTP);
2248 
2249  // "delete" any vertices that have only one cluster
2250  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
2251  if (vtx[ivx].CTP != clCTP) continue;
2252  if (vtx[ivx].NClusters == 1) {
2253  vtx[ivx].NClusters = 0;
2254  for (unsigned short icl = 0; icl < tcl.size(); ++icl) {
2255  if (tcl[icl].CTP != clCTP) continue;
2256  if (tcl[icl].BeginVtx == ivx) {
2257  tcl[icl].BeginVtx = -99;
2258  break;
2259  }
2260  if (tcl[icl].EndVtx == ivx) {
2261  tcl[icl].EndVtx = -99;
2262  break;
2263  }
2264  } // icl
2265  } // vtx[ivx].NClusters == 1
2266  } // ivx
2267 
2268  } // FindVertices()
2269 
2271  void ClusterCrawlerAlg::ClusterVertex(unsigned short it)
2272  {
2273  // try to attach cluster it to an existing vertex
2274 
2275  if (vtx.size() == 0) return;
2276 
2277  unsigned short iv, jv;
2278  short dwib, dwjb, dwie, dwje;
2279  bool matchEnd, matchBegin;
2280 
2281  for (iv = 0; iv < vtx.size(); ++iv) {
2282  // ignore vertices in the wrong cryostat/TPC/Plane
2283  if (vtx[iv].CTP != clCTP) continue;
2284  // ignore deleted vertices
2285  if (vtx[iv].NClusters == 0) continue;
2286  // determine which end to match - begin or end. Handle short tracks
2287  matchEnd = false;
2288  matchBegin = false;
2289  if (tcl[it].tclhits.size() < 6) {
2290  // See which end is closer to this vertex vs other vertices
2291  dwib = std::abs(vtx[iv].Wire - tcl[it].BeginWir);
2292  if (dwib > 2) dwib = 2;
2293  dwie = std::abs(vtx[iv].Wire - tcl[it].EndWir);
2294  if (dwie > 2) dwie = 2;
2295  dwjb = 999;
2296  dwje = 999;
2297  for (jv = 0; jv < vtx.size(); ++jv) {
2298  if (iv == jv) continue;
2299  if (std::abs(vtx[jv].Time - tcl[it].BeginTim) < 50) {
2300  if (std::abs(vtx[jv].Wire - tcl[it].BeginWir) < dwjb)
2301  dwjb = std::abs(vtx[jv].Wire - tcl[it].BeginWir);
2302  } // std::abs(vtx[jv].Time - tcl[it].BeginTim) < 50
2303  if (std::abs(vtx[jv].Time - tcl[it].EndTim) < 50) {
2304  if (std::abs(vtx[jv].Wire - tcl[it].EndWir) < dwje)
2305  dwje = std::abs(vtx[jv].Wire - tcl[it].EndWir);
2306  } // std::abs(vtx[jv].Time - tcl[it].EndTim) < 50
2307  } // jv
2308  matchEnd = tcl[it].EndVtx != iv && dwie < 3 && dwie < dwje && dwie < dwib;
2309  matchBegin = tcl[it].BeginVtx != iv && dwib < 3 && dwib < dwjb && dwib < dwie;
2310  }
2311  else {
2312  matchEnd = tcl[it].EndVtx < 0 && vtx[iv].Wire <= tcl[it].EndWir + 2;
2313  matchBegin = tcl[it].BeginVtx < 0 && vtx[iv].Wire >= tcl[it].BeginWir - 2;
2314  }
2315  if (matchEnd) {
2316  if (vtxprt)
2317  mf::LogVerbatim("CC") << " Match End chi " << ClusterVertexChi(it, 1, iv) << " to vtx "
2318  << iv << " signalOK "
2319  << ChkSignal(
2320  vtx[iv].Wire, vtx[iv].Time, tcl[it].EndWir, tcl[it].EndTim);
2321  if (ClusterVertexChi(it, 1, iv) < fVertex2DCut &&
2322  ChkSignal(vtx[iv].Wire, vtx[iv].Time, tcl[it].EndWir, tcl[it].EndTim)) {
2323  // good match
2324  tcl[it].EndVtx = iv;
2325  // re-fit it
2326  FitVtx(iv);
2327  if (vtxprt)
2328  mf::LogVerbatim("CC") << " Add End " << tcl[it].ID << " to vtx " << iv << " NClusters "
2329  << vtx[iv].NClusters;
2330  if (vtx[iv].ChiDOF < fVertex2DCut && vtx[iv].WireErr < fVertex2DWireErrCut) return;
2331  if (vtxprt)
2332  mf::LogVerbatim("CC") << " Bad fit. ChiDOF " << vtx[iv].ChiDOF << " WireErr "
2333  << vtx[iv].WireErr << " Undo it";
2334  tcl[it].EndVtx = -99;
2335  FitVtx(iv);
2336  } // tChi < 3
2337  } // matchEnd
2338 
2339  if (matchBegin) {
2340  if (vtxprt)
2341  mf::LogVerbatim("CC") << " Match Begin chi " << ClusterVertexChi(it, 0, iv) << " to vtx "
2342  << iv << " signalOK "
2343  << ChkSignal(vtx[iv].Wire,
2344  vtx[iv].Time,
2345  tcl[it].BeginWir,
2346  tcl[it].BeginTim);
2347  if (ClusterVertexChi(it, 0, iv) < fVertex2DCut &&
2348  ChkSignal(vtx[iv].Wire, vtx[iv].Time, tcl[it].BeginWir, tcl[it].BeginTim)) {
2349  // good match
2350  tcl[it].BeginVtx = iv;
2351  // re-fit it
2352  FitVtx(iv);
2353  if (vtxprt)
2354  mf::LogVerbatim("CC") << " Add Begin " << tcl[it].ID << " to vtx " << iv
2355  << " NClusters " << vtx[iv].NClusters;
2356  if (vtx[iv].ChiDOF < fVertex2DCut && vtx[iv].WireErr < fVertex2DWireErrCut) return;
2357  if (vtxprt)
2358  mf::LogVerbatim("CC") << " Bad fit. ChiDOF " << vtx[iv].ChiDOF << " WireErr "
2359  << vtx[iv].WireErr << " Undo it";
2360  tcl[it].BeginVtx = -99;
2361  FitVtx(iv);
2362  } // tChi < 3
2363  } // matchBegin
2364  } // iv
2365  } // ClusterVertex()
2366 
2368  void ClusterCrawlerAlg::ChkVertex(float fvw,
2369  float fvt,
2370  unsigned short it1,
2371  unsigned short it2,
2372  short topo)
2373  {
2374  // Checks the vertex (vw, fvt) against the existing set of vertices.
2375  // If there a match, clusters it1 and/or it2 are associated with it
2376  // if there is signal between the existing vertex and the start of
2377  // the cluster. The topo flag indicates the type of vertex that was
2378  // found: 1 = US of it1 && US of it2, 2 = US of it1 && DS of it2,
2379  // 3 = DS of it1 && US of it2, 4 = DS of it1 and DS of it2.
2380  // didit is set true if a cluster is attached to a (new or old) vertex
2381 
2382  if (vtxprt)
2383  mf::LogVerbatim("CC") << " ChkVertex " << tcl[it1].EndWir << ":" << (int)tcl[it1].EndTim
2384  << " - " << tcl[it1].BeginWir << ":" << (int)tcl[it1].BeginTim
2385  << " and " << tcl[it2].EndWir << ":" << (int)tcl[it2].EndTim << " - "
2386  << tcl[it2].BeginWir << ":" << (int)tcl[it2].BeginTim << " topo "
2387  << topo << " fvw " << fvw << " fvt " << fvt;
2388 
2389  unsigned int vw = (unsigned int)(0.5 + fvw);
2390  unsigned int iht;
2391 
2392  // don't make vertices using short tracks that are close to
2393  // long straight clusters. These are most likely due to numerous
2394  // short delta ray clusters
2395  if (tcl[it1].tclhits.size() < 10 && tcl[it2].tclhits.size() < 10) {
2396  for (unsigned short it = 0; it < tcl.size(); ++it) {
2397  if (it == it1 || it == it2) continue;
2398  if (tcl[it].ID < 0) continue;
2399  if (tcl[it].CTP != clCTP) continue;
2400  if (tcl[it].tclhits.size() < 100) continue;
2401  if (std::abs(tcl[it].BeginAng - tcl[it].EndAng) > 0.1) continue;
2402  // don't reject because it is near the end of a long cluster
2403  if (vw < tcl[it].EndWir + 5) continue;
2404  if (vw > tcl[it].BeginWir - 5) continue;
2405  for (unsigned short ii = 0; ii < tcl[it].tclhits.size(); ++ii) {
2406  iht = tcl[it].tclhits[ii];
2407  if (fHits[iht].WireID().Wire <= vw) {
2408  if (std::abs(fHits[iht].PeakTime() - fvt) < 60) return;
2409  } // fHits[iht].WireID().Wire <= vWire
2410  } // ii
2411  } // it
2412  }
2413 
2414  // check vertex and clusters for proximity to existing vertices
2415  unsigned short nFitOK = 0;
2416  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
2417  if (vtx[iv].CTP != clCTP) continue;
2418  // make this a really loose cut since the errors on the prospective vertex aren't known yet
2419  if (PointVertexChi(fvw, fvt, iv) > 300) continue;
2420  if (vtxprt)
2421  mf::LogVerbatim("CC") << " vtx " << iv << " PointVertexChi "
2422  << PointVertexChi(fvw, fvt, iv);
2423  // got a match. Check the appropriate cluster end and attach
2424  if ((topo == 1 || topo == 2) && tcl[it1].EndVtx < 0) {
2425  if (ChkSignal(vw, fvt, tcl[it1].EndWir, tcl[it1].EndTim)) {
2426  // try to fit
2427  tcl[it1].EndVtx = iv;
2428  FitVtx(iv);
2429  if (vtxprt)
2430  mf::LogVerbatim("CC") << " FitVtx " << iv << " WireErr " << vtx[iv].WireErr
2431  << " ChiDOF " << vtx[iv].ChiDOF;
2432  if (vtx[iv].WireErr < fVertex2DWireErrCut && vtx[iv].ChiDOF < 5) { ++nFitOK; }
2433  else {
2434  // bad fit
2435  tcl[it1].EndVtx = -99;
2436  FitVtx(iv);
2437  } // check fit
2438  } // ChkSignal
2439  }
2440  else if ((topo == 3 || topo == 4) && tcl[it1].BeginVtx < 0) {
2441  if (ChkSignal(vw, fvt, tcl[it1].BeginWir, tcl[it1].BeginTim)) {
2442  tcl[it1].BeginVtx = iv;
2443  FitVtx(iv);
2444  if (vtxprt)
2445  mf::LogVerbatim("CC") << " FitVtx " << iv << " WireErr " << vtx[iv].WireErr
2446  << " ChiDOF " << vtx[iv].ChiDOF;
2447  if (vtx[iv].WireErr < fVertex2DWireErrCut && vtx[iv].ChiDOF < 5) { ++nFitOK; }
2448  else {
2449  // bad fit
2450  tcl[it1].BeginVtx = -99;
2451  FitVtx(iv);
2452  } // bad fit
2453  } // ChkSignal
2454  } // cluster it2
2455  if ((topo == 1 || topo == 3) && tcl[it2].EndVtx < 0) {
2456  if (ChkSignal(vw, fvt, tcl[it2].EndWir, tcl[it2].EndTim)) {
2457  tcl[it2].EndVtx = iv;
2458  FitVtx(iv);
2459  if (vtxprt)
2460  mf::LogVerbatim("CC") << " FitVtx " << iv << " WireErr " << vtx[iv].WireErr
2461  << " ChiDOF " << vtx[iv].ChiDOF;
2462  if (vtx[iv].WireErr < fVertex2DWireErrCut && vtx[iv].ChiDOF < 5) { ++nFitOK; }
2463  else {
2464  // bad fit
2465  tcl[it2].EndVtx = -99;
2466  FitVtx(iv);
2467  } // bad fit
2468  } // ChkSignal
2469  }
2470  else if ((topo == 2 || topo == 4) && tcl[it2].BeginVtx < 0) {
2471  if (ChkSignal(vw, fvt, tcl[it2].BeginWir, tcl[it2].BeginTim)) {
2472  tcl[it2].BeginVtx = iv;
2473  FitVtx(iv);
2474  if (vtxprt)
2475  mf::LogVerbatim("CC") << " FitVtx " << iv << " WireErr " << vtx[iv].WireErr
2476  << " ChiDOF " << vtx[iv].ChiDOF;
2477  if (vtx[iv].WireErr < fVertex2DWireErrCut && vtx[iv].ChiDOF < 5) { ++nFitOK; }
2478  else {
2479  // bad fit
2480  tcl[it2].BeginVtx = -99;
2481  FitVtx(iv);
2482  } // bad fit
2483  } // ChkSignal
2484  } // cluster it2
2485  if (nFitOK > 0) {
2486  if (vtxprt) mf::LogVerbatim("CC") << " Attached " << nFitOK << " clusters to vertex " << iv;
2487  return;
2488  }
2489  } // iv
2490 
2491  // no match to existing vertices. Ensure that there is a wire signal between
2492  // the vertex and the appropriate ends of the clusters
2493  bool SigOK = false;
2494  if (topo == 1 || topo == 2) { SigOK = ChkSignal(vw, fvt, tcl[it1].EndWir, tcl[it1].EndTim); }
2495  else {
2496  SigOK = ChkSignal(vw, fvt, tcl[it1].BeginWir, tcl[it1].BeginTim);
2497  }
2498  if (!SigOK) return;
2499 
2500  if (topo == 1 || topo == 3) { SigOK = ChkSignal(vw, fvt, tcl[it2].EndWir, tcl[it2].EndTim); }
2501  else {
2502  SigOK = ChkSignal(vw, fvt, tcl[it2].BeginWir, tcl[it2].BeginTim);
2503  }
2504  if (!SigOK) return;
2505 
2506  VtxStore newvx;
2507  newvx.Wire = vw;
2508  newvx.Time = fvt;
2509  newvx.Topo = topo;
2510  newvx.CTP = clCTP;
2511  newvx.Fixed = false;
2512  vtx.push_back(newvx);
2513  unsigned short iv = vtx.size() - 1;
2514  if (topo == 1 || topo == 2) {
2515  if (tcl[it1].EndVtx >= 0) {
2516  vtx.pop_back();
2517  return;
2518  }
2519  tcl[it1].EndVtx = iv;
2520  }
2521  else {
2522  if (tcl[it1].BeginVtx >= 0) {
2523  vtx.pop_back();
2524  return;
2525  }
2526  tcl[it1].BeginVtx = iv;
2527  }
2528  if (topo == 1 || topo == 3) {
2529  if (tcl[it2].EndVtx >= 0) {
2530  vtx.pop_back();
2531  return;
2532  }
2533  tcl[it2].EndVtx = iv;
2534  }
2535  else {
2536  if (tcl[it2].BeginVtx >= 0) {
2537  vtx.pop_back();
2538  return;
2539  }
2540  tcl[it2].BeginVtx = iv;
2541  }
2542  // fit it
2543  FitVtx(iv);
2544  // reject it if the fit is bad
2545  if (vtx[iv].ChiDOF < 5 && vtx[iv].WireErr < fVertex2DWireErrCut && vtx[iv].TimeErr < 20) {
2546  if (vtxprt)
2547  mf::LogVerbatim("CC") << " New vtx " << iv << " in plane " << plane << " topo " << topo
2548  << " cls " << tcl[it1].ID << " " << tcl[it2].ID << " W:T " << (int)vw
2549  << ":" << (int)fvt << " NClusters " << vtx[iv].NClusters;
2550  // Try to refine the cluster hits and vertex position
2551  if (fRefineVertexClusters) RefineVertexClusters(iv);
2552  }
2553  else {
2554  if (vtxprt)
2555  mf::LogVerbatim("CC") << " Bad vtx fit " << vtx[iv].ChiDOF << " wire err "
2556  << vtx[iv].WireErr << " TimeErr " << vtx[iv].TimeErr;
2557  // clobber the vertex and references to it
2558  vtx.pop_back();
2559  if (tcl[it1].BeginVtx == iv) tcl[it1].BeginVtx = -99;
2560  if (tcl[it1].EndVtx == iv) tcl[it1].EndVtx = -99;
2561  if (tcl[it2].BeginVtx == iv) tcl[it2].BeginVtx = -99;
2562  if (tcl[it2].EndVtx == iv) tcl[it2].EndVtx = -99;
2563  } // bad fit
2564 
2565  } // ChkVertex()
2566 
2568  bool ClusterCrawlerAlg::ChkSignal(unsigned int iht, unsigned int jht)
2569  {
2570  if (iht > fHits.size() - 1) return false;
2571  if (jht > fHits.size() - 1) return false;
2572  unsigned int wire1 = fHits[iht].WireID().Wire;
2573  float time1 = fHits[iht].PeakTime();
2574  unsigned int wire2 = fHits[jht].WireID().Wire;
2575  float time2 = fHits[jht].PeakTime();
2576  return ChkSignal(wire1, time1, wire2, time2);
2577  } // ChkSignal
2578 
2580  bool ClusterCrawlerAlg::ChkSignal(unsigned int wire1,
2581  float time1,
2582  unsigned int wire2,
2583  float time2)
2584  {
2585  // returns true if there is a signal on the line between
2586  // (wire1, time1) and (wire2, time2).
2587  // Be sure to set time1 < time2 if you are checking for signals on a single wire
2588 
2589  // Gaussian amplitude in bins of size 0.15
2590  const float gausAmp[20] = {1, 0.99, 0.96, 0.90, 0.84, 0.75, 0.67, 0.58, 0.49, 0.40,
2591  0.32, 0.26, 0.20, 0.15, 0.11, 0.08, 0.06, 0.04, 0.03, 0.02};
2592 
2593  // return true if fMinAmp is set ignore wire signal checking in this plane
2594  if (fMinAmp[plane] <= 0) return true;
2595 
2596  // get the begin and end right
2597  unsigned int wireb = wire1;
2598  float timeb = time1;
2599  unsigned int wiree = wire2;
2600  float timee = time2;
2601  // swap them?
2602  if (wiree > wireb) {
2603  wireb = wire2;
2604  timeb = time2;
2605  wiree = wire1;
2606  timee = time1;
2607  }
2608  if (wiree < fFirstWire || wiree > fLastWire) return false;
2609  if (wireb < fFirstWire || wireb > fLastWire) return false;
2610 
2611  int wire0 = wiree;
2612  // checking a single wire?
2613  float slope = 0;
2614  bool oneWire = false;
2615  float prTime, prTimeLo = 0, prTimeHi = 0;
2616  if (wireb == wiree) {
2617  oneWire = true;
2618  if (time1 < time2) {
2619  prTimeLo = time1;
2620  prTimeHi = time2;
2621  }
2622  else {
2623  prTimeLo = time2;
2624  prTimeHi = time1;
2625  }
2626  }
2627  else {
2628  slope = (timeb - timee) / (wireb - wiree);
2629  }
2630 
2631  int bin;
2632  unsigned short nmissed = 0;
2633  for (unsigned int wire = wiree; wire < wireb + 1; ++wire) {
2634  if (oneWire) { prTime = (prTimeLo + prTimeHi) / 2; }
2635  else {
2636  prTime = timee + (wire - wire0) * slope;
2637  }
2638  // skip dead wires
2639  if (WireHitRange[wire].first == -1) continue;
2640  // no hits on this wire
2641  if (WireHitRange[wire].first == -2) ++nmissed;
2642  unsigned int firsthit = WireHitRange[wire].first;
2643  unsigned int lasthit = WireHitRange[wire].second;
2644  float amp = 0;
2645  for (unsigned int khit = firsthit; khit < lasthit; ++khit) {
2646  if (oneWire) {
2647  // TODO: This sometimes doesn't work with overlapping hits
2648  // A not totally satisfactory solution
2649  if (prTime < fHits[khit].StartTick()) continue;
2650  if (prTime > fHits[khit].EndTick()) continue;
2651  return true;
2652  }
2653  else {
2654  // skip checking if we are far away from prTime on the positive side
2655  if (fHits[khit].PeakTime() - prTime > 500) continue;
2656  bin = std::abs(fHits[khit].PeakTime() - prTime) / fHits[khit].RMS();
2657  bin /= 0.15;
2658  if (bin > 19) continue;
2659  if (bin < 0) continue;
2660  // add amplitude from all hits
2661  amp += fHits[khit].PeakAmplitude() * gausAmp[bin];
2662  }
2663  } // khit
2664  if (amp < fMinAmp[plane]) ++nmissed;
2665  } // wire
2666  if (nmissed > fAllowNoHitWire) return false;
2667  return true;
2668 
2669  } // ChkSignal()
2670 
2672  bool ClusterCrawlerAlg::SplitCluster(unsigned short icl, unsigned short pos, unsigned short ivx)
2673  {
2674  // split cluster icl into two clusters
2675 
2676  if (tcl[icl].ID < 0) return false;
2677 
2678  // declare icl obsolete
2679  MakeClusterObsolete(icl);
2680 
2681  unsigned short ii, iclnew;
2682  unsigned int iht;
2683 
2684  if (pos > 2) {
2685  // Have enough hits to make a cluster at the Begin end
2686  // Create the first cluster (DS) using the Begin info
2687  clBeginSlp = tcl[icl].BeginSlp;
2688  clBeginSlpErr = tcl[icl].BeginSlpErr;
2689  clBeginAng = tcl[icl].BeginAng;
2690  clBeginWir = tcl[icl].BeginWir;
2691  clBeginTim = tcl[icl].BeginTim;
2692  clBeginChg = tcl[icl].BeginChg;
2693  clStopCode = 5;
2694  clProcCode = tcl[icl].ProcCode;
2695  fcl2hits.clear();
2696  chifits.clear();
2697  hitNear.clear();
2698  chgNear.clear();
2699  for (ii = 0; ii < pos; ++ii) {
2700  iht = tcl[icl].tclhits[ii];
2701  fcl2hits.push_back(iht);
2702  }
2703  // determine the pass in which this cluster was created
2704  pass = tcl[icl].ProcCode - 10 * (tcl[icl].ProcCode / 10);
2705  if (pass > fNumPass - 1) pass = fNumPass - 1;
2706  // fit the end hits
2707  FitCluster();
2708  clEndSlp = clpar[1];
2709  clEndSlpErr = clparerr[1];
2710  clEndAng = std::atan(fScaleF * clEndSlp);
2711  // find the charge at the end
2712  FitClusterChg();
2713  clEndChg = fAveChg;
2714  if (!TmpStore()) {
2715  RestoreObsoleteCluster(icl);
2716  return false;
2717  }
2718  // associate the End with the supplied vertex
2719  iclnew = tcl.size() - 1;
2720  tcl[iclnew].EndVtx = ivx;
2721  tcl[iclnew].BeginVtx = tcl[icl].BeginVtx;
2722  if (vtxprt)
2723  mf::LogVerbatim("CC") << "SplitCluster made cluster " << iclnew << " attached to Begin vtx "
2724  << ivx;
2725  } // pos > 2
2726 
2727  if (pos < tcl[icl].tclhits.size() - 3) {
2728  // have enough hits to make a cluster at the End
2729  // now create the second cluster (US)
2730  clEndSlp = tcl[icl].EndSlp;
2731  clEndSlpErr = tcl[icl].EndSlpErr;
2732  clEndAng = std::atan(fScaleF * clEndSlp);
2733  clEndWir = tcl[icl].EndWir;
2734  clEndTim = tcl[icl].EndTim;
2735  clEndChg = tcl[icl].EndChg;
2736  clStopCode = 5;
2737  clProcCode = tcl[icl].ProcCode;
2738  fcl2hits.clear();
2739  chifits.clear();
2740  hitNear.clear();
2741  chgNear.clear();
2742  bool didFit = false;
2743  for (ii = pos; ii < tcl[icl].tclhits.size(); ++ii) {
2744  iht = tcl[icl].tclhits[ii];
2745  if (inClus[iht] != 0) {
2746  RestoreObsoleteCluster(icl);
2747  return false;
2748  }
2749  fcl2hits.push_back(iht);
2750  // define the Begin parameters
2751  if (fcl2hits.size() == fMaxHitsFit[pass] || fcl2hits.size() == fMinHits[pass]) {
2752  FitCluster();
2753  clBeginSlp = clpar[1];
2754  clBeginAng = std::atan(fScaleF * clBeginSlp);
2755  clBeginSlpErr = clparerr[1];
2756  didFit = true;
2757  }
2758  if ((unsigned short)fcl2hits.size() == fNHitsAve[pass] + 1) {
2759  FitClusterChg();
2760  clBeginChg = fAveChg;
2761  didFit = true;
2762  }
2763  } // ii
2764  // do a fit using all hits if one wasn't done
2765  if (!didFit) {
2766  FitCluster();
2767  FitClusterChg();
2768  clBeginChg = fAveChg;
2769  }
2770  if (!TmpStore()) {
2771  // clobber the previously stored cluster
2772  MakeClusterObsolete(tcl.size() - 1);
2773  RestoreObsoleteCluster(icl);
2774  return false;
2775  }
2776  // associate the End with the supplied vertex
2777  iclnew = tcl.size() - 1;
2778  tcl[iclnew].BeginVtx = ivx;
2779  tcl[iclnew].EndVtx = tcl[icl].EndVtx;
2780  if (vtxprt)
2781  mf::LogVerbatim("CC") << "SplitCluster made cluster " << iclnew << " attached to End vtx "
2782  << ivx;
2783  }
2784 
2785  return true;
2786  } // SplitCluster()
2787 
2789  void ClusterCrawlerAlg::ChkMerge()
2790  {
2791  // Try to merge clusters. Clusters that have been subsumed in other
2792  // clusters, i.e. no longer valid, have ID < 0
2793 
2794  if (tcl.size() < 2) return;
2795  // The size of the ClusterStore vector will increase while merging
2796  // is on-going so the upper limit on it1 is fixed tcl.size() - 1
2797  // before merging starts
2798 
2799  prt = (fDebugPlane == (short)plane && fDebugWire < 0);
2800  if (prt) mf::LogVerbatim("CC") << "ChkMerge on pass " << pass;
2801 
2802  unsigned short it1, it2, nh1, pass1, pass2;
2803  float bs1, bth1, bt1, bc1, es1, eth1, et1, ec1;
2804  float bs2, bth2, bt2, bc2, es2, eth2, et2, ec2;
2805  int bw1, ew1, bw2, ew2, ndead;
2806  float dth, dw, angcut, chgrat, chgcut, dtim, timecut, bigslp;
2807  bool bothLong, NoVtx;
2808 
2809  int skipcut, tclsize = tcl.size();
2810  int maxOverlap = 3;
2811 
2812  for (it1 = 0; it1 < tclsize - 1; ++it1) {
2813  // ignore already merged clusters
2814  if (tcl[it1].ID < 0) continue;
2815  if (tcl[it1].CTP != clCTP) continue;
2816  bs1 = tcl[it1].BeginSlp;
2817  // convert slope to angle
2818  bth1 = std::atan(fScaleF * bs1);
2819  // more compact notation for begin/end, wire/time/chg/slp/theta, 1/2
2820  bw1 = tcl[it1].BeginWir;
2821  bt1 = tcl[it1].BeginTim;
2822  bc1 = tcl[it1].BeginChg;
2823  es1 = tcl[it1].EndSlp;
2824  eth1 = tcl[it1].EndAng;
2825  ew1 = tcl[it1].EndWir;
2826  et1 = tcl[it1].EndTim;
2827  ec1 = tcl[it1].EndChg;
2828  nh1 = tcl[it1].tclhits.size();
2829  pass1 = tcl[it1].ProcCode - 10 * (tcl[it1].ProcCode / 10);
2830  if (pass1 > fNumPass) pass1 = fNumPass;
2831  for (it2 = it1 + 1; it2 < tclsize; ++it2) {
2832  // ignore already merged clusters
2833  if (tcl[it1].ID < 0) continue;
2834  if (tcl[it2].ID < 0) continue;
2835  // only merge if they are in the right cryostat/TPC/plane
2836  if (tcl[it2].CTP != clCTP) continue;
2837  // Don't bother if these clusters, if merged, would fail the
2838  // cluster hit fraction cut
2839  if (!ChkMergedClusterHitFrac(it1, it2)) { continue; }
2840  bs2 = tcl[it2].BeginSlp;
2841  bth2 = std::atan(fScaleF * bs2);
2842  bw2 = tcl[it2].BeginWir;
2843  bt2 = tcl[it2].BeginTim;
2844  bc2 = tcl[it2].BeginChg;
2845  es2 = tcl[it2].EndSlp;
2846  eth2 = tcl[it2].EndAng;
2847  ew2 = tcl[it2].EndWir;
2848  et2 = tcl[it2].EndTim;
2849  ec2 = tcl[it2].EndChg;
2850  pass2 = tcl[it2].ProcCode - 10 * (tcl[it2].ProcCode / 10);
2851  if (pass2 > fNumPass) pass2 = fNumPass;
2852  // use the more promiscuous pass for cuts
2853  angcut = fKinkAngCut[pass1];
2854  if (fKinkAngCut[pass2] > angcut) angcut = fKinkAngCut[pass2];
2855  skipcut = fMaxWirSkip[pass1];
2856  if (fMaxWirSkip[pass2] > skipcut) skipcut = fMaxWirSkip[pass2];
2857  chgcut = fMergeChgCut[pass1];
2858  if (fMergeChgCut[pass2] > chgcut) chgcut = fMergeChgCut[pass2];
2859  // tweak the cuts for long straight tracks
2860  bothLong = (nh1 > 100 && tcl[it2].tclhits.size() > 100);
2861 
2862  // look for US and DS broken clusters w similar angle.
2863  // US cluster 2 merge with DS cluster 1?
2864  // This is the most likely occurrence given the order in which
2865  // clusters are created so put it first.
2866  dth = std::abs(bth2 - eth1);
2867  ndead = DeadWireCount(bw2, ew1);
2868  dw = ew1 - bw2 - ndead;
2869  // require no vertex between
2870  NoVtx = (tcl[it1].EndVtx < 0) && (tcl[it2].BeginVtx < 0);
2871  if (prt && bw2 < (ew1 + maxOverlap))
2872  mf::LogVerbatim("CC") << "Chk1 ID1-2 " << tcl[it1].ID << "-" << tcl[it2].ID << " " << ew1
2873  << ":" << (int)et1 << " " << bw2 << ":" << (int)bt2 << " dw " << dw
2874  << " ndead " << ndead << " skipcut " << skipcut << " dth " << dth
2875  << " angcut " << angcut;
2876  if (NoVtx && bw2 < (ew1 + maxOverlap) && dw < skipcut && dth < angcut) {
2877  chgrat = 2 * fabs(bc2 - ec1) / (bc2 + ec1);
2878  // ignore the charge cut for long tracks with small dth
2879  if (bothLong && dth < 0.05) chgrat = 0.;
2880  // project bw2,bt2 to ew1
2881  dtim = fabs(bt2 + (ew1 - bw2) * bs2 - et1);
2882  bigslp = std::abs(bs2);
2883  if (std::abs(es1) > bigslp) bigslp = std::abs(es1);
2884  timecut = fTimeDelta[pass2] * AngleFactor(bigslp);
2885  if (prt)
2886  mf::LogVerbatim("CC") << " dtim " << dtim << " timecut " << (int)timecut << " ec1 "
2887  << ec1 << " bc2 " << bc2 << " chgrat " << chgrat << " chgcut "
2888  << chgcut << " es1 " << es1 << " ChkSignal "
2889  << ChkSignal(ew1, et1, bw2, bt2);
2890  if (chgrat < chgcut && dtim < timecut) {
2891  // ensure there is a signal between cluster ends
2892  if (ChkSignal(ew1, et1, bw2, bt2)) {
2893  DoMerge(it2, it1, 10);
2894  tclsize = tcl.size();
2895  break;
2896  }
2897  } // chgrat < chgcut ...
2898  } // US cluster 2 merge with DS cluster 1?
2899 
2900  // look for US and DS broken clusters w similar angle
2901  // US cluster 1 merge with DS cluster 2?
2902  dth = fabs(bth1 - eth2);
2903  ndead = DeadWireCount(bw1, ew2);
2904  dw = ew2 - bw1 - ndead;
2905  if (prt && bw1 < (ew2 + maxOverlap) && dw < skipcut)
2906  mf::LogVerbatim("CC") << "Chk2 ID1-2 " << tcl[it1].ID << "-" << tcl[it2].ID << " " << bw1
2907  << ":" << (int)bt1 << " " << bw2 << ":" << (int)et2 << " dw " << dw
2908  << " ndead " << ndead << " skipcut " << skipcut << " dth " << dth
2909  << " angcut " << angcut;
2910  // require no vertex between
2911  NoVtx = (tcl[it2].EndVtx < 0) && (tcl[it1].BeginVtx < 0);
2912  if (NoVtx && bw1 < (ew2 + maxOverlap) && dw < skipcut && dth < angcut) {
2913  chgrat = 2 * fabs((bc1 - ec2) / (bc1 + ec2));
2914  // ignore the charge cut for long tracks with small dth
2915  if (bothLong && dth < 0.05) chgrat = 0.;
2916  // project sw1,st1 to ew2
2917  dtim = std::abs(bt1 + (ew2 - bw1) * bs1 - et2);
2918  bigslp = std::abs(bs1);
2919  if (std::abs(bs2) > bigslp) bigslp = std::abs(bs2);
2920  timecut = fTimeDelta[pass2] * AngleFactor(bigslp);
2921  if (prt)
2922  mf::LogVerbatim("CC") << " dtim " << dtim << " err " << dtim << " timecut "
2923  << (int)timecut << " chgrat " << chgrat << " chgcut " << chgcut
2924  << " ChkSignal " << ChkSignal(bw1, bt1, ew2, et2);
2925  // TODO: we should be checking for a signal here like we did above
2926  if (chgrat < chgcut && dtim < timecut) {
2927  if (ChkSignal(bw1, bt1, ew2, et2)) {
2928  DoMerge(it1, it2, 10);
2929  tclsize = tcl.size();
2930  break;
2931  }
2932  } // chgrat < chgcut ...
2933  } // US cluster 1 merge with DS cluster 2
2934 
2935  if (bw2 < bw1 && ew2 > ew1) {
2936  // look for small cl2 within the wire boundary of cl1
2937  // with similar times and slopes for both clusters
2938  dth = fabs(eth2 - eth1);
2939  dtim = fabs(et1 + (ew2 - ew1 - 1) * es1 - et2);
2940  bigslp = std::abs(es1);
2941  if (std::abs(es1) > bigslp) bigslp = std::abs(es1);
2942  timecut = fTimeDelta[pass2] * AngleFactor(bigslp);
2943  // count the number of wires with no hits on cluster 1
2944  short nmiss1 = bw1 - ew1 + 1 - tcl[it1].tclhits.size();
2945  // compare with the number of hits in cluster 2
2946  short nin2 = tcl[it2].tclhits.size();
2947  if (prt)
2948  mf::LogVerbatim("CC") << "cl2: " << ew2 << ":" << (int)et2 << " - " << bw2 << ":"
2949  << (int)bt2 << " within cl1 " << ew1 << ":" << (int)et1 << " - "
2950  << bw1 << ":" << (int)bt1 << " ? dth " << dth << " dtim " << dtim
2951  << " nmissed " << nmiss1 << " timecut " << timecut
2952  << " FIX THIS CODE";
2953  // make rough cuts before calling ChkMerge12
2954  // this may not work well for long wandering clusters
2955  // TODO fix this code
2956  bool didit = false;
2957  if (dth < 1 && dtim < timecut && nmiss1 >= nin2) ChkMerge12(it1, it2, didit);
2958  if (didit) {
2959  tclsize = tcl.size();
2960  break;
2961  } //didit
2962  } // small cl2 within the wire boundary of cl1
2963 
2964  if (bw1 < bw2 && ew1 > ew2) {
2965  // look for small cl1 within the wire boundary of cl2
2966  // with similar times and slopes for both clusters
2967  dth = std::abs(eth2 - eth1);
2968  dtim = std::abs(et2 + (ew1 - ew2 - 1) * es2 - et1);
2969  bigslp = std::abs(es1);
2970  if (std::abs(es1) > bigslp) bigslp = std::abs(es1);
2971  timecut = fTimeDelta[pass2] * AngleFactor(bigslp);
2972  // count the number of wires with no hits on cluster 2
2973  short nmiss2 = bw2 - ew2 + 1 - tcl[it2].tclhits.size();
2974  // compare with the number of hits in cluster 1
2975  short nin1 = tcl[it1].tclhits.size();
2976  if (prt)
2977  mf::LogVerbatim("CC") << "cl1: " << ew1 << ":" << (int)et1 << " - " << bw1 << ":"
2978  << (int)bt1 << " within cl2 " << ew2 << ":" << (int)et2 << " - "
2979  << bw2 << ":" << (int)bt2 << " ? dth " << dth << " dtim " << dtim
2980  << " nmissed " << nmiss2 << " timecut " << timecut
2981  << " FIX THIS CODE";
2982  // make rough cuts before calling ChkMerge12
2983  // this may not work well for long wandering clusters
2984  bool didit = false;
2985  if (dth < 1 && dtim < timecut && nmiss2 >= nin1) ChkMerge12(it2, it1, didit);
2986  if (didit) {
2987  tclsize = tcl.size();
2988  break;
2989  } // didit
2990  } // small cl1 within the wire boundary of cl2
2991 
2992  if (tcl[it1].ID < 0) break;
2993  } // cluster 2
2994  if (tcl[it1].ID < 0) continue;
2995  } // cluster 1
2996  }
2997 
2999  void ClusterCrawlerAlg::ChkMerge12(unsigned short it1, unsigned short it2, bool& didit)
3000  {
3001  // Calling routine has done a rough check that cluster it2 is a candidate
3002  // for merging with cluster it1. The wire range spanned by it2 lies
3003  // within the wire range of it1 and the clusters are reasonably close
3004  // together in time.
3005 
3006  // assume that no merging was done
3007  didit = false;
3008 
3009  if (prt) mf::LogVerbatim("CC") << "ChkMerge12 " << tcl[it1].ID << " " << tcl[it2].ID;
3010 
3011  ClusterStore& cl1 = tcl[it1];
3012  // fill a vector spanning the length of cluster 1 and filled with the hit
3013  // time
3014  int ew1 = tcl[it1].EndWir;
3015  int bw1 = tcl[it1].BeginWir;
3016  unsigned int iht, hit;
3017  int wire;
3018  std::vector<unsigned int> cl1hits(bw1 + 1 - ew1);
3019  // put in the hit IDs
3020  for (iht = 0; iht < cl1.tclhits.size(); ++iht) {
3021  hit = cl1.tclhits[iht];
3022  wire = fHits[hit].WireID().Wire;
3023  if (wire - ew1 < 0 || wire - ew1 > (int)cl1hits.size()) { return; }
3024  cl1hits[wire - ew1] = hit;
3025  }
3026  unsigned int ew2 = tcl[it2].EndWir;
3027  float et2 = tcl[it2].EndTim;
3028  // look for the closest wire with a hit on cluster 1
3029  unsigned int wiron1 = 0;
3030  // count the number of missing hits
3031  short nmiss = 0;
3032  for (wire = ew2 - 1; wire > ew1; --wire) {
3033  if (cl1hits[wire - ew1] > 0) {
3034  wiron1 = wire;
3035  break;
3036  }
3037  ++nmiss;
3038  } // wire
3039  if (prt) mf::LogVerbatim("CC") << "chk next US wire " << wiron1 << " missed " << nmiss;
3040  if (wiron1 == 0) return;
3041  if (nmiss > fMaxWirSkip[pass]) return;
3042 
3043  // compare the wires with hits on cluster 2 with the gap in cluster 1
3044  // the number of hit wires that fit in the gap
3045  unsigned int hiton2;
3046  int wiron2;
3047  unsigned short nfit = 0;
3048  for (iht = 0; iht < tcl[it2].tclhits.size(); ++iht) {
3049  hiton2 = tcl[it2].tclhits[iht];
3050  wiron2 = fHits[hiton2].WireID().Wire;
3051  if (wiron2 < ew1 || wiron2 > bw1) return;
3052  if (cl1hits[wiron2 - ew1] == 0) ++nfit;
3053  }
3054  // require complete filling of the gap
3055  if (nfit < tcl[it2].tclhits.size()) return;
3056 
3057  // decode the pass for both clusters and select the matching cuts
3058  unsigned short pass1 = tcl[it1].ProcCode - 10 * (tcl[it1].ProcCode / 10);
3059  unsigned short pass2 = tcl[it2].ProcCode - 10 * (tcl[it2].ProcCode / 10);
3060  unsigned short cpass = pass1;
3061  // use the tighter cuts
3062  if (pass2 < pass1) cpass = pass2;
3063 
3064  // ***** Check End of Cluster 2 matching with middle of cluster 1
3065  if ((int)wiron1 - ew1 < 0) return;
3066  unsigned int hiton1 = cl1hits[wiron1 - ew1];
3067  if (hiton1 > fHits.size() - 1) { return; }
3068  // check the time difference
3069  float timon1 = fHits[hiton1].PeakTime();
3070  float dtim = std::abs(et2 + (wiron1 - ew2) * tcl[it2].EndSlp - timon1);
3071  if (dtim > fTimeDelta[cpass]) return;
3072  // check the slope difference. First do a local fit on cluster 1 near
3073  // the matching point
3074  FitClusterMid(it1, hiton1, 3);
3075  if (clChisq > 20.) return;
3076  // fit parameters are now in clpar.
3077  // check for angle consistency
3078  float dth = std::abs(std::atan(fScaleF * clpar[1]) - std::atan(fScaleF * tcl[it2].EndSlp));
3079  if (prt) mf::LogVerbatim("CC") << "US dtheta " << dth << " cut " << fKinkAngCut[cpass];
3080  if (dth > fKinkAngCut[cpass]) return;
3081  // make a charge ratio cut. fAveChg was calculated in FitClusterMid
3082  float chgrat = 2 * std::abs(fAveChg - tcl[it2].EndChg) / (fAveChg + tcl[it2].EndChg);
3083  if (prt) mf::LogVerbatim("CC") << "US chgrat " << chgrat << " cut " << fMergeChgCut[pass];
3084  // ensure that there is a signal on any missing wires at the US end of 1
3085  bool SigOK;
3086  SigOK = ChkSignal(wiron1, timon1, ew2, et2);
3087  if (prt) mf::LogVerbatim("CC") << "US SigOK? " << SigOK;
3088  if (!SigOK) return;
3089 
3090  // ***** Check Begin of Cluster 2 matching with middle of cluster 1
3091  unsigned int bw2 = tcl[it2].BeginWir;
3092  float bt2 = tcl[it2].BeginTim;
3093  nmiss = 0;
3094  wiron1 = 0;
3095  for (wire = bw2 + 1; wire < bw1; ++wire) {
3096  if (cl1hits[wire - ew1] > 0) {
3097  wiron1 = wire;
3098  break;
3099  }
3100  ++nmiss;
3101  }
3102  if (wiron1 == 0) return;
3103  if (nmiss > fMaxWirSkip[pass]) return;
3104  // fit this section of cluster 1 with 4 hits starting at the hit on the
3105  // closest wire and moving DS
3106  hiton1 = cl1hits[wiron1 - ew1];
3107  if (hiton1 > fHits.size() - 1) { return; }
3108  timon1 = fHits[hiton1].PeakTime();
3109  dtim = std::abs(bt2 - (wiron1 - bw2) * tcl[it2].BeginSlp - timon1);
3110  if (dtim > fTimeDelta[cpass]) return;
3111  FitClusterMid(it1, hiton1, -3);
3112  if (clChisq > 20.) return;
3113  // check for angle consistency
3114  dth = std::abs(std::atan(fScaleF * clpar[1]) - std::atan(fScaleF * tcl[it2].BeginSlp));
3115  if (prt) mf::LogVerbatim("CC") << "DS dtheta " << dth << " cut " << fKinkAngCut[cpass];
3116  if (dth > fKinkAngCut[cpass]) return;
3117  // make a charge ratio cut
3118  chgrat = 2 * std::abs(fAveChg - tcl[it2].BeginChg) / (fAveChg + tcl[it2].BeginChg);
3119  if (prt) mf::LogVerbatim("CC") << "DS chgrat " << chgrat << " cut " << fMergeChgCut[pass];
3120  // ensure that there is a signal on any missing wires at the US end of 1
3121  SigOK = ChkSignal(wiron1, timon1, bw2, bt2);
3122  if (prt) mf::LogVerbatim("CC") << "DS SigOK? " << SigOK;
3123  if (!SigOK) return;
3124 
3125  if (prt) mf::LogVerbatim("CC") << "Merge em";
3126  // success. Merge them
3127  DoMerge(it1, it2, 100);
3128  didit = true;
3129  } // ChkMerge12()
3130 
3132  void ClusterCrawlerAlg::DoMerge(unsigned short it1, unsigned short it2, short inProcCode)
3133  {
3134  // Merge clusters.
3135 
3136  ClusterStore& cl1 = tcl[it1];
3137  ClusterStore& cl2 = tcl[it2];
3138 
3139  if (cl1.tclhits.size() < 3) return;
3140  if (cl2.tclhits.size() < 3) return;
3141 
3142  unsigned int lowire, hiwire, whsize, ii, iht, indx;
3143  // do a fit across the boundary between cl1 and cl2 to
3144  // ensure that they truly should be merged
3145  unsigned int fithit;
3146  // Find the low and high wire for both clusters.
3147  // Assume that cluster 1 is DS
3148  bool cl1DS = true;
3149  hiwire = cl1.BeginWir;
3150  fithit = cl1.tclhits[cl1.tclhits.size() - 2];
3151  if (cl2.BeginWir > hiwire) {
3152  hiwire = cl2.BeginWir;
3153  fithit = cl2.tclhits[cl2.tclhits.size() - 2];
3154  cl1DS = false;
3155  }
3156  lowire = cl1.EndWir;
3157  if (cl2.EndWir < lowire) lowire = cl2.EndWir;
3158 
3159  // make a vector of wire hits
3160  whsize = hiwire + 2 - lowire;
3161  std::vector<int> wirehit(whsize, -1);
3162  // put in the hit IDs for cluster 2
3163  for (ii = 0; ii < cl2.tclhits.size(); ++ii) {
3164  iht = cl2.tclhits[ii];
3165  indx = fHits[iht].WireID().Wire - lowire;
3166  wirehit[indx] = iht;
3167  } // iht
3168  // now cluster 1
3169  for (ii = 0; ii < cl1.tclhits.size(); ++ii) {
3170  iht = cl1.tclhits[ii];
3171  indx = fHits[iht].WireID().Wire - lowire;
3172  wirehit[indx] = iht;
3173  } // iht
3174  // make the new cluster
3175  fcl2hits.clear();
3176  for (std::vector<int>::reverse_iterator rit = wirehit.rbegin(); rit != wirehit.rend(); ++rit) {
3177  if (*rit < 0) continue;
3178  fcl2hits.push_back(*rit);
3179  } // rit
3180 
3181  // fit the 6 hits that are near the merging point
3182  short nhitfit = 6;
3183  FitClusterMid(fcl2hits, fithit, nhitfit);
3184  if (clChisq > 5) return;
3185 
3186  // mark cl1 and cl2 obsolete
3187  MakeClusterObsolete(it1);
3188  MakeClusterObsolete(it2);
3189 
3190  short endVtx = 0;
3191  short begVtx = 0;
3192  short del1Vtx = -99;
3193  short del2Vtx = -99;
3194  if (cl1DS) {
3195  // use cluster 1 Begin info
3196  clBeginSlp = cl1.BeginSlp;
3197  clBeginSlpErr = cl1.BeginSlpErr;
3198  clBeginAng = cl1.BeginAng;
3199  clBeginWir = cl1.BeginWir;
3200  clBeginTim = cl1.BeginTim;
3201  clBeginChg = cl1.BeginChg;
3202  clBeginChgNear = cl1.BeginChgNear;
3203  begVtx = cl1.BeginVtx;
3204  del1Vtx = cl1.EndVtx;
3205  // and cluster 2 End info
3206  clEndSlp = cl2.EndSlp;
3207  clEndSlpErr = cl2.EndSlpErr;
3208  clEndAng = cl2.EndAng;
3209  clEndWir = cl2.EndWir;
3210  clEndTim = cl2.EndTim;
3211  clEndChg = cl2.EndChg;
3212  clEndChgNear = cl2.EndChgNear;
3213  endVtx = cl2.EndVtx;
3214  del2Vtx = cl2.BeginVtx;
3215  clStopCode = cl2.StopCode;
3216  }
3217  else {
3218  // use cluster 2 Begin info
3219  clBeginSlp = cl2.BeginSlp;
3220  clBeginSlpErr = cl2.BeginSlpErr;
3221  clBeginAng = cl2.BeginAng;
3222  clBeginWir = cl2.BeginWir;
3223  clBeginTim = cl2.BeginTim;
3224  clBeginChg = cl2.BeginChg;
3225  clBeginChgNear = cl2.BeginChgNear;
3226  begVtx = cl2.BeginVtx;
3227  del2Vtx = cl2.EndVtx;
3228  // and cluster 1 End info
3229  clEndSlp = cl1.EndSlp;
3230  clEndSlpErr = cl1.EndSlpErr;
3231  clEndWir = cl1.EndWir;
3232  clEndTim = cl1.EndTim;
3233  clEndChg = cl1.EndChg;
3234  clEndChgNear = cl1.EndChgNear;
3235  endVtx = cl1.EndVtx;
3236  del1Vtx = cl1.BeginVtx;
3237  clStopCode = cl1.StopCode;
3238  }
3239 
3240  // append it to the tcl vector
3241  clCTP = cl1.CTP;
3242  if (!TmpStore()) return;
3243  unsigned short itnew = tcl.size() - 1;
3244  if (prt)
3245  mf::LogVerbatim("CC") << "DoMerge " << cl1.ID << " " << cl2.ID << " -> " << tcl[itnew].ID;
3246  // stuff the processor code with the current pass
3247  tcl[itnew].ProcCode = inProcCode + pass;
3248  // transfer the vertex info
3249  // delete a vertex between these two?
3250  if (del1Vtx >= 0 && del1Vtx == del2Vtx) vtx[del1Vtx].NClusters = 0;
3251  // preserve the vertex assignments
3252  tcl[itnew].BeginVtx = begVtx;
3253  tcl[itnew].EndVtx = endVtx;
3254  } // DoMerge
3255 
3257  void ClusterCrawlerAlg::PrintVertices()
3258  {
3259 
3260  mf::LogVerbatim myprt("CC");
3261 
3262  if (vtx3.size() > 0) {
3263  // print out 3D vertices
3264  myprt
3265  << "****** 3D vertices ******************************************__2DVtx_Indx__*******\n";
3266  myprt
3267  << "Vtx Cstat TPC Proc X Y Z XEr YEr ZEr pln0 pln1 pln2 Wire\n";
3268  for (unsigned short iv = 0; iv < vtx3.size(); ++iv) {
3269  myprt << std::right << std::setw(3) << std::fixed << iv << std::setprecision(1);
3270  myprt << std::right << std::setw(7) << vtx3[iv].CStat;
3271  myprt << std::right << std::setw(5) << vtx3[iv].TPC;
3272  myprt << std::right << std::setw(5) << vtx3[iv].ProcCode;
3273  myprt << std::right << std::setw(8) << vtx3[iv].X;
3274  myprt << std::right << std::setw(8) << vtx3[iv].Y;
3275  myprt << std::right << std::setw(8) << vtx3[iv].Z;
3276  myprt << std::right << std::setw(5) << vtx3[iv].XErr;
3277  myprt << std::right << std::setw(5) << vtx3[iv].YErr;
3278  myprt << std::right << std::setw(5) << vtx3[iv].ZErr;
3279  myprt << std::right << std::setw(5) << vtx3[iv].Ptr2D[0];
3280  myprt << std::right << std::setw(5) << vtx3[iv].Ptr2D[1];
3281  myprt << std::right << std::setw(5) << vtx3[iv].Ptr2D[2];
3282  myprt << std::right << std::setw(5) << vtx3[iv].Wire;
3283  if (vtx3[iv].Wire < 0) { myprt << " Matched in all planes"; }
3284  else {
3285  myprt << " Incomplete";
3286  }
3287  myprt << "\n";
3288  }
3289  } // vtx3.size
3290 
3291  if (vtx.size() > 0) {
3292  // print out 2D vertices
3293  myprt << "************ 2D vertices ************\n";
3294  myprt << "Vtx CTP wire error tick error ChiDOF NCl topo cluster IDs\n";
3295  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
3296  if (fDebugPlane < 3 && fDebugPlane != (int)vtx[iv].CTP) continue;
3297  myprt << std::right << std::setw(3) << std::fixed << iv << std::setprecision(1);
3298  myprt << std::right << std::setw(6) << vtx[iv].CTP;
3299  myprt << std::right << std::setw(8) << vtx[iv].Wire << " +/- ";
3300  myprt << std::right << std::setw(4) << vtx[iv].WireErr;
3301  myprt << std::right << std::setw(8) << vtx[iv].Time << " +/- ";
3302  myprt << std::right << std::setw(4) << vtx[iv].TimeErr;
3303  myprt << std::right << std::setw(8) << vtx[iv].ChiDOF;
3304  myprt << std::right << std::setw(5) << vtx[iv].NClusters;
3305  myprt << std::right << std::setw(6) << vtx[iv].Topo;
3306  myprt << " ";
3307  // display the cluster IDs
3308  for (unsigned short ii = 0; ii < tcl.size(); ++ii) {
3309  if (fDebugPlane < 3 && fDebugPlane != (int)tcl[ii].CTP) continue;
3310  if (tcl[ii].ID < 0) continue;
3311  if (tcl[ii].BeginVtx == (short)iv) myprt << std::right << std::setw(4) << tcl[ii].ID;
3312  if (tcl[ii].EndVtx == (short)iv) myprt << std::right << std::setw(4) << tcl[ii].ID;
3313  }
3314  myprt << "\n";
3315  } // iv
3316  } // vtx.size
3317 
3318  } // PrintVertices
3319 
3322  {
3323 
3324  // prints clusters to the screen for code development
3325  mf::LogVerbatim myprt("CC");
3326 
3327  PrintVertices();
3328 
3329  float aveRMS, aveRes;
3330  myprt << "*************************************** Clusters "
3331  "*********************************************************************\n";
3332  myprt << " ID CTP nht Stop Proc beg_W:T bAng bSlp Err bChg end_W:T eAng eSlp "
3333  "Err eChg bVx eVx aveRMS Qual cnt\n";
3334  for (unsigned short ii = 0; ii < tcl.size(); ++ii) {
3335  // print clusters in all planes (fDebugPlane = 3) or in a selected plane
3336  if (fDebugPlane < 3 && fDebugPlane != (int)tcl[ii].CTP) continue;
3337  myprt << std::right << std::setw(4) << tcl[ii].ID;
3338  myprt << std::right << std::setw(3) << tcl[ii].CTP;
3339  myprt << std::right << std::setw(5) << tcl[ii].tclhits.size();
3340  myprt << std::right << std::setw(4) << tcl[ii].StopCode;
3341  myprt << std::right << std::setw(6) << tcl[ii].ProcCode;
3342  unsigned int iTime = tcl[ii].BeginTim;
3343  myprt << std::right << std::setw(6) << tcl[ii].BeginWir << ":" << iTime;
3344  if (iTime < 10) { myprt << " "; }
3345  else if (iTime < 100) {
3346  myprt << " ";
3347  }
3348  else if (iTime < 1000)
3349  myprt << " ";
3350  myprt << std::right << std::setw(7) << std::fixed << std::setprecision(2) << tcl[ii].BeginAng;
3351  if (std::abs(tcl[ii].BeginSlp) < 100) {
3352  myprt << std::right << std::setw(6) << std::fixed << std::setprecision(2)
3353  << tcl[ii].BeginSlp;
3354  }
3355  else {
3356  myprt << std::right << std::setw(6) << (int)tcl[ii].BeginSlp;
3357  }
3358  myprt << std::right << std::setw(6) << std::fixed << std::setprecision(2)
3359  << tcl[ii].BeginSlpErr;
3360  myprt << std::right << std::setw(5) << (int)tcl[ii].BeginChg;
3361  iTime = tcl[ii].EndTim;
3362  myprt << std::right << std::setw(6) << tcl[ii].EndWir << ":" << iTime;
3363  if (iTime < 10) { myprt << " "; }
3364  else if (iTime < 100) {
3365  myprt << " ";
3366  }
3367  else if (iTime < 1000)
3368  myprt << " ";
3369  myprt << std::right << std::setw(7) << std::fixed << std::setprecision(2) << tcl[ii].EndAng;
3370  if (std::abs(tcl[ii].EndSlp) < 100) {
3371  myprt << std::right << std::setw(6) << std::fixed << std::setprecision(2) << tcl[ii].EndSlp;
3372  }
3373  else {
3374  myprt << std::right << std::setw(6) << (int)tcl[ii].EndSlp;
3375  }
3376  myprt << std::right << std::setw(6) << std::fixed << std::setprecision(2)
3377  << tcl[ii].EndSlpErr;
3378  myprt << std::right << std::setw(5) << (int)tcl[ii].EndChg;
3379  myprt << std::right << std::setw(5) << tcl[ii].BeginVtx;
3380  myprt << std::right << std::setw(5) << tcl[ii].EndVtx;
3381  aveRMS = 0;
3382  unsigned int iht = 0;
3383  for (unsigned short jj = 0; jj < tcl[ii].tclhits.size(); ++jj) {
3384  iht = tcl[ii].tclhits[jj];
3385  aveRMS += fHits[iht].RMS();
3386  }
3387  aveRMS /= (float)tcl[ii].tclhits.size();
3388  myprt << std::right << std::setw(5) << std::fixed << std::setprecision(1) << aveRMS;
3389  aveRes = 0;
3390  // find cluster tracking resolution
3391  unsigned int hit0, hit1, hit2, cnt = 0;
3392  float arg;
3393  for (unsigned short iht = 1; iht < tcl[ii].tclhits.size() - 1; ++iht) {
3394  hit1 = tcl[ii].tclhits[iht];
3395  hit0 = tcl[ii].tclhits[iht - 1];
3396  hit2 = tcl[ii].tclhits[iht + 1];
3397  // require hits on adjacent wires
3398  if (fHits[hit1].WireID().Wire + 1 != fHits[hit0].WireID().Wire) continue;
3399  if (fHits[hit2].WireID().Wire + 1 != fHits[hit1].WireID().Wire) continue;
3400  arg = (fHits[hit0].PeakTime() + fHits[hit2].PeakTime()) / 2 - fHits[hit1].PeakTime();
3401  aveRes += arg * arg;
3402  ++cnt;
3403  }
3404  if (cnt > 1) {
3405  aveRes /= (float)cnt;
3406  aveRes = sqrt(aveRes);
3407  // convert to a quality factor
3408  aveRes /= (aveRMS * fHitErrFac);
3409  myprt << std::right << std::setw(6) << std::fixed << std::setprecision(1) << aveRes;
3410  myprt << std::right << std::setw(5) << std::fixed << cnt;
3411  }
3412  else {
3413  myprt << " NA";
3414  myprt << std::right << std::setw(5) << std::fixed << cnt;
3415  }
3416  myprt << "\n";
3417  } // ii
3418 
3419  } // PrintClusters()
3420 
3422  void ClusterCrawlerAlg::TmpGet(unsigned short it1)
3423  {
3424  // copies temp cluster it1 into the fcl2hits vector, etc. This is
3425  // effectively the inverse of cl2TmpStore
3426 
3427  if (it1 > tcl.size()) return;
3428 
3429  clBeginSlp = tcl[it1].BeginSlp;
3430  clBeginSlpErr = tcl[it1].BeginSlpErr;
3431  clBeginAng = tcl[it1].BeginAng;
3432  clBeginWir = tcl[it1].BeginWir;
3433  clBeginTim = tcl[it1].BeginTim;
3434  clBeginChg = tcl[it1].BeginChg;
3435  clBeginChgNear = tcl[it1].BeginChgNear;
3436  clEndSlp = tcl[it1].EndSlp;
3437  clEndSlpErr = tcl[it1].EndSlpErr;
3438  clEndAng = tcl[it1].EndAng;
3439  clEndWir = tcl[it1].EndWir;
3440  clEndTim = tcl[it1].EndTim;
3441  clEndChg = tcl[it1].EndChg;
3442  clEndChgNear = tcl[it1].EndChgNear;
3443  clStopCode = tcl[it1].StopCode;
3444  clProcCode = tcl[it1].ProcCode;
3445  clCTP = tcl[it1].CTP;
3446  fcl2hits = tcl[it1].tclhits;
3447  }
3448 
3450  bool ClusterCrawlerAlg::TmpStore()
3451  {
3452 
3453  if (fcl2hits.size() < 2) return false;
3454  if (fcl2hits.size() > fHits.size()) return false;
3455 
3456  if (NClusters == SHRT_MAX) return false;
3457 
3458  ++NClusters;
3459 
3460  unsigned int hit0 = fcl2hits[0];
3461  unsigned int hit;
3462  unsigned int tCST = fHits[hit0].WireID().Cryostat;
3463  unsigned int tTPC = fHits[hit0].WireID().TPC;
3464  unsigned int tPlane = fHits[hit0].WireID().Plane;
3465  unsigned int lastWire = 0;
3466 
3467  for (unsigned short it = 0; it < fcl2hits.size(); ++it) {
3468  hit = fcl2hits[it];
3469  if (inClus[hit] != 0) {
3470  --NClusters;
3471  return false;
3472  }
3473  // check for WireID() consistency
3474  if (fHits[hit].WireID().Cryostat != tCST || fHits[hit].WireID().TPC != tTPC ||
3475  fHits[hit].WireID().Plane != tPlane) {
3476  --NClusters;
3477  return false;
3478  }
3479  // check for decreasing wire number
3480  if (clProcCode < 900 && it > 0 && fHits[hit].WireID().Wire >= lastWire) {
3481  --NClusters;
3482  return false;
3483  }
3484  lastWire = fHits[hit].WireID().Wire;
3485  inClus[hit] = NClusters;
3486  }
3487 
3488  // ensure that the cluster begin/end info is correct
3489 
3490  // define the begin/end charge if it wasn't done already
3491  if (clEndChg <= 0) {
3492  // use the next to the last two hits. The End hit may have low charge
3493  unsigned int ih0 = fcl2hits.size() - 2;
3494  hit = fcl2hits[ih0];
3495  clEndChg = fHits[hit].Integral();
3496  hit = fcl2hits[ih0 - 1];
3497  clEndChg += fHits[hit].Integral();
3498  clEndChg = clEndChg / 2.;
3499  }
3500  if (clBeginChg <= 0) {
3501  // use the 2nd and third hit. The Begin hit may have low charge
3502  hit = fcl2hits[1];
3503  clBeginChg = fHits[hit].Integral();
3504  hit = fcl2hits[2];
3505  clBeginChg += fHits[hit].Integral();
3506  clBeginChg = clBeginChg / 2.;
3507  }
3508 
3509  hit0 = fcl2hits[0];
3510  hit = fcl2hits[fcl2hits.size() - 1];
3511 
3512  // store the cluster in the temporary ClusterStore struct
3513  ClusterStore clstr;
3514 
3515  clstr.ID = NClusters;
3516  clstr.BeginSlp = clBeginSlp;
3517  clstr.BeginSlpErr = clBeginSlpErr;
3518  clstr.BeginAng = std::atan(fScaleF * clBeginSlp);
3519  clstr.BeginWir = fHits[hit0].WireID().Wire;
3520  clstr.BeginTim = fHits[hit0].PeakTime();
3521  clstr.BeginChg = clBeginChg;
3522  clstr.BeginChgNear = clBeginChgNear;
3523  clstr.EndSlp = clEndSlp;
3524  clstr.EndSlpErr = clEndSlpErr;
3525  clstr.EndAng = std::atan(fScaleF * clEndSlp);
3526  clstr.EndWir = fHits[hit].WireID().Wire;
3527  clstr.EndTim = fHits[hit].PeakTime();
3528  clstr.EndChg = clEndChg;
3529  clstr.EndChgNear = clEndChgNear;
3530  clstr.StopCode = clStopCode;
3531  clstr.ProcCode = clProcCode;
3532  clstr.BeginVtx = -99;
3533  clstr.EndVtx = -99;
3534  clstr.CTP = EncodeCTP(tCST, tTPC, tPlane);
3535  clstr.tclhits = fcl2hits;
3536  tcl.push_back(clstr);
3537 
3538  return true;
3539  } // TmpStore()
3540 
3542  void ClusterCrawlerAlg::LACrawlUS()
3543  {
3544  // Crawl a large angle cluster upstream. Similar to CrawlUS but require
3545  // that a hit be added on each wire
3546 
3547  unsigned int dhit = fcl2hits[0];
3548  short dwir = fHits[dhit].WireID().Wire;
3549  clLA = true;
3550  prt = false;
3551  if (fDebugPlane == (short)plane && dwir == fDebugWire && fDebugHit > 0)
3552  prt = std::abs(fHits[dhit].PeakTime() - fDebugHit) < 40;
3553 
3554  if (prt) {
3555  mf::LogVerbatim myprt("CC");
3556  myprt << "******************* LACrawlUS PASS " << pass << " Hits ";
3557  for (unsigned short ii = 0; ii < fcl2hits.size(); ++ii) {
3558  unsigned int iht = fcl2hits[fcl2hits.size() - 1 - ii];
3559  myprt << fHits[iht].WireID().Wire << ":" << (int)fHits[iht].PeakTime() << " ";
3560  }
3561  }
3562 
3563  bool SigOK = true;
3564  bool HitOK = true;
3565  short nmissed = 0;
3566  // count the number of kinks encountered. Hits US of the kink are removed
3567  // and crawling continues unless another kink is encountered
3568  unsigned short kinkOnWire = 0;
3569  unsigned int it = fcl2hits.size() - 1;
3570  unsigned int lasthit = fcl2hits[it];
3571  unsigned int lastwire = fHits[lasthit].WireID().Wire;
3572  unsigned int prevHit, prevWire;
3573  bool ChkCharge = false;
3574  for (unsigned int nextwire = lastwire - 1; nextwire >= fFirstWire; --nextwire) {
3575  if (prt)
3576  mf::LogVerbatim("CC") << "LACrawlUS: next wire " << nextwire << " HitRange "
3577  << WireHitRange[nextwire].first;
3578  // stop crawling if there is a nearby vertex
3579  if (CrawlVtxChk(nextwire)) {
3580  if (prt) mf::LogVerbatim("CC") << "LACrawlUS: stop at vertex";
3581  clStopCode = 6;
3582  break;
3583  }
3584  // AddLAHit will merge the hit on nextwire if necessary
3585  AddLAHit(nextwire, ChkCharge, HitOK, SigOK);
3586  if (prt)
3587  mf::LogVerbatim("CC") << "LACrawlUS: HitOK " << HitOK << " SigOK " << SigOK
3588  << " nmissed SigOK " << nmissed << " cut " << fAllowNoHitWire;
3589  if (SigOK) nmissed = 0;
3590  if (!SigOK) {
3591  ++nmissed;
3592  if (nmissed > fAllowNoHitWire) {
3593  clStopCode = 1;
3594  break;
3595  }
3596  continue;
3597  }
3598  // If a hit was added after a gap, check to see if there is indeed
3599  // a wire signal in the gap
3600  if (HitOK) {
3601  prevHit = fcl2hits[fcl2hits.size() - 2];
3602  prevWire = fHits[prevHit].WireID().Wire;
3603  if (prevWire > nextwire + 2) {
3604  if (!ChkSignal(fcl2hits[fcl2hits.size() - 1], fcl2hits[fcl2hits.size() - 2])) {
3605  // no hit so trim the last hit and quit
3606  FclTrimUS(1);
3607  break;
3608  } // no signal
3609  } // prevWire > nextwire + 2
3610  } // HitOK
3611  // Merge all of the hit multiplets in the fcl2hits array into single
3612  // hits when enough hits have been found to call this a credible large
3613  // angle cluster. The last hit was already merged in AddHit
3614  if (fcl2hits.size() == 4) {
3615  bool didMerge;
3616  for (unsigned short kk = 0; kk < fcl2hits.size() - 1; ++kk) {
3617  unsigned int hit = fcl2hits[kk];
3618  if (mergeAvailable[hit]) MergeHits(hit, didMerge);
3619  }
3620  // update the fit
3621  FitCluster();
3622  clBeginSlp = clpar[1];
3623  // start checking the charge ratio when adding new hits
3624  ChkCharge = true;
3625  continue;
3626  } // fcl2hits.size() == 4
3627  unsigned short chsiz = chifits.size() - 1;
3628  // chsiz is fcl2hits.size() - 1...
3629  if (chsiz < 6) continue;
3630  if (fKinkChiRat[pass] <= 0) continue;
3631  if (chifits.size() != fcl2hits.size()) {
3632  mf::LogError("CC") << "LACrawlUS: chifits size error " << chifits.size() << " "
3633  << fcl2hits.size();
3634  return;
3635  }
3636  if (prt)
3637  mf::LogVerbatim("CC") << "Kink chk " << chifits[chsiz] << " " << chifits[chsiz - 1] << " "
3638  << chifits[chsiz - 2] << " " << chifits[chsiz - 3];
3639  if (chifits[chsiz - 1] > fKinkChiRat[pass] * chifits[chsiz - 2] &&
3640  chifits[chsiz] > fKinkChiRat[pass] * chifits[chsiz - 1]) {
3641  // find the kink angle (crudely) from the 0th and 2nd hit
3642  unsigned int ih0 = fcl2hits.size() - 1;
3643  unsigned int hit0 = fcl2hits[ih0];
3644  unsigned int ih2 = ih0 - 2;
3645  unsigned int hit2 = fcl2hits[ih2];
3646  float dt02 = fHits[hit2].PeakTime() - fHits[hit0].PeakTime();
3647  float dw02 = fHits[hit2].WireID().Wire - fHits[hit0].WireID().Wire;
3648  float th02 = std::atan(fScaleF * dt02 / dw02);
3649  // and the 3rd and 5th hit
3650  unsigned int ih3 = ih2 - 1;
3651  unsigned int hit3 = fcl2hits[ih3];
3652  unsigned int ih5 = ih3 - 2;
3653  unsigned int hit5 = fcl2hits[ih5];
3654  float dt35 = fHits[hit5].PeakTime() - fHits[hit3].PeakTime();
3655  float dw35 = fHits[hit5].WireID().Wire - fHits[hit3].WireID().Wire;
3656  float th35 = std::atan(fScaleF * dt35 / dw35);
3657  float dth = std::abs(th02 - th35);
3658  if (prt)
3659  mf::LogVerbatim("CC") << " Kink angle " << std::setprecision(3) << dth << " cut "
3660  << fKinkAngCut[pass];
3661  if (dth > fKinkAngCut[pass]) {
3662  // hit a kink. Lop of the first 3 hits, refit and keep crawling?
3663  FclTrimUS(3);
3664  FitCluster();
3665  // See if this is a second kink and it is close to the first
3666  // kink (which had hits removed).
3667  if (kinkOnWire > 0) {
3668  if (kinkOnWire - nextwire < 4) {
3669  if (prt)
3670  mf::LogVerbatim("CC")
3671  << "Hit a second kink. kinkOnWire = " << kinkOnWire << " Stopping";
3672  // set the kink stop code
3673  clStopCode = 3;
3674  break;
3675  }
3676  }
3677  kinkOnWire = nextwire;
3678  if (prt) mf::LogVerbatim("CC") << "Removed kink hits";
3679  } // kinkang check
3680  } // chifits test
3681  } // nextwire
3682 
3683  CheckClusterHitFrac(prt);
3684 
3685  clProcCode += 300;
3686  if (prt) mf::LogVerbatim("CC") << "LACrawlUS done. Nhits = " << fcl2hits.size();
3687  prt = false;
3688  } // LACrawlUS
3689 
3691  void ClusterCrawlerAlg::CrawlUS()
3692  {
3693  // Crawl along a trail of hits moving upstream
3694 
3695  if (fcl2hits.size() < 2) return;
3696 
3697  unsigned int dhit = fcl2hits[0];
3698  int dwir = fHits[dhit].WireID().Wire;
3699  clLA = false;
3700 
3701  prt = false;
3702  if (fDebugPlane == (short)plane && dwir == fDebugWire && fDebugHit > 0)
3703  prt = std::abs(fHits[dhit].PeakTime() - fDebugHit) < 20;
3704 
3705  if (prt) {
3706  mf::LogVerbatim myprt("CC");
3707  myprt << "******************* Start CrawlUS on pass " << pass << " Hits: ";
3708  for (unsigned short ii = 0; ii < fcl2hits.size(); ++ii) {
3709  unsigned int iht = fcl2hits[fcl2hits.size() - 1 - ii];
3710  myprt << fHits[iht].WireID().Wire << ":" << (int)fHits[iht].PeakTime() << " ";
3711  }
3712  myprt << "\n";
3713  }
3714 
3715  // SigOK = true if there is a ADC signal near the projected cluster position
3716  bool SigOK = true;
3717  bool HitOK = true;
3718  // count the number of missed hits on adjacent wires
3719  short nmissed = 0;
3720  // count the number of added hits after skipping
3721  short nHitAfterSkip = 0;
3722  bool DidaSkip = false;
3723  bool PostSkip = false;
3724  unsigned int it = fcl2hits.size() - 1;
3725  unsigned int lasthit = fcl2hits[it];
3726  if (lasthit > fHits.size() - 1) {
3727  mf::LogError("CC") << "CrawlUS bad lasthit " << lasthit;
3728  return;
3729  }
3730  unsigned int lastwire = fHits[lasthit].WireID().Wire;
3731  if (prt) mf::LogVerbatim("CC") << "CrawlUS: last wire " << lastwire << " hit " << lasthit;
3732 
3733  unsigned int lastWireWithSignal = lastwire;
3734  unsigned short nDroppedHit = 0;
3735 
3736  for (unsigned int nextwire = lastwire - 1; nextwire > fFirstWire; --nextwire) {
3737  if (prt)
3738  mf::LogVerbatim("CC") << "CrawlUS: next wire " << nextwire << " HitRange "
3739  << WireHitRange[nextwire].first;
3740  // stop crawling if there is a nearby vertex
3741  if (CrawlVtxChk(nextwire)) {
3742  if (prt) mf::LogVerbatim("CC") << "CrawlUS: stop at vertex";
3743  clStopCode = 6;
3744  break;
3745  }
3746  // Switch to large angle crawling?
3747  if (std::abs(clpar[1]) > fLAClusSlopeCut) {
3748  if (prt) mf::LogVerbatim("CC") << ">>>>> CrawlUS: Switching to LACrawlUS";
3749  LACrawlUS();
3750  return;
3751  }
3752  // add hits and check for PH and width consistency
3753  AddHit(nextwire, HitOK, SigOK);
3754  if (prt)
3755  mf::LogVerbatim("CC") << "CrawlUS: HitOK " << HitOK << " SigOK " << SigOK << " nmissed "
3756  << nmissed;
3757  if (SigOK) lastWireWithSignal = nextwire;
3758  if (!HitOK) {
3759  // no hit on this wire. Was there a signal or dead wire?
3760  if (SigOK) {
3761  // no hit on the wire but there is a signal
3762  ++nmissed;
3763  // see if we are in the PostSkip phase and missed more than 1 wire
3764  if (PostSkip && nmissed > fMinWirAfterSkip[pass]) {
3765  // cluster is really short
3766  if ((int)(fcl2hits.size() - nHitAfterSkip) < 4) {
3767  fcl2hits.clear();
3768  return;
3769  }
3770  if (prt) mf::LogVerbatim("CC") << " PostSkip && nmissed = " << nmissed;
3771  clStopCode = 2;
3772  FclTrimUS(nHitAfterSkip);
3773  FitCluster();
3774  if (clChisq > 90.) {
3775  fcl2hits.clear();
3776  return;
3777  }
3778  FitCluster();
3779  return;
3780  } // PostSkip && nmissed >
3781  if (nmissed > 1) {
3782  DidaSkip = true;
3783  PostSkip = false;
3784  }
3785  } // SigOK
3786  else {
3787  // SigOK is false
3788  clStopCode = 0;
3789  lasthit = fcl2hits[fcl2hits.size() - 1];
3790  if ((lastWireWithSignal - nextwire) > fAllowNoHitWire) {
3791  if (prt)
3792  mf::LogVerbatim("CC")
3793  << "No hit or signal on wire " << nextwire << " last wire with signal "
3794  << lastWireWithSignal << " exceeding fAllowNoHitWire " << fAllowNoHitWire
3795  << " Break!";
3796  break;
3797  }
3798  } // else SigOK false
3799  } // !HitOK
3800  else {
3801  if (prt)
3802  mf::LogVerbatim("CC") << " CrawlUS check clChisq " << clChisq << " cut " << fChiCut[pass];
3803  if (clChisq > fChiCut[pass]) {
3804  if (fcl2hits.size() < 3) {
3805  fcl2hits.clear();
3806  return;
3807  }
3808  // a fit error occurred. Lop off the leading hit and refit
3809  if (prt) mf::LogVerbatim("CC") << "ADD- Bad clChisq " << clChisq << " dropping hit";
3810  FclTrimUS(1);
3811  FitCluster();
3812  ++nDroppedHit;
3813  if (nDroppedHit > 4) {
3814  if (prt)
3815  mf::LogVerbatim("CC")
3816  << " Too many dropped hits: " << nDroppedHit << " Stop crawling";
3817  break;
3818  } // too many dropped hits
3819  if (clChisq > fChiCut[pass]) {
3820  if (prt)
3821  mf::LogVerbatim("CC")
3822  << " Bad clChisq " << clChisq << " after dropping hit. Stop crawling";
3823  break;
3824  }
3825  FitClusterChg();
3826  continue;
3827  } // clChisq > fChiCut[0]
3828  // monitor the onset of a kink. Look for a progressive increase
3829  // in chisq for the previous 0 - 2 hits.
3830  if (chifits.size() > 5 && fKinkChiRat[pass] > 0) {
3831  if (chifits.size() != fcl2hits.size()) {
3832  mf::LogError("CC") << "CrawlUS: chifits size error " << chifits.size() << " "
3833  << fcl2hits.size();
3834  return;
3835  }
3836  unsigned short chsiz = chifits.size() - 1;
3837  if (prt)
3838  mf::LogVerbatim("CC") << "Kink chk " << chifits[chsiz] << " " << chifits[chsiz - 1]
3839  << " " << chifits[chsiz - 2] << " " << chifits[chsiz - 3];
3840  if (chifits[chsiz - 1] > fKinkChiRat[pass] * chifits[chsiz - 2] &&
3841  chifits[chsiz] > fKinkChiRat[pass] * chifits[chsiz - 1]) {
3842  if (fcl2hits.size() != chifits.size()) {
3843  mf::LogError("CC") << "bad kink check size " << chifits.size() << " "
3844  << fcl2hits.size() << " plane " << plane << " cluster " << dwir
3845  << ":" << dhit;
3846  continue;
3847  }
3848  if (EndKinkAngle() > fKinkAngCut[pass]) {
3849  if (prt)
3850  mf::LogVerbatim("CC")
3851  << "******************* Stopped tracking - kink angle " << EndKinkAngle() << " > "
3852  << fKinkAngCut[pass] << " Removing 3 hits";
3853  // kill the last 3 hits and refit
3854  FclTrimUS(3);
3855  FitCluster();
3856  FitClusterChg();
3857  } // kinkang check
3858  } // chifits check
3859  } // chifits.size() > 5
3860  // done with kink check
3861  // update the cluster Begin information?
3862  if (fcl2hits.size() == fMaxHitsFit[pass] || fcl2hits.size() == fMinHits[pass]) {
3863  clBeginSlp = clpar[1];
3864  clBeginSlpErr = clparerr[1];
3865  }
3866  // define the begin cluster charge if it's not defined yet
3867  if (clBeginChg <= 0 && fAveChg > 0) {
3868  clBeginChg = fAveChg;
3869  if (prt) mf::LogVerbatim("CC") << " Set clBeginChg " << clBeginChg;
3870  }
3871  // reset nmissed
3872  nmissed = 0;
3873  // start counting hits added after skipping
3874  if (DidaSkip) {
3875  // start PostSkip phase
3876  PostSkip = true;
3877  DidaSkip = false;
3878  nHitAfterSkip = 0;
3879  } // DidaSkip
3880  // check for PostSkip phase
3881  if (PostSkip) {
3882  // end the PostSkip phase if there are enough hits
3883  ++nHitAfterSkip;
3884  if (nHitAfterSkip == fMinWirAfterSkip[pass]) PostSkip = false;
3885  }
3886  // check for bad chisq
3887  if (clChisq > fChiCut[pass]) {
3888  if (prt) mf::LogVerbatim("CC") << "<<ADD- Bad chisq " << clChisq;
3889  // remove the last few hits if there is a systematic increase in chisq and re-fit
3890  float chirat;
3891  unsigned short lopped = 0;
3892  for (unsigned short nlop = 0; nlop < 4; ++nlop) {
3893  unsigned short cfsize = chifits.size() - 1;
3894  chirat = chifits[cfsize] / chifits[cfsize - 1];
3895  if (prt)
3896  mf::LogVerbatim("CC")
3897  << "chirat " << chirat << " last hit " << fcl2hits[fcl2hits.size() - 1];
3898  if (chirat < 1.2) break;
3899  if (prt) mf::LogVerbatim("CC") << "<<ADD- Bad chisq. Bad chirat " << chirat;
3900  FclTrimUS(1);
3901  ++lopped;
3902  if (fcl2hits.size() < 6) break;
3903  if (chifits.size() < 6) break;
3904  } // nlop
3905  if (fcl2hits.size() < 6) {
3906  clStopCode = 4;
3907  if (prt) mf::LogVerbatim("CC") << "Bad fit chisq - short cluster. Break";
3908  break;
3909  }
3910  if (lopped == 0 && fcl2hits.size() > 5) {
3911  if (prt) mf::LogVerbatim("CC") << "<<ADD- Bad chisq. remove 1 hit";
3912  FclTrimUS(1);
3913  ++lopped;
3914  }
3915  FitCluster();
3916  FitClusterChg();
3917  if (prt)
3918  mf::LogVerbatim("CC") << "Bad fit chisq - removed " << lopped << " hits. Continue";
3919  } // clChisq > fChiCut[pass]
3920  } // !HitOK check
3921  } // nextwire
3922  if (prt)
3923  mf::LogVerbatim("CC") << "******************* CrawlUS normal stop. size " << fcl2hits.size();
3924 
3925  bool reFit = false;
3926  // end kink angle check
3927  if (fcl2hits.size() > 5) {
3928  // check for a kink at the US end
3929  if (prt)
3930  mf::LogVerbatim("CC") << "EndKinkAngle check " << EndKinkAngle() << " cut "
3931  << fKinkAngCut[pass];
3932  if (EndKinkAngle() > fKinkAngCut[pass]) {
3933  if (prt) mf::LogVerbatim("CC") << "EndKinkAngle removes 3 hits ";
3934  FclTrimUS(3);
3935  reFit = true;
3936  }
3937  } // fcl2hits.size() > 5
3938 
3939  // count the number of hits on adjacent wires at the leading edge and
3940  // ensure that the count is consistent with fMinWirAfterSkip
3941  if ((unsigned short)fcl2hits.size() > fMinWirAfterSkip[pass] + 3) {
3942  unsigned int ih0 = fcl2hits.size() - 1;
3943  unsigned int hit0 = fcl2hits[ih0];
3944  unsigned int uswir = fHits[hit0].WireID().Wire;
3945  unsigned int nxtwir;
3946  unsigned short nAdjHit = 0;
3947  for (unsigned short ii = ih0 - 1; ii > 0; --ii) {
3948  nxtwir = fHits[fcl2hits[ii]].WireID().Wire;
3949  ++nAdjHit;
3950  if (nxtwir != uswir + 1) break;
3951  // break if there are enough hits
3952  if (nAdjHit == fMinWirAfterSkip[pass]) break;
3953  uswir = nxtwir;
3954  } // ii
3955  // lop off hits?
3956  if (nAdjHit < fMinWirAfterSkip[pass]) {
3957  if (prt) mf::LogVerbatim("CC") << "fMinWirAfterSkip removes " << nAdjHit << " hits ";
3958  FclTrimUS(nAdjHit);
3959  reFit = true;
3960  }
3961  } // fcl2hits.size() > fMinWirAfterSkip[pass] + 3
3962 
3963  // check for a bad hit on the end
3964  if (!reFit && fcl2hits.size() > 3) {
3965  float chirat = chifits[chifits.size() - 1] / chifits[chifits.size() - 2];
3966  if (prt)
3967  mf::LogVerbatim("CC") << "Last hit chirat " << chirat << " cut " << fKinkChiRat[pass];
3968  if (prt)
3969  mf::LogVerbatim("CC") << "Check " << clChisq << " " << chifits[chifits.size() - 1] << " "
3970  << chifits[chifits.size() - 2];
3971  if (chirat > fKinkChiRat[pass]) {
3972  if (prt) mf::LogVerbatim("CC") << "<<ADD- at end";
3973  FclTrimUS(1);
3974  reFit = true;
3975  }
3976  } // !reFit
3977 
3978  if (reFit) {
3979  FitCluster();
3980  FitClusterChg();
3981  }
3982  CheckClusterHitFrac(prt);
3983  if (prt)
3984  mf::LogVerbatim("CC") << "******************* CrawlUS done. Size " << fcl2hits.size()
3985  << " min length for this pass " << fMinHits[pass];
3986 
3987  prt = false;
3988  } // CrawlUS()
3989 
3991  float ClusterCrawlerAlg::EndKinkAngle()
3992  {
3993  // find the kink angle (crudely) from the 0th and 2nd hit on the cluster under construction
3994 
3995  unsigned int ih0 = fcl2hits.size() - 1;
3996  unsigned int hit0 = fcl2hits[ih0];
3997  unsigned int ih2 = ih0 - 2;
3998  unsigned int hit2 = fcl2hits[ih2];
3999  float dt02 = fHits[hit2].PeakTime() - fHits[hit0].PeakTime();
4000  float dw02 = fHits[hit2].WireID().Wire - fHits[hit0].WireID().Wire;
4001  float th02 = std::atan(fScaleF * dt02 / dw02);
4002  // and the 3rd and 5th hit
4003  unsigned int ih3 = ih2 - 1;
4004  unsigned int hit3 = fcl2hits[ih3];
4005  unsigned int ih5 = ih3 - 2;
4006  unsigned int hit5 = fcl2hits[ih5];
4007  float dt35 = fHits[hit5].PeakTime() - fHits[hit3].PeakTime();
4008  float dw35 = fHits[hit5].WireID().Wire - fHits[hit3].WireID().Wire;
4009  float th35 = std::atan(fScaleF * dt35 / dw35);
4010  return std::abs(th02 - th35);
4011  }
4012 
4014  bool ClusterCrawlerAlg::ChkMergedClusterHitFrac(unsigned short it1, unsigned short it2)
4015  {
4016  // This routine is called before two tcl clusters, it1 and it2, are slated to be
4017  // merged to see if they will satisfy the minimum hit fraction criterion
4018  if (it1 > tcl.size() - 1) return false;
4019  if (it2 > tcl.size() - 1) return false;
4020  unsigned int eWire = 99999;
4021  unsigned int bWire = 0, wire;
4022  if (tcl[it1].BeginWir > bWire) bWire = tcl[it1].BeginWir;
4023  if (tcl[it2].BeginWir > bWire) bWire = tcl[it2].BeginWir;
4024  if (tcl[it1].EndWir < eWire) eWire = tcl[it1].EndWir;
4025  if (tcl[it2].EndWir < eWire) eWire = tcl[it2].EndWir;
4026  unsigned int mergedClusterLength = bWire - eWire + 1;
4027  // define a vector of size = length of the wire range
4028  std::vector<bool> cHits(mergedClusterLength, false);
4029  // set the elements true if there is a hit
4030  unsigned int ii, iht, indx;
4031  for (ii = 0; ii < tcl[it1].tclhits.size(); ++ii) {
4032  iht = tcl[it1].tclhits[ii];
4033  if (iht > fHits.size() - 1) {
4034  mf::LogError("CC") << "ChkMergedClusterHitFrac bad iht ";
4035  return false;
4036  }
4037  indx = fHits[iht].WireID().Wire - eWire;
4038  cHits[indx] = true;
4039  } // ii
4040  for (ii = 0; ii < tcl[it2].tclhits.size(); ++ii) {
4041  iht = tcl[it2].tclhits[ii];
4042  if (iht > fHits.size() - 1) {
4043  mf::LogError("CC") << "ChkMergedClusterHitFrac bad iht ";
4044  return false;
4045  }
4046  indx = fHits[iht].WireID().Wire - eWire;
4047  cHits[indx] = true;
4048  } // ii
4049  // set cHits true if the wire is dead
4050  for (ii = 0; ii < cHits.size(); ++ii) {
4051  wire = eWire + ii;
4052  if (WireHitRange[wire].first == -1) cHits[ii] = true;
4053  }
4054  // count the number of wires with hits
4055  float nhits = std::count(cHits.begin(), cHits.end(), true);
4056  float hitFrac = nhits / (float)cHits.size();
4057  return (hitFrac > fMinHitFrac);
4058  } // ChkMergedClusterHitFrac
4059 
4061  void ClusterCrawlerAlg::CheckClusterHitFrac(bool prt)
4062  {
4063 
4064  // Find the fraction of the wires on the cluster that have
4065  // hits.
4066  unsigned int iht = fcl2hits[fcl2hits.size() - 1];
4067  clEndWir = fHits[iht].WireID().Wire;
4068  clBeginWir = fHits[fcl2hits[0]].WireID().Wire;
4069  float hitFrac = (float)(fcl2hits.size() + DeadWireCount()) / (float)(clBeginWir - clEndWir + 1);
4070 
4071  if (hitFrac < fMinHitFrac) {
4072  if (prt)
4073  mf::LogVerbatim("CC") << "CheckClusterHitFrac: Poor hit fraction " << hitFrac
4074  << " clBeginWir " << clBeginWir << " clEndWir " << clEndWir
4075  << " size " << fcl2hits.size() << " DeadWireCount "
4076  << DeadWireCount();
4077  fcl2hits.clear();
4078  return;
4079  } // hitFrac
4080 
4081  /* TODO: Does this make sense?
4082  // lop off the last hit if it is part of a hit multiplet
4083  if(fHits[iht].Multiplicity() > 1) {
4084  fcl2hits.resize(fcl2hits.size() - 1);
4085  }
4086 */
4087  // check for short track ghosts
4088  if (fcl2hits.size() < 5) {
4089  unsigned short nsing = 0;
4090  for (unsigned short iht = 0; iht < fcl2hits.size(); ++iht)
4091  if (fHits[fcl2hits[iht]].Multiplicity() == 1) ++nsing;
4092  hitFrac = (float)nsing / (float)fcl2hits.size();
4093  if (hitFrac < fMinHitFrac) {
4094  fcl2hits.clear();
4095  if (prt)
4096  mf::LogVerbatim("CC") << "CheckClusterHitFrac: Poor short track hit fraction " << hitFrac;
4097  return;
4098  } // hitFrac
4099  } // short ghost track check
4100 
4101  // analyze the pattern of nearby charge
4102  // will need the cluster charge so calculate it here if it isn't defined yet
4103  if (clBeginChg <= 0) {
4104  unsigned int iht, nht = 0;
4105  for (unsigned short ii = 0; ii < fcl2hits.size(); ++ii) {
4106  iht = fcl2hits[ii];
4107  clBeginChg += fHits[iht].Integral();
4108  ++nht;
4109  if (nht == 8) break;
4110  }
4111  clBeginChg /= (float)nht;
4112  } // clBeginChg == 0
4113  // handle short vs long clusters
4114  unsigned short cnt = chgNear.size() / 2;
4115  // get the average charge from <= 30 hits at each end
4116  if (chgNear.size() > 60) cnt = 30;
4117  clBeginChgNear = 0;
4118  clEndChgNear = 0;
4119  for (unsigned short ids = 0; ids < cnt; ++ids) {
4120  clBeginChgNear += chgNear[ids];
4121  clEndChgNear += chgNear[chgNear.size() - 1 - ids];
4122  }
4123  clBeginChgNear /= (float)cnt;
4124  clEndChgNear /= (float)cnt;
4125 
4126  } // CheckClusterHitFrac()
4127 
4129  void ClusterCrawlerAlg::FitClusterMid(unsigned short it1, unsigned int ihtin, short nhit)
4130  {
4131  FitClusterMid(tcl[it1].tclhits, ihtin, nhit);
4132  } // FitClusterMid
4133 
4135  void ClusterCrawlerAlg::FitClusterMid(std::vector<unsigned int>& hitVec,
4136  unsigned int ihtin,
4137  short nhit)
4138  {
4139  // Fits hits on temp cluster it1 to a line starting at hit ihtin and including
4140  // nhit hits incrementing towards the hit vector End when nhit > 0 and
4141  // decrementing towards the hit vector Begin when nhit < 0.
4142  // The fit params are stashed in the clpar and clparerr arrays.
4143  // fAveChg is re-calculated as well.
4144 
4145  // set chisq bad in case something doesn't work out
4146  clChisq = 99.;
4147 
4148  if (hitVec.size() < 3) return;
4149 
4150  std::vector<float> xwir;
4151  std::vector<float> ytim;
4152  std::vector<float> ytimerr2;
4153 
4154  unsigned short ii, hitcnt = 0, nht = 0, usnhit;
4155  float wire0 = 0;
4156  unsigned int iht;
4157  bool UseEm = false;
4158  fAveChg = 0.;
4159  fChgSlp = 0.;
4160 
4161  if (nhit > 0) {
4162  usnhit = nhit;
4163  // find the first desired hit and move towards the End
4164  for (ii = 0; ii < hitVec.size(); ++ii) {
4165  iht = hitVec[ii];
4166  if (iht > fHits.size() - 1) {
4167  mf::LogError("CC") << "FitClusterMid bad iht " << iht;
4168  return;
4169  }
4170  // look for the desired first hit. Use this as the origin wire
4171  if (iht == ihtin) {
4172  UseEm = true;
4173  wire0 = fHits[iht].WireID().Wire;
4174  }
4175  // use hits after finding the first desired hit
4176  if (UseEm) {
4177  xwir.push_back((float)fHits[iht].WireID().Wire - wire0);
4178  ytim.push_back(fHits[iht].PeakTime());
4179  // pass the error^2 to the fitter
4180  float terr = fHitErrFac * fHits[iht].RMS();
4181  ytimerr2.push_back(terr * terr);
4182  fAveChg += fHits[iht].Integral();
4183  ++hitcnt;
4184  if (hitcnt == usnhit) break;
4185  }
4186  }
4187  nht = hitcnt;
4188  }
4189  else {
4190  usnhit = -nhit;
4191  // find the first desired hit and move towards the Begin
4192  for (auto ii = hitVec.crbegin(); ii != hitVec.crend(); ++ii) {
4193  iht = *ii;
4194  if (iht > fHits.size() - 1) {
4195  mf::LogVerbatim("CC") << "FitClusterMid bad iht " << iht;
4196  return;
4197  }
4198  // look for the desired first hit. Use this as the origin wire
4199  if (iht == ihtin) {
4200  UseEm = true;
4201  wire0 = fHits[iht].WireID().Wire;
4202  }
4203  // use hits after finding the first desired hit
4204  if (UseEm) {
4205  xwir.push_back((float)fHits[iht].WireID().Wire - wire0);
4206  ytim.push_back(fHits[iht].PeakTime());
4207  float terr = fHitErrFac * fHits[iht].RMS();
4208  ytimerr2.push_back(terr * terr);
4209  fAveChg += fHits[iht].Integral();
4210  ++hitcnt;
4211  if (hitcnt == usnhit) break;
4212  }
4213  }
4214  nht = hitcnt;
4215  }
4216 
4217  if (nht < 2) return;
4218  fAveChg = fAveChg / (float)nht;
4219  fChgSlp = 0.;
4220 
4221  float intcpt = 0.;
4222  float slope = 0.;
4223  float intcpterr = 0.;
4224  float slopeerr = 0.;
4225  float chidof = 0.;
4226  fLinFitAlg.LinFit(xwir, ytim, ytimerr2, intcpt, slope, intcpterr, slopeerr, chidof);
4227  clChisq = chidof;
4228  if (clChisq > fChiCut[0]) return;
4229  clpar[0] = intcpt;
4230  clpar[1] = slope;
4231  clpar[2] = wire0;
4232  clparerr[0] = intcpterr;
4233  clparerr[1] = slopeerr;
4234  }
4235 
4237  void ClusterCrawlerAlg::FitCluster()
4238  {
4239  // Fits the hits on the US end of a cluster. This routine assumes that
4240  // wires are numbered from lower (upstream) to higher (downstream) and
4241  // that the hits in the fclhits vector are sorted so that upstream hits
4242  // are at the end of the vector
4243 
4244  clChisq = 999.;
4245 
4246  if (pass > fNumPass - 1) {
4247  mf::LogError("CC") << "FitCluster called on invalid pass " << pass;
4248  return;
4249  }
4250 
4251  unsigned short ii, nht = 0;
4252  // fit all hits or truncate?
4253  nht = fcl2hits.size();
4254  if (clLA) {
4255  // Fit large angle cluster
4256  if (nht > fLAClusMaxHitsFit) nht = fLAClusMaxHitsFit;
4257  }
4258  else {
4259  if (nht > fMaxHitsFit[pass]) nht = fMaxHitsFit[pass];
4260  }
4261  if (nht < 2) return;
4262 
4263  std::vector<float> xwir;
4264  std::vector<float> ytim;
4265  std::vector<float> ytimerr2;
4266  // apply an angle dependent scale factor.
4267  float angfactor = AngleFactor(clpar[1]);
4268 
4269  unsigned int wire;
4270  unsigned int wire0 = fHits[fcl2hits[fcl2hits.size() - 1]].WireID().Wire;
4271  unsigned int ihit;
4272  float terr, qave = 0, qwt;
4273  for (ii = 0; ii < nht; ++ii) {
4274  ihit = fcl2hits[fcl2hits.size() - 1 - ii];
4275  qave += fHits[ihit].Integral();
4276  } // ii
4277  qave /= (float)nht;
4278  for (ii = 0; ii < nht; ++ii) {
4279  ihit = fcl2hits[fcl2hits.size() - 1 - ii];
4280  wire = fHits[ihit].WireID().Wire;
4281  xwir.push_back(wire - wire0);
4282  ytim.push_back(fHits[ihit].PeakTime());
4283  // Scale error by hit multiplicity to account for bias in hit
4284  // multiplet fitting
4285  terr = fHitErrFac * fHits[ihit].RMS() * fHits[ihit].Multiplicity();
4286  if (fAveChg > 0) {
4287  // increase the error for large charge hits
4288  qwt = (fHits[ihit].Integral() / qave) - 1;
4289  if (qwt < 1) qwt = 1;
4290  terr *= qwt;
4291  }
4292  if (terr < 0.01) terr = 0.01;
4293  ytimerr2.push_back(angfactor * terr * terr);
4294  }
4295  CalculateAveHitWidth();
4296  if (prt) {
4297  mf::LogVerbatim myprt("CC");
4298  myprt << "FitCluster W:T ";
4299  unsigned short cnt = 0;
4300  for (std::vector<unsigned int>::reverse_iterator it = fcl2hits.rbegin();
4301  it != fcl2hits.rend();
4302  ++it) {
4303  unsigned int ihit = *it;
4304  unsigned short wire = fHits[ihit].WireID().Wire;
4305  myprt << wire << ":" << (short)fHits[ihit].PeakTime() << " ";
4306  ++cnt;
4307  if (cnt == 8) {
4308  myprt << " .... ";
4309  break;
4310  }
4311  }
4312  } // prt
4313 
4314  if (xwir.size() < 2) return;
4315 
4316  float intcpt = 0.;
4317  float slope = 0.;
4318  float intcpterr = 0.;
4319  float slopeerr = 0.;
4320  float chidof = 0.;
4321  fLinFitAlg.LinFit(xwir, ytim, ytimerr2, intcpt, slope, intcpterr, slopeerr, chidof);
4322  clChisq = chidof;
4323  if (chidof > fChiCut[0]) return;
4324  clpar[0] = intcpt;
4325  clpar[1] = slope;
4326  clpar[2] = wire0;
4327  clparerr[0] = intcpterr;
4328  clparerr[1] = slopeerr;
4329 
4330  if (prt)
4331  mf::LogVerbatim("CC") << "nht " << nht << " fitpar " << (int)clpar[0] << "+/-"
4332  << (int)intcpterr << " " << clpar[1] << "+/-" << slopeerr << " clChisq "
4333  << clChisq;
4334  }
4336  float ClusterCrawlerAlg::AngleFactor(float slope)
4337  {
4338  // returns an angle dependent cluster projection error factor for fitting
4339  // and hit finding
4340 
4341  float slp = std::abs(slope);
4342  if (slp > 15) slp = 15;
4343  // return a value between 1 and 4
4344  float angfac = 1 + 0.03 * slp * slp;
4345  return angfac;
4346  }
4347 
4349  void ClusterCrawlerAlg::CalculateAveHitWidth()
4350  {
4351  fAveHitWidth = 0;
4352  for (unsigned short ii = 0; ii < fcl2hits.size(); ++ii)
4353  fAveHitWidth += fHits[fcl2hits[ii]].EndTick() - fHits[fcl2hits[ii]].StartTick();
4354  fAveHitWidth /= (float)fcl2hits.size();
4355  } // CalculateAveHitWidth
4356 
4358  void ClusterCrawlerAlg::FitClusterChg()
4359  {
4360  // Fits the charge of hits on the fcl2hits vector to a line, or simply
4361  // uses the average of 1 or 2 hits as determined by NHitsAve
4362 
4363  if (fcl2hits.size() == 0) return;
4364  unsigned int ih0 = fcl2hits.size() - 1;
4365 
4366  if (pass >= fNumPass) {
4367  mf::LogError("CC") << "FitClusterChg bad pass " << pass;
4368  return;
4369  }
4370 
4371  // Handle Large Angle clusters
4372  if (clLA) {
4373  // simple average of the charge (and the hit width
4374  // while we are here)
4375  unsigned short nhave = fLAClusMaxHitsFit;
4376  if (nhave > fcl2hits.size()) nhave = fcl2hits.size();
4377  fAveChg = 0;
4378  fChgSlp = 0;
4379  fAveHitWidth = 0;
4380  unsigned int iht;
4381  for (unsigned short ii = 0; ii < nhave; ++ii) {
4382  iht = fcl2hits[fcl2hits.size() - 1 - ii];
4383  fAveChg += fHits[iht].Integral();
4384  fAveHitWidth += (fHits[iht].EndTick() - fHits[iht].StartTick());
4385  } // ii
4386  fAveChg /= (float)fcl2hits.size();
4387  fAveHitWidth /= (float)fcl2hits.size();
4388  return;
4389  } // clLA
4390 
4391  // number of hits at the leading edge that we will fit
4392  unsigned short fitLen = fNHitsAve[pass];
4393  // start fitting charge when there are at least 6 hits if we are tracking
4394  // long clusters
4395  if (fitLen > 5 && // Fit 6 hits when tracking long clusters AND
4396  fcl2hits.size() > 5 && // there are at least 6 hits AND
4397  fcl2hits.size() < fitLen) // there are less than fNHitsAve[pass]
4398  fitLen = 5;
4399 
4400  // don't find the average charge --> no charge cut is made
4401  if (fNHitsAve[pass] < 1) return;
4402 
4403  if (fNHitsAve[pass] == 1) {
4404  // simply use the charge and width the last hit
4405  fAveChg = fHits[fcl2hits[ih0]].Integral();
4406  fChgSlp = 0.;
4407  }
4408  else if (fNHitsAve[pass] == 2) {
4409  // average the last two points if requested
4410  fAveChg = (fHits[fcl2hits[ih0]].Integral() + fHits[fcl2hits[ih0 - 1]].Integral()) / 2.;
4411  fChgSlp = 0.;
4412  }
4413  else if ((unsigned short)fcl2hits.size() > fitLen) {
4414  // do a real fit
4415  std::vector<float> xwir;
4416  std::vector<float> ychg;
4417  std::vector<float> ychgerr2;
4418  // origin of the fit
4419  unsigned int wire0 = fHits[fcl2hits[fcl2hits.size() - 1]].WireID().Wire;
4420  // find the mean and rms of the charge
4421  unsigned short npt = 0;
4422  unsigned short imlast = 0;
4423  float ave = 0.;
4424  float rms = 0.;
4425  // this loop intentionally ignores the Begin hit
4426  for (unsigned int ii = fcl2hits.size() - 1; ii > 0; --ii) {
4427  ++npt;
4428  float chg = fHits[fcl2hits[ii]].Integral();
4429  ave += chg;
4430  rms += chg * chg;
4431  if (npt == fitLen) {
4432  imlast = ii;
4433  break;
4434  }
4435  }
4436  float fnpt = npt;
4437  ave /= fnpt;
4438  rms = std::sqrt((rms - fnpt * ave * ave) / (fnpt - 1));
4439  float chgcut = ave + rms;
4440  float chg;
4441  unsigned int wire;
4442  for (unsigned short ii = fcl2hits.size() - 1; ii > imlast; --ii) {
4443  wire = fHits[fcl2hits[ii]].WireID().Wire;
4444  chg = fHits[fcl2hits[ii]].Integral();
4445  if (chg > chgcut) continue;
4446  xwir.push_back((float)(wire - wire0));
4447  ychg.push_back(chg);
4448  ychgerr2.push_back(chg);
4449  }
4450  if (ychg.size() < 3) return;
4451  float intcpt;
4452  float slope;
4453  float intcpterr;
4454  float slopeerr;
4455  float chidof;
4456  fLinFitAlg.LinFit(xwir, ychg, ychgerr2, intcpt, slope, intcpterr, slopeerr, chidof);
4457  if (prt)
4458  mf::LogVerbatim("CC") << "FitClusterChg wire " << wire0 << " chidof " << (int)chidof
4459  << " npt " << xwir.size() << " charge = " << (int)intcpt
4460  << " slope = " << (int)slope << " first ave " << (int)ave << " rms "
4461  << (int)rms;
4462  if (chidof > 100.) return;
4463  // fit must have gone wrong if the fStepCrawlChgRatCut average is greater than
4464  // the average using all points
4465  if (intcpt > ave) return;
4466  // ensure that change does not exceed 30%
4467  if (fAveChg > 0) {
4468  ave = intcpt / fAveChg;
4469  if (ave > 1.3) return;
4470  if (ave < 0.77) return;
4471  }
4472  fAveChg = intcpt;
4473  fChgSlp = slope;
4474  }
4475  } // fitchg
4476 
4478  void ClusterCrawlerAlg::AddLAHit(unsigned int kwire, bool& ChkCharge, bool& HitOK, bool& SigOK)
4479  {
4480  // A variant of AddHit for large angle clusters
4481 
4482  SigOK = false;
4483  HitOK = false;
4484 
4485  // not in the range of wires with hits
4486  if (kwire < fFirstWire || kwire > fLastWire) return;
4487 
4488  if (fcl2hits.size() == 0) return;
4489 
4490  // skip bad wire and assume the track was there
4491  if (WireHitRange[kwire].first == -1) {
4492  SigOK = true;
4493  return;
4494  }
4495  // return SigOK false if no hit on a good wire
4496  if (WireHitRange[kwire].first == -2) return;
4497 
4498  unsigned int firsthit = WireHitRange[kwire].first;
4499  unsigned int lasthit = WireHitRange[kwire].second;
4500 
4501  // max allowable time difference between projected cluster and a hit
4502  float timeDiff = 40 * AngleFactor(clpar[1]);
4503  float dtime;
4504 
4505  // the last hit added to the cluster
4506  unsigned int lastClHit = UINT_MAX;
4507  if (fcl2hits.size() > 0) {
4508  lastClHit = fcl2hits[fcl2hits.size() - 1];
4509  if (lastClHit == 0) {
4510  fAveChg = fHits[lastClHit].Integral();
4511  fAveHitWidth = fHits[lastClHit].EndTick() - fHits[lastClHit].StartTick();
4512  }
4513  } // fcl2hits.size() > 0
4514 
4515  // the projected time of the cluster on this wire
4516  float prtime = clpar[0] + ((float)kwire - clpar[2]) * clpar[1];
4517  float chgWinLo = prtime - fChgNearWindow;
4518  float chgWinHi = prtime + fChgNearWindow;
4519  float chgrat, hitWidth;
4520  float hitWidthCut = 0.5 * fAveHitWidth;
4521  float cnear = 0;
4522  // float fom;
4523  if (prt)
4524  mf::LogVerbatim("CC") << "AddLAHit: wire " << kwire << " prtime " << prtime
4525  << " max timeDiff " << timeDiff << " fAveChg " << fAveChg;
4526  unsigned int imbest = 0;
4527  unsigned int khit;
4528  for (khit = firsthit; khit < lasthit; ++khit) {
4529  // obsolete hit?
4530  if (inClus[khit] < 0) continue;
4531  dtime = std::abs(fHits[khit].PeakTime() - prtime);
4532  hitWidth = fHits[khit].EndTick() - fHits[khit].StartTick();
4533  chgrat = 1;
4534  if (ChkCharge && fAveChg > 0) { chgrat = fHits[khit].Integral() / fAveChg; }
4535  if (prt)
4536  mf::LogVerbatim("CC") << " Chk W:T " << kwire << ":" << (short)fHits[khit].PeakTime()
4537  << " dT " << std::fixed << std::setprecision(1) << dtime << " InClus "
4538  << inClus[khit] << " mult " << fHits[khit].Multiplicity() << " width "
4539  << (int)hitWidth << " MergeAvail " << mergeAvailable[khit] << " Chi2 "
4540  << std::fixed << std::setprecision(2) << fHits[khit].GoodnessOfFit()
4541  << " Charge " << (int)fHits[khit].Integral() << " chgrat "
4542  << std::fixed << std::setprecision(1) << chgrat << " index " << khit;
4543  // count charge in the window
4544  if (fHits[khit].PeakTime() > chgWinLo && fHits[khit].PeakTime() < chgWinHi)
4545  cnear += fHits[khit].Integral();
4546  // projected time outside the Signal time window?
4547  if (prtime < fHits[khit].StartTick() - timeDiff) continue;
4548  if (prtime > fHits[khit].EndTick() + timeDiff) continue;
4549  SigOK = true;
4550  // hit used?
4551  if (inClus[khit] > 0) continue;
4552  // ignore narrow width hits
4553  if (hitWidth < hitWidthCut) continue;
4554  // ignore very low charge hits
4555  if (chgrat < 0.1) continue;
4556  if (dtime < timeDiff) {
4557  HitOK = true;
4558  imbest = khit;
4559  timeDiff = dtime;
4560  }
4561  } // khit
4562 
4563  if (prt && !HitOK) mf::LogVerbatim("CC") << " no hit found ";
4564 
4565  if (!HitOK) return;
4566 
4567  if (prt)
4568  mf::LogVerbatim("CC") << " Pick hit time " << (int)fHits[imbest].PeakTime() << " hit index "
4569  << imbest;
4570 
4571  // merge hits in a multiplet?
4572  short hnear = 0;
4573  if (lastClHit != UINT_MAX && fHits[imbest].Multiplicity() > 1) {
4574  bool doMerge = true;
4575  // Standard code
4576  // don't merge if we are close to a vertex
4577  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
4578  if (vtx[ivx].CTP != clCTP) continue;
4579  if (prt)
4580  mf::LogVerbatim("CC") << " close vtx chk W:T " << vtx[ivx].Wire << ":"
4581  << (int)vtx[ivx].Time;
4582  if (std::abs(kwire - vtx[ivx].Wire) < 5 &&
4583  std::abs(int(fHits[imbest].PeakTime() - vtx[ivx].Time)) < 20) {
4584  if (prt) mf::LogVerbatim("CC") << " Close to a vertex. Don't merge hits";
4585  doMerge = false;
4586  }
4587  } // ivx
4588  // Decide which hits in the multiplet to merge. Hits that are well
4589  // separated from each other should not be merged
4590  if (doMerge) {
4591  unsigned short nused = 0;
4592  // the total charge of the hit multiplet
4593  float multipletChg = 0.;
4594  float chicut = AngleFactor(clpar[1]) * fHitMergeChiCut * fHits[lastClHit].RMS();
4595  // look for a big separation between adjacent hits
4596  std::pair<size_t, size_t> MultipletRange = FindHitMultiplet(imbest);
4597  for (size_t jht = MultipletRange.first; jht < MultipletRange.second; ++jht) {
4598  if (inClus[jht] < 0) continue;
4599  if (inClus[jht] == 0)
4600  multipletChg += fHits[jht].Integral();
4601  else
4602  ++nused;
4603  // check the neighbor hit separation
4604  if (jht > MultipletRange.first) {
4605  // pick the larger RMS of the two hits
4606  float hitRMS = fHits[jht].RMS();
4607  if (fHits[jht - 1].RMS() > hitRMS) hitRMS = fHits[jht - 1].RMS();
4608  const float tdiff =
4609  std::abs(fHits[jht].PeakTime() - fHits[jht - 1].PeakTime()) / hitRMS;
4610  if (prt) mf::LogVerbatim("CC") << " Hit RMS chisq " << tdiff << " chicut " << chicut;
4611  if (tdiff > chicut) doMerge = false;
4612  } // jht > 0
4613  } // jht
4614  if (prt) {
4615  if (!doMerge) mf::LogVerbatim("CC") << " Hits are well separated. Don't merge them ";
4616  }
4617  if (doMerge && nused == 0) {
4618  // compare the charge with the last hit added?
4619  if (ChkCharge) {
4620  // there is a nearby hit
4621  hnear = 1;
4622  float chgrat = multipletChg / fHits[lastClHit].Integral();
4623  if (prt)
4624  mf::LogVerbatim("CC") << " merge hits charge check " << (int)multipletChg
4625  << " Previous hit charge " << (int)fHits[lastClHit].Integral();
4626  if (chgrat > 2) doMerge = false;
4627  }
4628  } // doMerge && nused == 0
4629  } // doMerge true
4630  if (doMerge) {
4631  // there is a nearby hit and it will be merged
4632  hnear = -1;
4633  bool didMerge;
4634  MergeHits(imbest, didMerge);
4635  } // doMerge
4636  } // Hits[imbest].Multiplicity() > 1
4637 
4638  // attach to the cluster and fit
4639  fcl2hits.push_back(imbest);
4640  FitCluster();
4641  FitClusterChg();
4642  chifits.push_back(clChisq);
4643  hitNear.push_back(hnear);
4644  // remove the charge of the just added hit
4645  cnear -= fHits[imbest].Integral();
4646  if (cnear < 0) cnear = 0;
4647  // divide by the just added hit charge
4648  cnear /= fHits[imbest].Integral();
4649  chgNear.push_back(cnear);
4650  if (prt) {
4651  hitWidth = fHits[imbest].EndTick() - fHits[imbest].StartTick();
4652  mf::LogVerbatim("CC") << " >>LADD" << pass << " W:T " << PrintHit(imbest) << " dT "
4653  << timeDiff << " clChisq " << clChisq << " Chg "
4654  << (int)fHits[imbest].Integral() << " AveChg " << (int)fAveChg
4655  << " width " << (int)hitWidth << " AveWidth " << (int)fAveHitWidth
4656  << " fcl2hits size " << fcl2hits.size();
4657  } // prt
4658  // decide what to do with a bad fit
4659  if (clChisq > fChiCut[pass]) {
4660  FclTrimUS(1);
4661  FitCluster();
4662  HitOK = false;
4663  SigOK = false;
4664  if (prt) mf::LogVerbatim("CC") << " LADD- Removed bad hit. Stopped tracking";
4665  }
4666  } // AddLAHit()
4667 
4669  bool ClusterCrawlerAlg::ClusterHitsOK(short nHitChk)
4670  {
4671  // Check StartTick and EndTick of hits on adjacent wires overlap as illustrated below.
4672  // >>>>>> This is OK
4673  // Wire StartTick EndTick
4674  // n |--------------|
4675  // n+1 |--------------|
4676  // n+2 |--------------|
4677  // >>>>>> This is NOT OK
4678  // n |------|
4679  // n+1 |-----|
4680  // n+2 |------|
4681 
4682  if (fcl2hits.size() == 0) return true;
4683 
4684  unsigned short nHitToChk = fcl2hits.size();
4685  if (nHitChk > 0) nHitToChk = nHitChk + 1;
4686  unsigned short ii, indx;
4687 
4688  // require that they overlap
4689  // add a tolerance to the StartTick - EndTick overlap
4690  raw::TDCtick_t tol = 30;
4691  // expand the tolerance for induction planes
4692  if (plane < geom->TPC(geo::TPCID(cstat, tpc)).Nplanes() - 1) tol = 40;
4693 
4694  bool posSlope =
4695  (fHits[fcl2hits[0]].PeakTime() > fHits[fcl2hits[fcl2hits.size() - 1]].PeakTime());
4696 
4697  if (prt) {
4698  for (ii = 0; ii < nHitToChk; ++ii) {
4699  indx = fcl2hits.size() - 1 - ii;
4700  mf::LogVerbatim("CC") << "ClusterHitsOK chk " << fHits[fcl2hits[indx]].WireID().Wire
4701  << " start " << fHits[fcl2hits[indx]].StartTick() << " peak "
4702  << fHits[fcl2hits[indx]].PeakTime() << " end "
4703  << fHits[fcl2hits[indx]].EndTick() << " posSlope " << posSlope;
4704  }
4705  }
4706 
4707  raw::TDCtick_t hiStartTick, loEndTick;
4708  for (ii = 0; ii < nHitToChk - 1; ++ii) {
4709  indx = fcl2hits.size() - 1 - ii;
4710  // ignore if not on adjacent wires
4711  if (lar::util::absDiff(fHits[fcl2hits[indx]].WireID().Wire,
4712  fHits[fcl2hits[indx - 1]].WireID().Wire) > 1)
4713  continue;
4714  hiStartTick =
4715  std::max(fHits[fcl2hits[indx]].StartTick(), fHits[fcl2hits[indx - 1]].StartTick());
4716  loEndTick = std::min(fHits[fcl2hits[indx]].EndTick(), fHits[fcl2hits[indx - 1]].EndTick());
4717  if (posSlope) {
4718  if (loEndTick + tol < hiStartTick) { return false; }
4719  }
4720  else {
4721  if (loEndTick + tol < hiStartTick) { return false; }
4722  }
4723  } // ii
4724  return true;
4725  } // ClusterHitsOK
4726 
4728  void ClusterCrawlerAlg::AddHit(unsigned int kwire, bool& HitOK, bool& SigOK)
4729  {
4730  // Add a hit to the cluster if it meets several criteria:
4731  // similar pulse height to the cluster (if fAveChg is defined)
4732  // closest hit to the project cluster position.
4733  // Return SigOK if there is a nearby hit that was missed due to the cuts
4734 
4735  SigOK = false;
4736  HitOK = false;
4737 
4738  // not in the range of wires with hits
4739  if (kwire < fFirstWire || kwire > fLastWire) return;
4740 
4741  unsigned int lastClHit = UINT_MAX;
4742  if (fcl2hits.size() > 0) lastClHit = fcl2hits[fcl2hits.size() - 1];
4743 
4744  // the last hit added to the cluster
4745  unsigned int wire0 = clpar[2];
4746 
4747  // return if no signal and no hit
4748  if (fAllowNoHitWire == 0) {
4749  if (WireHitRange[kwire].first == -2) return;
4750  }
4751  else {
4752  // allow a number of wires with no hits
4753  if (WireHitRange[kwire].first == -2 && (wire0 - kwire) > fAllowNoHitWire) {
4754  SigOK = true;
4755  return;
4756  }
4757  }
4758  // skip bad wire, but assume the track was there
4759  if (WireHitRange[kwire].first == -1) {
4760  SigOK = true;
4761  return;
4762  }
4763 
4764  unsigned int firsthit = WireHitRange[kwire].first;
4765  unsigned int lasthit = WireHitRange[kwire].second;
4766 
4767  // the projected time of the cluster on this wire
4768  float dw = (float)kwire - (float)wire0;
4769  float prtime = clpar[0] + dw * clpar[1];
4770  if (prtime < 0 || (unsigned int)prtime > fMaxTime) return;
4771  // Find the projected time error including the projection error and the
4772  // error from the last hit added
4773  float prtimerr2 = std::abs(dw) * clparerr[1] * clparerr[1];
4774 
4775  // apply an angle dependent scale factor to the hit error. Default is very large error
4776  float hiterr = 10;
4777  if (lastClHit != UINT_MAX) hiterr = 3 * fHitErrFac * fHits[lastClHit].RMS();
4778  float err = std::sqrt(prtimerr2 + hiterr * hiterr);
4779  // Time window for accepting a hit.
4780  float hitWin = fClProjErrFac * err;
4781 
4782  float prtimeLo = prtime - hitWin;
4783  float prtimeHi = prtime + hitWin;
4784  float chgWinLo = prtime - fChgNearWindow;
4785  float chgWinHi = prtime + fChgNearWindow;
4786  if (prt) {
4787  mf::LogVerbatim("CC") << "AddHit: wire " << kwire << " prtime Lo " << (int)prtimeLo
4788  << " prtime " << (int)prtime << " Hi " << (int)prtimeHi << " prtimerr "
4789  << sqrt(prtimerr2) << " hiterr " << hiterr << " fAveChg "
4790  << (int)fAveChg << " fAveHitWidth " << std::setprecision(3)
4791  << fAveHitWidth;
4792  }
4793  // loop through the hits
4794  unsigned int imbest = INT_MAX;
4795  float best = 9999., dtime;
4796  float cnear = 0;
4797  float hitTime, hitChg, hitStartTick, hitEndTick;
4798  for (unsigned int khit = firsthit; khit < lasthit; ++khit) {
4799  // obsolete hit?
4800  if (inClus[khit] < 0) continue;
4801  hitTime = fHits[khit].PeakTime();
4802  dtime = std::abs(hitTime - prtime);
4803  if (dtime > 1000) continue;
4804  hitStartTick = fHits[khit].StartTick();
4805  hitEndTick = fHits[khit].EndTick();
4806  // weight by the charge difference
4807  if (fAveChg > 0) dtime *= std::abs(fHits[khit].Integral() - fAveChg) / fAveChg;
4808  if (prt && std::abs(dtime) < 100) {
4809  mf::LogVerbatim("CC") << " Chk W:T " << PrintHit(khit) << " dT " << std::fixed
4810  << std::setprecision(1) << (hitTime - prtime) << " InClus "
4811  << inClus[khit] << " mult " << fHits[khit].Multiplicity() << " RMS "
4812  << std::fixed << std::setprecision(1) << fHits[khit].RMS() << " Chi2 "
4813  << std::fixed << std::setprecision(1) << fHits[khit].GoodnessOfFit()
4814  << " Charge " << (int)fHits[khit].Integral() << " Peak " << std::fixed
4815  << std::setprecision(1) << fHits[khit].PeakAmplitude() << " LoT "
4816  << (int)hitStartTick << " HiT " << (int)hitEndTick << " index "
4817  << khit;
4818  }
4819  // count charge in the window
4820  if (fHits[khit].StartTick() > chgWinLo && fHits[khit].EndTick() < chgWinHi)
4821  cnear += fHits[khit].Integral();
4822  // check for signal
4823  if (prtimeHi < hitStartTick) continue;
4824  if (prtimeLo > hitEndTick) continue;
4825  SigOK = true;
4826  // check for good hit
4827  if (hitTime < prtimeLo) continue;
4828  if (hitTime > prtimeHi) continue;
4829  // hit used?
4830  if (inClus[khit] > 0) continue;
4831  if (dtime < best) {
4832  best = dtime;
4833  imbest = khit;
4834  }
4835  } // khit
4836 
4837  if (!SigOK) {
4838  if (fAllowNoHitWire == 0) return;
4839  if (prt)
4840  mf::LogVerbatim("CC") << " wire0 " << wire0 << " kwire " << kwire << " max "
4841  << fAllowNoHitWire << " imbest " << imbest;
4842  if ((wire0 - kwire) > fAllowNoHitWire) return;
4843  SigOK = true;
4844  }
4845 
4846  if (imbest == INT_MAX) return;
4847 
4848  recob::Hit const& hit = fHits[imbest];
4849  hitChg = hit.Integral();
4850 
4851  if (prt) mf::LogVerbatim("CC") << " Best hit time " << (int)hit.PeakTime();
4852 
4853  short hnear = 0;
4854  // merge hits in a doublet?
4855  bool didMerge = false;
4856  if (lastClHit != UINT_MAX && fAveHitWidth > 0 && fHitMergeChiCut > 0 &&
4857  hit.Multiplicity() == 2) {
4858  bool doMerge = true;
4859  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
4860  if (std::abs(kwire - vtx[ivx].Wire) < 10 &&
4861  std::abs(int(hit.PeakTime() - vtx[ivx].Time)) < 20) {
4862  doMerge = false;
4863  break;
4864  }
4865  } // ivx
4866  // quit if localindex does not make sense.
4867  if (hit.LocalIndex() != 0 && imbest == 0) doMerge = false;
4868  if (doMerge) {
4869  // find the neighbor hit
4870  unsigned int oht;
4871  if (hit.LocalIndex() == 0) { oht = imbest + 1; }
4872  else {
4873  oht = imbest - 1;
4874  } // hit.LocalIndex() == 0
4875  // check the hit time separation
4876  recob::Hit const& other_hit = fHits[oht];
4877  float hitSep = std::abs(hit.PeakTime() - other_hit.PeakTime());
4878  hitSep /= hit.RMS();
4879  // check the the charge similarity
4880  float totChg = hitChg + other_hit.Integral();
4881  float lastHitChg = fAveChg;
4882  if (lastHitChg < 0) lastHitChg = fHits[lastClHit].Integral();
4883  hnear = 1;
4884  if (prt)
4885  mf::LogVerbatim("CC") << " Chk hit merge hitsep " << hitSep << " dChg "
4886  << std::abs(totChg - lastHitChg) << " Cut "
4887  << std::abs(hit.Integral() - lastHitChg);
4888  if (inClus[oht] == 0 && hitSep < fHitMergeChiCut) {
4889  if (prt) mf::LogVerbatim("CC") << " Merging hit doublet " << imbest;
4890  MergeHits(imbest, didMerge);
4891  if (prt && !didMerge) mf::LogVerbatim("CC") << " Hit merge failed ";
4892  } // not in a cluster, hitSep OK, total charge OK
4893  } // doMerge
4894  } // fHitMergeChiCut > 0 && hit.Multiplicity() == 2
4895 
4896  // Make a charge similarity cut if the average charge is defined
4897  bool fitChg = true;
4898  if (fAveChg > 0.) {
4899 
4900  float chgrat = (hitChg - fAveChg) / fAveChg;
4901  if (prt) mf::LogVerbatim("CC") << " Chgrat " << std::setprecision(2) << chgrat;
4902 
4903  // charge is way too high?
4904  if (chgrat > 3 * fChgCut[pass]) {
4905  if (prt)
4906  mf::LogVerbatim("CC") << " fails 3 x high charge cut " << fChgCut[pass] << " on pass "
4907  << pass;
4908  return;
4909  }
4910 
4911  // Determine if the last hit added was a large (low) charge hit
4912  // This will be used to prevent adding large (low) charge hits on two
4913  // consecutive fits. This cut is only applied to hits on adjacent wires
4914  float bigchgcut = 1.5 * fChgCut[pass];
4915  bool lasthitbig = false;
4916  bool lasthitlow = false;
4917  if (lastClHit != UINT_MAX && lar::util::absDiff(wire0, kwire) == 1) {
4918  float lastchgrat = (fHits[lastClHit].Integral() - fAveChg) / fAveChg;
4919  lasthitbig = (lastchgrat > bigchgcut);
4920  lasthitlow = (lastchgrat < -fChgCut[pass]);
4921  }
4922 
4923  // the last hit added was low charge and this one is as well
4924  if (lasthitlow && chgrat < -fChgCut[pass]) {
4925  if (prt) mf::LogVerbatim("CC") << " fails low charge cut. Stop crawling.";
4926  SigOK = false;
4927  return;
4928  } // lasthitlow
4929 
4930  // the last hit was high charge and this one is also
4931  if (lasthitbig && chgrat > fChgCut[pass]) {
4932  if (prt)
4933  mf::LogVerbatim("CC") << " fails 2nd hit high charge cut. Last hit was high also. ";
4934  return;
4935  } // lasthitbig
4936 
4937  // require that large charge hits have a very good projection error
4938  if (chgrat > fChgCut[pass]) {
4939  if (best > 2 * err) {
4940  if (prt) mf::LogVerbatim("CC") << " high charge && bad dT= " << best << " err= " << err;
4941  return;
4942  }
4943  } // chgrat > fChgCut[pass]
4944 
4945  // decide whether to fit the charge
4946  fitChg = (chgrat < std::abs(fChgCut[pass]));
4947  } // fAveChg > 0
4948 
4949  // we now have a hit that meets all the criteria. Fit it
4950  fcl2hits.push_back(imbest);
4951  // This is strictly only necessary when calling AddHit for seed clusters
4952  if (fcl2hits.size() == 3) std::sort(fcl2hits.begin(), fcl2hits.end(), SortByLowHit);
4953  FitCluster();
4954  chifits.push_back(clChisq);
4955  hitNear.push_back(hnear);
4956  // remove the charge of the just added hit
4957  cnear -= fHits[imbest].Integral();
4958  if (cnear < 0) cnear = 0;
4959  // divide by the just added hit charge
4960  cnear /= fHits[imbest].Integral();
4961  chgNear.push_back(cnear);
4962  // nearby hit check
4963  // ChkClusterNearbyHits(prt);
4964  HitOK = true;
4965 
4966  if (chgNear.size() != fcl2hits.size()) {
4967  mf::LogError("CC") << "AddHit: Bad length";
4968  return;
4969  }
4970 
4971  if (prt)
4972  mf::LogVerbatim("CC") << " >>ADD" << pass << " W:T " << PrintHit(imbest) << " dT " << best
4973  << " clChisq " << clChisq << " Chg " << (int)fHits[imbest].Integral()
4974  << " AveChg " << (int)fAveChg << " fcl2hits size " << fcl2hits.size();
4975 
4976  if (!fitChg) return;
4977  if (prt) mf::LogVerbatim("CC") << " Fit charge ";
4978  FitClusterChg();
4979  } // AddHit()
4980 
4982  void ClusterCrawlerAlg::ChkClusterNearbyHits(bool prt)
4983  {
4984  // analyze the hitnear vector
4985  // 0 = no nearby hit exists
4986  // 1 = a nearby hit exists but was not merged
4987  // -1 = a nearby hit was merged
4988 
4989  if (fHitMergeChiCut <= 0) return;
4990 
4991  if (hitNear.size() != fcl2hits.size()) {
4992  mf::LogWarning("CC") << "Coding error: hitNear size != fcl2hits";
4993  return;
4994  }
4995 
4996  // Analyze the last 6 hits added but don't consider the first few hits
4997  if (hitNear.size() < 12) return;
4998 
4999  // TODO move into loops
5000  unsigned short ii, indx;
5001  unsigned short merged = 0;
5002  unsigned short notmerged = 0;
5003  for (ii = 0; ii < 6; ++ii) {
5004  indx = hitNear.size() - 1 - ii;
5005  if (hitNear[indx] > 0) ++notmerged;
5006  if (hitNear[indx] < 0) ++merged;
5007  }
5008 
5009  if (prt)
5010  mf::LogVerbatim("CC") << "ChkClusterNearbyHits: nearby hits merged " << merged
5011  << " not merged " << notmerged;
5012 
5013  if (notmerged < 2) return;
5014 
5015  // a number of nearby hits were not merged while crawling, so the
5016  // average charge is probably wrong. Look at the last 6 hits added
5017  // and merge them if they are close
5018  bool didMerge;
5019  for (ii = 0; ii < 6; ++ii) {
5020  indx = fcl2hits.size() - 1 - ii;
5021  const unsigned int iht = fcl2hits[indx];
5022  recob::Hit const& hit = fHits[iht];
5023  if (hit.Multiplicity() == 2) {
5024  // quit if localindex does not make sense.
5025  if (hit.LocalIndex() != 0 && iht == 0) continue;
5026  // hit doublet. Get the index of the other hit
5027  unsigned int oht;
5028  if (hit.LocalIndex() == 0) { oht = iht + 1; }
5029  else {
5030  oht = iht - 1;
5031  } // hit.LocalIndex() == 0
5032  recob::Hit const& other_hit = fHits[oht];
5033  // TODO use Hit::TimeDistanceAsRMS()
5034  float hitSep = std::abs(hit.PeakTime() - other_hit.PeakTime());
5035  hitSep /= hit.RMS();
5036  if (hitSep < fHitMergeChiCut && inClus[oht] == 0) {
5037  if (prt)
5038  mf::LogVerbatim("CC") << "Merging hit doublet " << iht << " W:T "
5039  << fHits[iht].WireID().Wire << ":" << fHits[iht].PeakTime();
5040  MergeHits(iht, didMerge);
5041  if (didMerge) hitNear[indx] = -1;
5042  } // hitSep OK and not in a cluster
5043  } // hit doublet
5044  } // ii
5045 
5046  // now re-fit
5047  FitCluster();
5048  FitClusterChg();
5049 
5050  if (prt) mf::LogVerbatim("CC") << "ChkClusterNearbyHits refit cluster. fAveChg= " << fAveChg;
5051 
5052  } // ChkClusterHitNear()
5053 
5055  void ClusterCrawlerAlg::FitVtx(unsigned short iv)
5056  {
5057  std::vector<float> x;
5058  std::vector<float> y;
5059  std::vector<float> ey2;
5060  float arg;
5061 
5062  // don't fit fixed vertices
5063  if (vtx[iv].Fixed) return;
5064 
5065  // Set this large in case something bad happens
5066  vtx[iv].ChiDOF = 99;
5067 
5068  // make a list of clusters
5069  unsigned short icl;
5070  std::vector<unsigned short> vcl;
5071  for (icl = 0; icl < tcl.size(); ++icl) {
5072  if (tcl[icl].ID < 0) continue;
5073  if (tcl[icl].CTP != vtx[iv].CTP) continue;
5074  if (tcl[icl].EndVtx == iv) vcl.push_back(icl);
5075  if (tcl[icl].BeginVtx == iv) vcl.push_back(icl);
5076  }
5077 
5078  vtx[iv].NClusters = vcl.size();
5079 
5080  if (vcl.size() == 0) return;
5081 
5082  // don't let the time error be less than the expected
5083  // time error of hits on a cluster. This is crude by
5084  // probably good enough
5085  icl = vcl[0];
5086  unsigned short indx = tcl[icl].tclhits.size() - 1;
5087  unsigned int hit = tcl[icl].tclhits[indx];
5088  float minTimeErr = fHitErrFac * fHits[hit].RMS() * fHits[hit].Multiplicity();
5089 
5090  if (vcl.size() == 1) {
5091  icl = vcl[0];
5092  // Put the vertex at the appropriate end of the cluster
5093  if (tcl[icl].EndVtx == iv) {
5094  vtx[iv].Wire = tcl[icl].EndWir;
5095  vtx[iv].WireErr = 1;
5096  vtx[iv].Time = tcl[icl].EndTim;
5097  // set the vertex time error to the hit error used for fitting
5098  indx = tcl[icl].tclhits.size() - 1;
5099  hit = tcl[icl].tclhits[indx];
5100  vtx[iv].TimeErr = fHitErrFac * fHits[hit].RMS() * fHits[hit].Multiplicity();
5101  vtx[iv].ChiDOF = 0;
5102  }
5103  if (tcl[icl].BeginVtx == iv) {
5104  vtx[iv].Wire = tcl[icl].BeginWir;
5105  vtx[iv].WireErr = 1;
5106  vtx[iv].Time = tcl[icl].BeginTim;
5107  // set the vertex time error to the hit error used for fitting
5108  hit = tcl[icl].tclhits[0];
5109  vtx[iv].TimeErr = fHitErrFac * fHits[hit].RMS() * fHits[hit].Multiplicity();
5110  vtx[iv].ChiDOF = 0;
5111  }
5112  return;
5113  } // size 1
5114 
5115  std::vector<double> slps;
5116  std::vector<double> slperrs;
5117  for (unsigned short ii = 0; ii < vcl.size(); ++ii) {
5118  icl = vcl[ii];
5119  if (tcl[icl].EndVtx == iv) {
5120  x.push_back(tcl[icl].EndSlp);
5121  slps.push_back(tcl[icl].EndSlp);
5122  slperrs.push_back(tcl[icl].EndSlpErr);
5123  arg = tcl[icl].EndSlp * tcl[icl].EndWir - tcl[icl].EndTim;
5124  y.push_back(arg);
5125  if (tcl[icl].EndSlpErr > 0.) { arg = tcl[icl].EndSlpErr * tcl[icl].EndWir; }
5126  else {
5127  arg = .1 * tcl[icl].EndWir;
5128  }
5129  ey2.push_back(arg * arg);
5130  }
5131  else if (tcl[icl].BeginVtx == iv) {
5132  x.push_back(tcl[icl].BeginSlp);
5133  slps.push_back(tcl[icl].BeginSlp);
5134  slperrs.push_back(tcl[icl].BeginSlpErr);
5135  arg = tcl[icl].BeginSlp * tcl[icl].BeginWir - tcl[icl].BeginTim;
5136  y.push_back(arg);
5137  if (tcl[icl].BeginSlpErr > 0.) { arg = tcl[icl].BeginSlpErr * tcl[icl].BeginWir; }
5138  else {
5139  arg = .1 * tcl[icl].BeginWir;
5140  }
5141  ey2.push_back(arg * arg);
5142  }
5143  } // ii
5144  if (x.size() < 2) return;
5145 
5146  // calculate error
5147  double sumerr = 0, cnt = 0;
5148  for (unsigned short ii = 0; ii < slps.size() - 1; ++ii) {
5149  for (unsigned short jj = ii + 1; jj < slps.size(); ++jj) {
5150  arg = std::min(slperrs[ii], slperrs[jj]);
5151  arg /= (slps[ii] - slps[jj]);
5152  sumerr += arg * arg;
5153  ++cnt;
5154  } // jj
5155  } // ii
5156  sumerr /= cnt;
5157 
5158  float vTime = 0.;
5159  float vTimeErr = 0.;
5160  float vWire = 0.;
5161  float vWireErr = 0.;
5162  float chiDOF;
5163  fLinFitAlg.LinFit(x, y, ey2, vTime, vWire, vTimeErr, vWireErr, chiDOF);
5164  if (chiDOF > 900) return;
5165  vTime = -vTime;
5166  // a crazy time from the fit?
5167  if (vTime < 0 || vTime > fMaxTime) return;
5168  // a crazy wire from the fit?
5169  geo::PlaneID iplID = DecodeCTP(vtx[iv].CTP);
5170  if (vWire < 0 || vWire > geom->Nwires(iplID)) return;
5171  vtx[iv].ChiDOF = chiDOF;
5172  vtx[iv].Wire = vWire;
5173  vtx[iv].Time = vTime;
5174  vtx[iv].WireErr = vWire * sqrt(sumerr);
5175  vtx[iv].TimeErr = vTime * fabs(sumerr);
5176  // set minimum wire error
5177  if (vtx[iv].WireErr < 1) vtx[iv].WireErr = 2;
5178  // set minimum time error
5179  if (vtx[iv].TimeErr < minTimeErr) vtx[iv].TimeErr = minTimeErr;
5180 
5181  } // FitVtx
5182 
5184  void ClusterCrawlerAlg::Vtx3ClusterMatch(detinfo::DetectorPropertiesData const& det_prop,
5185  geo::TPCID const& tpcid)
5186  {
5187  // Look for clusters that end/begin near the expected wire/time
5188  // for incomplete 3D vertices
5189  if (empty(vtx3)) return;
5190 
5191  const unsigned int cstat = tpcid.Cryostat;
5192  const unsigned int tpc = tpcid.TPC;
5193 
5194  unsigned int thePlane, theWire;
5195  float theTime;
5196  int dwb, dwe;
5197 
5198  for (unsigned short ivx = 0; ivx < vtx3.size(); ++ivx) {
5199  // A complete 3D vertex with matching 2D vertices in all planes?
5200  if (vtx3[ivx].Wire < 0) continue;
5201  if (vtx3[ivx].CStat != cstat || vtx3[ivx].TPC != tpc) continue;
5202  // Find the plane that is missing a 2D vertex
5203  thePlane = 3;
5204  theWire = vtx3[ivx].Wire;
5205  for (plane = 0; plane < 3; ++plane) {
5206  if (vtx3[ivx].Ptr2D[plane] >= 0) continue;
5207  thePlane = plane;
5208  break;
5209  } // plane
5210  if (thePlane > 2) continue;
5211  theTime = det_prop.ConvertXToTicks(vtx3[ivx].X, thePlane, tpc, cstat);
5212  clCTP = EncodeCTP(cstat, tpc, thePlane);
5213  // Create a new 2D vertex and see how many clusters we can attach to it
5214  VtxStore vnew;
5215  vnew.Wire = theWire;
5216  vnew.Time = theTime;
5217  vnew.CTP = clCTP;
5218  vnew.Topo = 7;
5219  vnew.Fixed = false;
5220  vtx.push_back(vnew);
5221  unsigned short ivnew = vtx.size() - 1;
5222  std::vector<short> vclIndex;
5223  for (unsigned short icl = 0; icl < tcl.size(); ++icl) {
5224  if (tcl[icl].ID < 0) continue;
5225  if (tcl[icl].CTP != clCTP) continue;
5226  dwb = lar::util::absDiff(theWire, tcl[icl].BeginWir);
5227  dwe = lar::util::absDiff(theWire, tcl[icl].EndWir);
5228  // rough cut to start
5229  if (dwb > 10 && dwe > 10) continue;
5230  if (dwb < dwe && dwb < 10 && tcl[icl].BeginVtx < 0) {
5231  // cluster begin is closer
5232  if (theWire < tcl[icl].BeginWir + 5) continue;
5233  if (ClusterVertexChi(icl, 0, ivnew) > fVertex3DCut) continue;
5234  tcl[icl].BeginVtx = ivnew;
5235  vclIndex.push_back(icl);
5236  }
5237  else if (dwe < 10 && tcl[icl].EndVtx < 0) {
5238  // cluster end is closer
5239  if (theWire > tcl[icl].EndWir - 5) continue;
5240  if (ClusterVertexChi(icl, 1, ivnew) > fVertex3DCut) continue;
5241  tcl[icl].EndVtx = ivnew;
5242  vclIndex.push_back(icl);
5243  } // dwb/dwe check
5244  } // icl
5245  bool goodVtx = false;
5246  if (vclIndex.size() > 0) {
5247  FitVtx(ivnew);
5248  goodVtx = (vtx[ivnew].ChiDOF < fVertex3DCut);
5249  vtx3[ivx].Ptr2D[thePlane] = ivnew;
5250  }
5251  if (goodVtx) {
5252  vtx3[ivx].Ptr2D[thePlane] = ivnew;
5253  vtx3[ivx].Wire = -1;
5254  }
5255  else {
5256  // clobber the vertex
5257  vtx.pop_back();
5258  for (unsigned short ii = 0; ii < vclIndex.size(); ++ii) {
5259  unsigned short icl = vclIndex[ii];
5260  if (tcl[icl].BeginVtx == ivnew) tcl[icl].BeginVtx = -99;
5261  if (tcl[icl].EndVtx == ivnew) tcl[icl].EndVtx = -99;
5262  } // ii
5263  }
5264  } // ivx
5265  } // Vtx3ClusterMatch
5266 
5268  void ClusterCrawlerAlg::Vtx3ClusterSplit(detinfo::DetectorPropertiesData const& det_prop,
5269  geo::TPCID const& tpcid)
5270  {
5271  // Try to split clusters in a view in which there is no 2D vertex
5272  // assigned to a 3D vertex
5273  if (empty(vtx3)) return;
5274 
5275  const unsigned int cstat = tpcid.Cryostat;
5276  const unsigned int tpc = tpcid.TPC;
5277 
5278  vtxprt = (fDebugPlane >= 0) && (fDebugHit == 6666);
5279 
5280  unsigned int lastplane = 5, kcl, kclID;
5281  float theTime;
5282  unsigned int thePlane, theWire, plane;
5283  unsigned int loWire, hiWire;
5284 
5285  for (unsigned short ivx = 0; ivx < vtx3.size(); ++ivx) {
5286  if (vtx3[ivx].CStat != cstat || vtx3[ivx].TPC != tpc) continue;
5287  // Complete 3D vertex with matching 2D vertices in all planes?
5288  if (vtxprt)
5289  mf::LogVerbatim("CC") << "Vtx3ClusterSplit ivx " << ivx << " Ptr2D " << vtx3[ivx].Ptr2D[0]
5290  << " " << vtx3[ivx].Ptr2D[1] << " " << vtx3[ivx].Ptr2D[2] << " wire "
5291  << vtx3[ivx].Wire;
5292  if (vtx3[ivx].Wire < 0) continue;
5293  // find the plane that needs to be studied
5294  thePlane = 3;
5295  theWire = vtx3[ivx].Wire;
5296  for (plane = 0; plane < 3; ++plane) {
5297  if (vtx3[ivx].Ptr2D[plane] >= 0) continue;
5298  thePlane = plane;
5299  break;
5300  } // plane
5301  if (thePlane > 2) continue;
5302  theTime = det_prop.ConvertXToTicks(
5303  (double)vtx3[ivx].X, (int)thePlane, (int)tpcid.TPC, (int)tpcid.Cryostat);
5304  // get the hit range if necessary
5305  if (thePlane != lastplane) {
5306  clCTP = EncodeCTP(tpcid.Cryostat, tpcid.TPC, thePlane);
5307  GetHitRange(clCTP);
5308  lastplane = thePlane;
5309  }
5310  // make a list of clusters that have hits near this point on nearby wires
5311  std::vector<short> clIDs;
5312  if (theWire > fFirstWire + 5) { loWire = theWire - 5; }
5313  else {
5314  loWire = fFirstWire;
5315  }
5316  if (theWire < fLastWire - 5) { hiWire = theWire + 5; }
5317  else {
5318  hiWire = fLastWire;
5319  }
5320  if (vtxprt)
5321  mf::LogVerbatim("CC") << "3DVtx " << ivx << " look for cluster hits near P:W:T " << thePlane
5322  << ":" << theWire << ":" << (int)theTime << " Wire range " << loWire
5323  << " to " << hiWire;
5324  for (unsigned int wire = loWire; wire < hiWire; ++wire) {
5325  // ignore dead wires or wires with no hits
5326  if (WireHitRange[wire].first < 0) continue;
5327  unsigned int firsthit = WireHitRange[wire].first;
5328  unsigned int lasthit = WireHitRange[wire].second;
5329  for (unsigned int khit = firsthit; khit < lasthit; ++khit) {
5330  // ignore obsolete and un-assigned hits
5331  if (inClus[khit] <= 0) continue;
5332  if ((unsigned int)inClus[khit] > tcl.size() + 1) {
5333  mf::LogError("CC") << "Invalid hit InClus. " << khit << " " << inClus[khit];
5334  continue;
5335  }
5336  // check an expanded time range
5337  if (theTime < fHits[khit].StartTick() - 20) continue;
5338  if (theTime > fHits[khit].EndTick() + 20) continue;
5339  kclID = inClus[khit];
5340  kcl = kclID - 1;
5341  // ignore obsolete clusters
5342  if (tcl[kcl].ID < 0) continue;
5343  // ignore short clusters
5344  if (tcl[kcl].tclhits.size() < 6) continue;
5345  // ignore long straight clusters
5346  if (tcl[kcl].tclhits.size() > 100 && std::abs(tcl[kcl].BeginAng - tcl[kcl].EndAng) < 0.1)
5347  continue;
5348  // put the cluster in the list if it's not there already
5349  if (vtxprt)
5350  mf::LogVerbatim("CC") << "Bingo " << ivx << " plane " << thePlane << " wire " << wire
5351  << " hit " << fHits[khit].WireID().Wire << ":"
5352  << (int)fHits[khit].PeakTime() << " inClus " << inClus[khit]
5353  << " P:W:T " << thePlane << ":" << tcl[kcl].BeginWir << ":"
5354  << (int)tcl[kcl].BeginTim;
5355  if (std::find(clIDs.begin(), clIDs.end(), kclID) == clIDs.end()) {
5356  clIDs.push_back(kclID);
5357  } // std::find
5358  } // khit
5359  } // wire
5360  if (clIDs.size() == 0) continue;
5361  if (vtxprt)
5362  for (unsigned int ii = 0; ii < clIDs.size(); ++ii)
5363  mf::LogVerbatim("CC") << " clIDs " << clIDs[ii];
5364 
5365  unsigned short ii, icl, jj;
5366  unsigned int iht;
5367  short nhitfit;
5368  bool didit;
5369  // find a reasonable time error using the 2D vertices that comprise this
5370  // incomplete 3D vertex
5371  float tErr = 1;
5372  unsigned short i2Dvx = 0;
5373  for (ii = 0; ii < 3; ++ii) {
5374  if (ii == thePlane) continue;
5375  i2Dvx = vtx3[ivx].Ptr2D[ii];
5376  if (i2Dvx > vtx.size() - 1) {
5377  mf::LogError("CC") << "Vtx3ClusterSplit: Coding error";
5378  return;
5379  }
5380  if (vtx[i2Dvx].TimeErr > tErr) tErr = vtx[i2Dvx].TimeErr;
5381  } // ii -> plane
5382 
5383  // do a local fit near the crossing point and make a tighter cut
5384  for (ii = 0; ii < clIDs.size(); ++ii) {
5385  icl = clIDs[ii] - 1;
5386  didit = false;
5387  for (jj = 0; jj < tcl[icl].tclhits.size(); ++jj) {
5388  iht = tcl[icl].tclhits[jj];
5389  if (fHits[iht].WireID().Wire < theWire) {
5390  nhitfit = 3;
5391  if (jj > 3) nhitfit = -3;
5392  FitClusterMid(icl, iht, nhitfit);
5393  float doca = DoCA(-1, 1, theWire, theTime);
5394  if (vtxprt)
5395  mf::LogVerbatim("CC") << " cls " << icl << " DoCA " << doca << " tErr " << tErr;
5396  if ((doca / tErr) > 2) clIDs[ii] = -1;
5397  didit = true;
5398  break;
5399  } // fHits[iht].WireID().Wire < theWire
5400  if (didit) break;
5401  } // jj
5402  if (didit) break;
5403  } // ii
5404  if (vtxprt) {
5405  mf::LogVerbatim("CC") << "clIDs after fit " << clIDs.size();
5406  for (ii = 0; ii < clIDs.size(); ++ii)
5407  mf::LogVerbatim("CC") << " clIDs " << clIDs[ii];
5408  }
5409 
5410  // see if any candidates remain
5411  unsigned short nok = 0;
5412  for (ii = 0; ii < clIDs.size(); ++ii)
5413  if (clIDs[ii] >= 0) ++nok;
5414  if (nok == 0) continue;
5415 
5416  // make a new 2D vertex
5417  VtxStore vnew;
5418  vnew.Wire = theWire;
5419  vnew.WireErr = 1;
5420  vnew.Time = theTime;
5421  vnew.TimeErr = 1;
5422  vnew.Topo = 8;
5423  vnew.CTP = clCTP;
5424  vnew.Fixed = false;
5425  vtx.push_back(vnew);
5426  // update the 2D -> 3D vertex pointer
5427  unsigned short ivnew = vtx.size() - 1;
5428  if (vtxprt)
5429  mf::LogVerbatim("CC") << "Make new 2D vtx " << ivnew << " in plane " << thePlane
5430  << " from 3D vtx " << ivx;
5431  vtx3[ivx].Ptr2D[thePlane] = ivnew;
5432  // either split or attach clusters to this vertex
5433  for (ii = 0; ii < clIDs.size(); ++ii) {
5434  if (clIDs[ii] < 0) continue;
5435  icl = clIDs[ii] - 1;
5436  // find the split position
5437  // split the cluster. Find the split position
5438  unsigned short pos = 0;
5439  for (unsigned short jj = 0; jj < tcl[icl].tclhits.size(); ++jj) {
5440  iht = tcl[icl].tclhits[jj];
5441  if (fHits[iht].WireID().Wire < theWire) {
5442  pos = jj;
5443  break;
5444  }
5445  } // jj
5446  if (pos == 0) {
5447  // vertex is DS of the cluster Begin
5448  tcl[icl].BeginVtx = ivnew;
5449  if (vtxprt) mf::LogVerbatim("CC") << "Attach to Begin " << icl;
5450  }
5451  else if (pos > tcl[icl].tclhits.size()) {
5452  // vertex is US of the cluster Eend
5453  tcl[icl].EndVtx = ivnew;
5454  if (vtxprt) mf::LogVerbatim("CC") << "Attach to End " << icl;
5455  }
5456  else {
5457  // vertex is in the middle of the cluster
5458  if (vtxprt) mf::LogVerbatim("CC") << "Split cluster " << clIDs[ii] << " at pos " << pos;
5459  if (!SplitCluster(icl, pos, ivnew)) {
5460  if (vtxprt) mf::LogVerbatim("CC") << "SplitCluster failed";
5461  continue;
5462  }
5463  tcl[icl].ProcCode += 10000;
5464  tcl[tcl.size() - 1].ProcCode += 10000;
5465  } // pos check
5466  } // ii
5467  // Fit the vertex position
5468  FitVtx(ivnew);
5469  if (vtx[ivnew].ChiDOF < 5 && vtx[ivnew].WireErr < fVertex2DWireErrCut) {
5470  // mark the 3D vertex as complete
5471  vtx3[ivx].Wire = -1;
5472  }
5473  else {
5474  if (vtxprt)
5475  mf::LogVerbatim("CC") << "Bad vtx fit " << ivnew << " ChiDOF " << vtx[ivnew].ChiDOF
5476  << " WireErr " << vtx[ivnew].WireErr;
5477  // Recover (partially) from a bad fit. Leave the ProcCode as-is to trace this problem
5478  vtx.pop_back();
5479  vtx3[ivx].Ptr2D[thePlane] = -1;
5480  // find the cluster - vertex associations
5481  for (jj = 0; jj < tcl.size(); ++jj) {
5482  if (tcl[jj].BeginVtx == ivnew) tcl[jj].BeginVtx = -99;
5483  if (tcl[jj].EndVtx == ivnew) tcl[jj].EndVtx = -99;
5484  } // jj
5485  }
5486  } // ivx
5487 
5488  } // Vtx3ClusterSplit()
5489 
5491  void ClusterCrawlerAlg::FindHammerClusters(detinfo::DetectorPropertiesData const& det_prop)
5492  {
5493  // look for a long cluster that stops at a short cluster in two views. This can occur in a CCmu
5494  // interaction where two protons are emitted back-to-back and are therefore reconstructed as one cluster
5495  // This routine looks for this signature, and if found, splits the short clusters and creates a new 3D vertex.
5496  // This routine only considers the case where the long cluster intersects the short cluster at the US (End) end.
5497 
5498  unsigned int nPln = geom->TPC(geo::TPCID(cstat, tpc)).Nplanes();
5499  if (nPln != 3) return;
5500 
5501  float ew1, ew2, bw2, fvw;
5502 
5503  struct Hammer {
5504  bool Used;
5505  unsigned int Wire; // intersection point of the long cluster and the short cluster
5506  float Tick; // intersection point of the long cluster and the short cluster
5507  float X;
5508  unsigned short longClIndex;
5509  unsigned short shortClIndex;
5510  unsigned short splitPos;
5511  };
5512  std::array<std::vector<Hammer>, 3> hamrVec;
5513 
5514  unsigned int ipl;
5515  bool useit = false;
5516  for (ipl = 0; ipl < 3; ++ipl) {
5517  clCTP = EncodeCTP(cstat, tpc, ipl);
5518  for (unsigned short ic1 = 0; ic1 < tcl.size(); ++ic1) {
5519  if (tcl[ic1].ID < 0) continue;
5520  // require a long cluster
5521  if (tcl[ic1].tclhits.size() < 20) continue;
5522  if (tcl[ic1].CTP != clCTP) continue;
5523  // ignore long clusters with an End vertex assignment
5524  if (tcl[ic1].EndVtx >= 0) continue;
5525  ew1 = tcl[ic1].EndWir;
5526  for (unsigned short ic2 = 0; ic2 < tcl.size(); ++ic2) {
5527  if (tcl[ic2].ID < 0) continue;
5528  // require a short cluster
5529  if (tcl[ic2].tclhits.size() > 20) continue;
5530  // but not too short cluster
5531  if (tcl[ic2].tclhits.size() < 6) continue;
5532  if (tcl[ic2].CTP != clCTP) continue;
5533  ew2 = tcl[ic2].EndWir;
5534  bw2 = tcl[ic2].BeginWir;
5535  // check the US end. The End of the long cluster must lie between the Begin and End wire of the
5536  // short cluster
5537  if (ew1 < ew2 || ew1 > bw2) continue;
5538  // look for intersection of the two clusters
5539  float best = 10;
5540  short ibst = -1;
5541  unsigned short spos = 0;
5542  for (unsigned short ii = 0; ii < tcl[ic2].tclhits.size(); ++ii) {
5543  unsigned int iht = tcl[ic2].tclhits[ii];
5544  float dw = fHits[iht].WireID().Wire - tcl[ic1].EndWir;
5545  float dt = fabs(fHits[iht].PeakTime() - tcl[ic1].EndTim - tcl[ic1].EndSlp * dw);
5546  if (dt < best) {
5547  best = dt;
5548  ibst = iht;
5549  spos = ii;
5550  }
5551  } // ii
5552  if (ibst < 0) continue;
5553  fvw = 0.5 + fHits[ibst].WireID().Wire;
5554  Hammer aHam;
5555  aHam.Used = false;
5556  aHam.Wire = (0.5 + fvw);
5557  aHam.Tick = fHits[ibst].PeakTime();
5558  aHam.X = det_prop.ConvertTicksToX((double)aHam.Tick, (int)ipl, (int)tpc, (int)cstat);
5559  aHam.longClIndex = ic1;
5560  aHam.shortClIndex = ic2;
5561  aHam.splitPos = spos;
5562  unsigned short indx = hamrVec[ipl].size();
5563  hamrVec[ipl].resize(indx + 1);
5564  hamrVec[ipl][indx] = aHam;
5565  useit = true;
5566  } // ic2
5567  if (useit) break;
5568  } // ic1
5569  } // ipl
5570 
5571  unsigned short noham = 0;
5572  for (ipl = 0; ipl < 3; ++ipl)
5573  if (hamrVec[ipl].size() == 0) ++noham;
5574  if (noham > 1) return;
5575 
5576  // Y,Z limits of the detector
5577 
5578  geo::TPCID const tpcid(cstat, tpc);
5579  const geo::TPCGeo& thetpc = geom->TPC(tpcid);
5580  auto const world = thetpc.GetCenter();
5581  float YLo = world.Y() - thetpc.HalfHeight() + 1;
5582  float YHi = world.Y() + thetpc.HalfHeight() - 1;
5583  float ZLo = world.Z() - thetpc.Length() / 2 + 1;
5584  float ZHi = world.Z() + thetpc.Length() / 2 - 1;
5585 
5586  // Match in 3D
5587  float dX;
5588  double y, z;
5589  unsigned short icl, jpl, jcl, kpl, splitPos;
5590  for (ipl = 0; ipl < 3; ++ipl) {
5591  if (hamrVec[ipl].size() == 0) continue;
5592  jpl = (ipl + 1) % nPln;
5593  kpl = (jpl + 1) % nPln;
5594  for (unsigned short ii = 0; ii < hamrVec[ipl].size(); ++ii) {
5595  if (hamrVec[ipl][ii].Used) continue;
5596  for (unsigned short jj = 0; jj < hamrVec[jpl].size(); ++jj) {
5597  if (hamrVec[jpl][jj].Used) continue;
5598  dX = hamrVec[ipl][ii].X - hamrVec[jpl][jj].X;
5599  if (fabs(dX) > fVertex3DCut) continue;
5600  geo::PlaneID const plane_i{tpcid, ipl};
5601  geo::PlaneID const plane_j{tpcid, jpl};
5602  geom->IntersectionPoint(geo::WireID{plane_i, hamrVec[ipl][ii].Wire},
5603  geo::WireID{plane_j, hamrVec[jpl][jj].Wire},
5604  y,
5605  z);
5606  if (y < YLo || y > YHi || z < ZLo || z > ZHi) continue;
5607  // mark them used
5608  hamrVec[ipl][ii].Used = true;
5609  hamrVec[jpl][jj].Used = true;
5610  // make a new 3D vertex
5611  Vtx3Store newVtx3;
5612  newVtx3.ProcCode = 7;
5613  newVtx3.X = 0.5 * (hamrVec[ipl][ii].X + hamrVec[jpl][jj].X);
5614  // TODO: do this correctly;
5615  newVtx3.XErr = fabs(hamrVec[ipl][ii].X - hamrVec[jpl][jj].X);
5616  newVtx3.Y = y;
5617  newVtx3.YErr = 1; // TODO
5618  newVtx3.Z = z;
5619  newVtx3.ZErr = 1; // TODO
5620  newVtx3.CStat = cstat;
5621  newVtx3.TPC = tpc;
5622 
5623  // make 2D vertex in ipl
5624  VtxStore newVtx2;
5625  newVtx2.Wire = hamrVec[ipl][ii].Wire;
5626  newVtx2.WireErr = 2;
5627  newVtx2.Time = hamrVec[ipl][ii].Tick;
5628  newVtx2.TimeErr = 5;
5629  newVtx2.Topo = 6;
5630  newVtx2.Fixed = false;
5631  icl = hamrVec[ipl][ii].longClIndex;
5632  newVtx2.CTP = tcl[icl].CTP;
5633  vtx.push_back(newVtx2);
5634  unsigned short ivnew = vtx.size() - 1;
5635  // associate the new vertex with the long cluster
5636  tcl[icl].EndVtx = ivnew;
5637  FitVtx(ivnew);
5638  // stash the index in newVtx3
5639  newVtx3.Ptr2D[ipl] = (short)ivnew;
5640  // split the short cluster and associate the new clusters with the new vtx
5641  icl = hamrVec[ipl][ii].shortClIndex;
5642  splitPos = hamrVec[ipl][ii].splitPos;
5643  if (!SplitCluster(icl, splitPos, ivnew)) return;
5644 
5645  // make 2D vertex in jpl
5646  newVtx2.Wire = hamrVec[jpl][jj].Wire;
5647  newVtx2.Time = hamrVec[jpl][jj].Tick;
5648  newVtx2.Topo = 6;
5649  jcl = hamrVec[jpl][jj].longClIndex;
5650  newVtx2.CTP = tcl[jcl].CTP;
5651  vtx.push_back(newVtx2);
5652  ivnew = vtx.size() - 1;
5653  // associate the new vertex with the long cluster
5654  tcl[jcl].EndVtx = ivnew;
5655  // stash the index in newVtx3
5656  newVtx3.Ptr2D[jpl] = (short)(vtx.size() - 1);
5657  // split the short cluster and associate the new clusters with the new
5658  // vtx
5659  jcl = hamrVec[jpl][jj].shortClIndex;
5660  splitPos = hamrVec[jpl][jj].splitPos;
5661  if (!SplitCluster(jcl, splitPos, vtx.size() - 1)) return;
5662  FitVtx(ivnew);
5663  // set the kpl 2D vertex index < 0. Let follow-on code find the 3rd
5664  // plane vertex
5665  newVtx3.Ptr2D[kpl] = -1;
5666  geo::Point_t const WPos{0, y, z};
5667  try {
5668  newVtx3.Wire = geom->NearestWireID(WPos, geo::PlaneID{cstat, tpc, kpl}).Wire;
5669  }
5670  catch (geo::InvalidWireError const& e) {
5671  newVtx3.Wire = e.suggestedWireID().Wire; // pick the closest valid wire
5672  }
5673  vtx3.push_back(newVtx3);
5674  } // jj
5675  } // ii
5676  }
5677 
5678  } // FindHammerClusters
5679 
5681  void ClusterCrawlerAlg::VtxMatch(detinfo::DetectorPropertiesData const& det_prop,
5682  geo::TPCID const& tpcid)
5683  {
5684  // Create 3D vertices from 2D vertices. 3D vertices that are matched
5685  // in all three planes have Ptr2D >= 0 for all planes
5686 
5687  geo::TPCGeo const& TPC = geom->TPC(tpcid);
5688 
5689  // Y,Z limits of the detector
5690  auto const world = TPC.GetCenter();
5691 
5692  // reduce the active area of the TPC by 1 cm to prevent wire boundary issues
5693  float YLo = world.Y() - TPC.HalfHeight() + 1;
5694  float YHi = world.Y() + TPC.HalfHeight() - 1;
5695  float ZLo = world.Z() - TPC.Length() / 2 + 1;
5696  float ZHi = world.Z() + TPC.Length() / 2 - 1;
5697 
5698  vtxprt = (fDebugPlane >= 0) && (fDebugHit == 6666);
5699 
5700  if (vtxprt) {
5701  mf::LogVerbatim("CC") << "Inside VtxMatch";
5702  PrintVertices();
5703  }
5704 
5705  // wire spacing in cm
5706  float wirePitch = geom->WirePitch(geo::PlaneID{tpcid, 0});
5707 
5708  // fill temp vectors of 2D vertex X and X errors
5709  std::vector<float> vX(vtx.size());
5710  std::vector<float> vXsigma(vtx.size());
5711  float vXp;
5712  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
5713  if (vtx[ivx].NClusters == 0) continue;
5714  geo::PlaneID iplID = DecodeCTP(vtx[ivx].CTP);
5715  if (iplID.TPC != tpc || iplID.Cryostat != cstat) continue;
5716  // Convert 2D vertex time error to X error
5717  vX[ivx] =
5718  det_prop.ConvertTicksToX((double)vtx[ivx].Time, (int)iplID.Plane, (int)tpc, (int)cstat);
5719  vXp = det_prop.ConvertTicksToX(
5720  (double)(vtx[ivx].Time + vtx[ivx].TimeErr), (int)iplID.Plane, (int)tpc, (int)cstat);
5721  vXsigma[ivx] = fabs(vXp - vX[ivx]);
5722  } // ivx
5723 
5724  // create a array/vector of 2D vertex indices in each plane
5725  std::array<std::vector<unsigned short>, 3> vIndex;
5726  unsigned short indx, ipl;
5727  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
5728  if (vtx[ivx].NClusters == 0) continue;
5729  geo::PlaneID iplID = DecodeCTP(vtx[ivx].CTP);
5730  if (iplID.TPC != tpc || iplID.Cryostat != cstat) continue;
5731  ipl = iplID.Plane;
5732  if (ipl > 2) continue;
5733  indx = vIndex[ipl].size();
5734  vIndex[ipl].resize(indx + 1);
5735  vIndex[ipl][indx] = ivx;
5736  }
5737 
5738  // vector of 2D vertices -> 3D vertices.
5739  std::vector<short> vPtr;
5740  for (unsigned short ii = 0; ii < vtx.size(); ++ii)
5741  vPtr.push_back(-1);
5742 
5743  // temp vector of all 2D vertex matches
5744  std::vector<Vtx3Store> v3temp;
5745 
5746  double y = 0, z = 0;
5747  geo::Point_t WPos{0, 0, 0};
5748  // i, j, k indicates 3 different wire planes
5749  unsigned short ii, jpl, jj, kpl, kk, ivx, jvx, kvx;
5750  unsigned int iWire, jWire;
5751  unsigned short v3dBest = 0;
5752  float xbest = 0, ybest = 0, zbest = 0;
5753  float kX, kWire;
5754  // compare vertices in each view
5755  bool gotit = false;
5756  for (ipl = 0; ipl < 2; ++ipl) {
5757  geo::PlaneID const plane_i{tpcid, ipl};
5758  for (ii = 0; ii < vIndex[ipl].size(); ++ii) {
5759  ivx = vIndex[ipl][ii];
5760  if (ivx > vtx.size() - 1) {
5761  mf::LogError("CC") << "VtxMatch: bad ivx " << ivx;
5762  return;
5763  }
5764  // vertex has been matched already
5765  if (vPtr[ivx] >= 0) continue;
5766  iWire = vtx[ivx].Wire;
5767  float best = fVertex3DCut;
5768  // temp array of 2D vertex indices in each plane
5769  // BUG the double brace syntax is required to work around clang bug 21629
5770  // (https://bugs.llvm.org/show_bug.cgi?id=21629)
5771  std::array<short, 3> t2dIndex = {{-1, -1, -1}};
5772  std::array<short, 3> tmpIndex = {{-1, -1, -1}};
5773  for (jpl = ipl + 1; jpl < 3; ++jpl) {
5774  geo::PlaneID const plane_j{tpcid, jpl};
5775  for (jj = 0; jj < vIndex[jpl].size(); ++jj) {
5776  jvx = vIndex[jpl][jj];
5777  if (jvx > vtx.size() - 1) {
5778  mf::LogError("CC") << "VtxMatch: bad jvx " << jvx;
5779  return;
5780  }
5781  // vertex has been matched already
5782  if (vPtr[jvx] >= 0) continue;
5783  jWire = vtx[jvx].Wire;
5784  // new stuff
5785  float dX = fabs(vX[ivx] - vX[jvx]);
5786  float dXSigma = sqrt(vXsigma[ivx] * vXsigma[ivx] + vXsigma[jvx] * vXsigma[jvx]);
5787  float dXChi = dX / dXSigma;
5788 
5789  if (vtxprt)
5790  mf::LogVerbatim("CC")
5791  << "VtxMatch: ipl " << ipl << " ivx " << ivx << " ivX " << vX[ivx] << " jpl " << jpl
5792  << " jvx " << jvx << " jvX " << vX[jvx] << " W:T " << (int)vtx[jvx].Wire << ":"
5793  << (int)vtx[jvx].Time << " dXChi " << dXChi << " fVertex3DCut " << fVertex3DCut;
5794 
5795  if (dXChi > fVertex3DCut) continue;
5796  geom->IntersectionPoint(geo::WireID{plane_i, iWire}, geo::WireID{plane_j, jWire}, y, z);
5797  if (y < YLo || y > YHi || z < ZLo || z > ZHi) continue;
5798  WPos.SetY(y);
5799  WPos.SetZ(z);
5800  kpl = 3 - ipl - jpl;
5801  kX = 0.5 * (vX[ivx] + vX[jvx]);
5802  kWire = -1;
5803  if (TPC.Nplanes() > 2) {
5804  try {
5805  kWire = geom->NearestWireID(WPos, geo::PlaneID{cstat, tpc, kpl}).Wire;
5806  }
5807  catch (geo::InvalidWireError const& e) {
5808  kWire = e.suggestedWireID().Wire; // pick the closest valid wire
5809  }
5810  }
5811  kpl = 3 - ipl - jpl;
5812  // save this incomplete 3D vertex
5813  Vtx3Store v3d;
5814  v3d.ProcCode = 1;
5815  tmpIndex[ipl] = ivx;
5816  tmpIndex[jpl] = jvx;
5817  tmpIndex[kpl] = -1;
5818  v3d.Ptr2D = tmpIndex;
5819  v3d.X = kX;
5820  v3d.XErr = dXSigma;
5821  v3d.Y = y;
5822  float yzSigma = wirePitch * sqrt(vtx[ivx].WireErr * vtx[ivx].WireErr +
5823  vtx[jvx].WireErr * vtx[jvx].WireErr);
5824  v3d.YErr = yzSigma;
5825  v3d.Z = z;
5826  v3d.ZErr = yzSigma;
5827  v3d.Wire = kWire;
5828  v3d.CStat = cstat;
5829  v3d.TPC = tpc;
5830  v3temp.push_back(v3d);
5831 
5832  if (vtxprt)
5833  mf::LogVerbatim("CC")
5834  << "VtxMatch: 2 Plane match ivx " << ivx << " P:W:T " << ipl << ":"
5835  << (int)vtx[ivx].Wire << ":" << (int)vtx[ivx].Time << " jvx " << jvx << " P:W:T "
5836  << jpl << ":" << (int)vtx[jvx].Wire << ":" << (int)vtx[jvx].Time << " dXChi "
5837  << dXChi << " yzSigma " << yzSigma;
5838 
5839  if (TPC.Nplanes() == 2) continue;
5840  // look for a 3 plane match
5841  best = fVertex3DCut;
5842  for (kk = 0; kk < vIndex[kpl].size(); ++kk) {
5843  kvx = vIndex[kpl][kk];
5844  if (vPtr[kvx] >= 0) continue;
5845  // Wire difference error
5846  float dW = wirePitch * (vtx[kvx].Wire - kWire) / yzSigma;
5847  // X difference error
5848  float dX = (vX[kvx] - kX) / dXSigma;
5849  float kChi = 0.5 * sqrt(dW * dW + dX * dX);
5850  if (kChi < best) {
5851  best = kChi;
5852  xbest = (vX[kvx] + 2 * kX) / 3;
5853  ybest = y;
5854  zbest = z;
5855  t2dIndex[ipl] = ivx;
5856  t2dIndex[jpl] = jvx;
5857  t2dIndex[kpl] = kvx;
5858  v3dBest = v3temp.size() - 1;
5859  }
5860 
5861  if (vtxprt)
5862  mf::LogVerbatim("CC")
5863  << " kvx " << kvx << " kpl " << kpl << " wire " << (int)vtx[kvx].Wire << " kTime "
5864  << (int)vtx[kvx].Time << " kChi " << kChi << " best " << best << " dW "
5865  << vtx[kvx].Wire - kWire;
5866 
5867  } // kk
5868  if (vtxprt)
5869  mf::LogVerbatim("CC") << " done best = " << best << " fVertex3DCut " << fVertex3DCut;
5870  if (TPC.Nplanes() > 2 && best < fVertex3DCut) {
5871  // create a real 3D vertex using the previously entered incomplete 3D vertex as a template
5872  if (v3dBest > v3temp.size() - 1) {
5873  mf::LogError("CC") << "VtxMatch: bad v3dBest " << v3dBest;
5874  return;
5875  }
5876  Vtx3Store v3d = v3temp[v3dBest];
5877  v3d.Ptr2D = t2dIndex;
5878  v3d.Wire = -1;
5879  // TODO need to average ybest and zbest here with error weighting
5880  v3d.X = xbest;
5881  v3d.Y = ybest;
5882  v3d.Z = zbest;
5883  vtx3.push_back(v3d);
5884  gotit = true;
5885  // mark the 2D vertices as used
5886  for (unsigned short jj = 0; jj < 3; ++jj)
5887  if (t2dIndex[jj] >= 0) vPtr[t2dIndex[jj]] = vtx3.size() - 1;
5888 
5889  if (vtxprt)
5890  mf::LogVerbatim("CC")
5891  << "New 3D vtx " << vtx3.size() << " X " << v3d.X << " Y " << v3d.Y << " Z "
5892  << v3d.Z << " t2dIndex " << t2dIndex[0] << " " << t2dIndex[1] << " "
5893  << t2dIndex[2] << " best Chi " << best;
5894 
5895  } // best < dRCut
5896  if (gotit) break;
5897  } // jj
5898  if (gotit) break;
5899  } // jpl
5900  if (gotit) break;
5901  } // ii
5902  } // ipl
5903 
5904  // Store incomplete 3D vertices but ignore those that are part of a complete 3D vertex
5905  unsigned short vsize = vtx3.size();
5906  for (unsigned short it = 0; it < v3temp.size(); ++it) {
5907  bool keepit = true;
5908  for (unsigned short i3d = 0; i3d < vsize; ++i3d) {
5909  for (unsigned short plane = 0; plane < 3; ++plane) {
5910  if (v3temp[it].Ptr2D[plane] == vtx3[i3d].Ptr2D[plane]) {
5911  keepit = false;
5912  break;
5913  }
5914  } // plane
5915  if (!keepit) break;
5916  } // i3d
5917 
5918  if (keepit) vtx3.push_back(v3temp[it]);
5919  } // it
5920 
5921  // Modify Ptr2D for 2-plane detector
5922  if (TPC.Nplanes() == 2) {
5923  for (unsigned short iv3 = 0; iv3 < vtx3.size(); ++iv3) {
5924  vtx3[iv3].Ptr2D[2] = 666;
5925  } //iv3
5926  } // 2 planes
5927 
5928  if (vtxprt) {
5929  for (unsigned short it = 0; it < vtx3.size(); ++it) {
5930  mf::LogVerbatim("CC") << "vtx3 " << it << " Ptr2D " << vtx3[it].Ptr2D[0] << " "
5931  << vtx3[it].Ptr2D[1] << " " << vtx3[it].Ptr2D[2] << " wire "
5932  << vtx3[it].Wire;
5933  }
5934  }
5935 
5936  } // VtxMatch
5937 
5939  void ClusterCrawlerAlg::GetHitRange(CTP_t CTP)
5940  {
5941  // fills the WireHitRange vector for the supplied Cryostat/TPC/Plane code
5942  // Hits must have been sorted by increasing wire number
5943  fFirstHit = 0;
5944  geo::PlaneID planeID = DecodeCTP(CTP);
5945  unsigned int nwires = geom->Nwires(planeID);
5946  WireHitRange.resize(nwires + 1);
5947 
5948  // These will be re-defined later
5949  fFirstWire = 0;
5950  fLastWire = 0;
5951 
5952  unsigned int wire, iht;
5953  unsigned int nHitInPlane;
5954  std::pair<int, int> flag;
5955 
5956  // Define the "no hits on wire" condition
5957  flag.first = -2;
5958  flag.second = -2;
5959  for (auto& apair : WireHitRange)
5960  apair = flag;
5961 
5962  nHitInPlane = 0;
5963 
5964  std::vector<bool> firsthit;
5965  firsthit.resize(nwires + 1, true);
5966  bool firstwire = true;
5967  for (iht = 0; iht < fHits.size(); ++iht) {
5968  if (fHits[iht].WireID().asPlaneID() != planeID) continue;
5969  wire = fHits[iht].WireID().Wire;
5970  // define the first hit start index in this TPC, Plane
5971  if (firsthit[wire]) {
5972  WireHitRange[wire].first = iht;
5973  firsthit[wire] = false;
5974  }
5975  if (firstwire) {
5976  fFirstWire = wire;
5977  firstwire = false;
5978  }
5979  WireHitRange[wire].second = iht + 1;
5980  fLastWire = wire + 1;
5981  ++nHitInPlane;
5982  }
5983  // overwrite with the "dead wires" condition
5984  lariov::ChannelStatusProvider const& channelStatus =
5986 
5987  flag.first = -1;
5988  flag.second = -1;
5989  unsigned int nbad = 0;
5990  for (wire = 0; wire < nwires; ++wire) {
5991  raw::ChannelID_t chan = geom->PlaneWireToChannel(geo::WireID(planeID, wire));
5992  if (!channelStatus.IsGood(chan)) {
5993  WireHitRange[wire] = flag;
5994  ++nbad;
5995  }
5996  } // wire
5997  // define the MergeAvailable vector and check for errors
5998  if (mergeAvailable.size() < fHits.size())
6000  << "GetHitRange: Invalid mergeAvailable vector size " << mergeAvailable.size()
6001  << fHits.size();
6002  unsigned int firstHit, lastHit;
6003  unsigned int cnt;
6004  cnt = 0;
6005  float maxRMS, chiSep, peakCut;
6006  for (wire = 0; wire < nwires; ++wire) {
6007  // ignore dead wires and wires with no hits
6008  if (WireHitRange[wire].first < 0) continue;
6009  firstHit = WireHitRange[wire].first;
6010  lastHit = WireHitRange[wire].second;
6011  for (iht = firstHit; iht < lastHit; ++iht) {
6012  if (fHits[iht].WireID().Wire != wire)
6014  << "Bad WireHitRange on wire " << wire << "\n";
6015  ++cnt;
6016  if (fHits[iht].Multiplicity() > 1) {
6017  peakCut = 0.6 * fHits[iht].PeakAmplitude();
6018  std::pair<size_t, size_t> MultipletRange = FindHitMultiplet(iht);
6019  for (size_t jht = MultipletRange.first; jht < MultipletRange.second; ++jht) {
6020  if (jht == iht) continue;
6021  // require that the j hit be similar in magnitude to the i hit
6022  if (fHits[jht].PeakAmplitude() < peakCut) continue;
6023  maxRMS = std::max(fHits[iht].RMS(), fHits[jht].RMS());
6024  chiSep = std::abs(fHits[iht].PeakTime() - fHits[jht].PeakTime()) / maxRMS;
6025  if (chiSep < fHitMergeChiCut) {
6026  mergeAvailable[iht] = true;
6027  break;
6028  }
6029  } // jht
6030  } // fHits[iht].Multiplicity() > 1
6031  } // iht
6032  } // wire
6033  if (cnt != nHitInPlane)
6034  mf::LogWarning("CC") << "Bad WireHitRange count " << cnt << " " << nHitInPlane << "\n";
6035 
6036  if (!fMergeAllHits) return;
6037 
6038  // Merge all of the hits
6039  bool didMerge;
6040  for (wire = 0; wire < nwires; ++wire) {
6041  if (WireHitRange[wire].first < 0) continue;
6042  firstHit = WireHitRange[wire].first;
6043  lastHit = WireHitRange[wire].second;
6044  for (iht = firstHit; iht < lastHit; ++iht) {
6045  if (!mergeAvailable[iht]) continue;
6046  // already merged?
6047  if (fHits[iht].GoodnessOfFit() == 6666) continue;
6048  MergeHits(iht, didMerge);
6049  mergeAvailable[iht] = false;
6050  } // iht
6051  } // wire
6052 
6053  } // GetHitRange()
6054 
6056  unsigned int ClusterCrawlerAlg::DeadWireCount(unsigned int inWire1, unsigned int inWire2)
6057  {
6058  if (inWire1 > inWire2) {
6059  // put in increasing order
6060  unsigned int tmp = inWire1;
6061  inWire1 = inWire2;
6062  inWire2 = tmp;
6063  } // inWire1 > inWire2
6064  ++inWire2;
6065  unsigned int wire, ndead = 0;
6066  for (wire = inWire1; wire < inWire2; ++wire)
6067  if (WireHitRange[wire].first == -1) ++ndead;
6068  return ndead;
6069  } // DeadWireCount
6070 
6073  {
6074  // Counts the number of dead wires in the range spanned by fcl2hits
6075  if (fcl2hits.size() < 2) return 0;
6076  unsigned int wire, ndead = 0;
6077  unsigned int iht = fcl2hits[fcl2hits.size() - 1];
6078  unsigned int eWire = fHits[iht].WireID().Wire;
6079  iht = fcl2hits[0];
6080  unsigned int bWire = fHits[iht].WireID().Wire + 1;
6081  for (wire = eWire; wire < bWire; ++wire)
6082  if (WireHitRange[wire].first == -1) ++ndead;
6083  return ndead;
6084  } // DeadWireCount
6085 
6087  bool ClusterCrawlerAlg::areInSameMultiplet(recob::Hit const& first_hit,
6088  recob::Hit const& second_hit)
6089  {
6090  return (first_hit.StartTick() == second_hit.StartTick()) &&
6091  (first_hit.WireID() == second_hit.WireID());
6092  } // ClusterCrawlerAlg::areInSameMultiplet()
6093 
6095  std::pair<size_t, size_t> ClusterCrawlerAlg::FindHitMultiplet(size_t iHit) const
6096  {
6097  std::pair<size_t, size_t> range{iHit, iHit};
6098 
6099  range.first = iHit - fHits[iHit].LocalIndex();
6100  range.second = range.first + fHits[iHit].Multiplicity();
6101 
6102  return range;
6103  } // ClusterCrawlerAlg::FindHitMultiplet()
6104 
6106  bool ClusterCrawlerAlg::CheckHitDuplicates(std::string location,
6107  std::string marker /* = "" */) const
6108  {
6109  // currently unused, only for debug
6110  unsigned int nDuplicates = 0;
6111  std::set<unsigned int> hits;
6112  for (unsigned int hit : fcl2hits) {
6113  if (hits.count(hit)) {
6114  ++nDuplicates;
6115  mf::LogProblem log("CC");
6116  log << "Hit #" << hit
6117  << " being included twice in the future cluster (ID=" << (tcl.size() + 1)
6118  << "?) at location: " << location;
6119  if (!marker.empty()) log << " (marker: '" << marker << "')";
6120  }
6121  hits.insert(hit);
6122  } // for
6123  return nDuplicates > 0;
6124  } // ClusterCrawlerAlg::CheckHitDuplicates()
6125 
6127  float ClusterCrawlerAlg::DoCA(short icl, unsigned short end, float vwire, float vtick)
6128  {
6129  // Find the Distance of Closest Approach betweeen a cluster and a point (vwire, vtick). The
6130  // DoCA is returned in Tick units.
6131 
6132  if (icl > (short)tcl.size()) return 9999;
6133 
6134  float cwire, cslp, ctick;
6135  // figure out which cluster to use
6136  if (icl < 0) {
6137  if (fcl2hits.size() == 0) return 9999;
6138  // cluster under construction
6139  if (end == 0) {
6140  cwire = clBeginWir;
6141  cslp = clBeginSlp;
6142  ctick = clBeginTim;
6143  }
6144  else {
6145  cwire = clpar[2];
6146  cslp = clpar[1];
6147  ctick = clpar[0];
6148  } // end
6149  }
6150  else {
6151  // tcl cluster
6152  if (end == 0) {
6153  cwire = tcl[icl].BeginWir;
6154  cslp = tcl[icl].BeginSlp;
6155  ctick = tcl[icl].BeginTim;
6156  }
6157  else {
6158  cwire = tcl[icl].EndWir;
6159  cslp = tcl[icl].EndSlp;
6160  ctick = tcl[icl].EndTim;
6161  } // end
6162  }
6163 
6164  // Closest approach wire
6165  float docaW = (vwire + cslp * (vtick - ctick) + cwire * cslp * cslp) / (1 + cslp * cslp);
6166  float dW = docaW - vwire;
6167  float dT = ctick + (vwire - cwire) * cslp - vtick;
6168  return sqrt(dW * dW + dT * dT);
6169 
6170  } // DoCA
6171 
6173  float ClusterCrawlerAlg::ClusterVertexChi(short icl, unsigned short end, unsigned short ivx)
6174  {
6175  // Returns the chisq/DOF between a cluster and a vertex
6176 
6177  if (icl > (short)tcl.size()) return 9999;
6178  if (ivx > vtx.size()) return 9999;
6179 
6180  float cwire, cslp, cslpErr, ctick;
6181  // figure out which cluster to use
6182  if (icl < 0) {
6183  if (fcl2hits.size() == 0) return 9999;
6184  // cluster under construction
6185  if (end == 0) {
6186  cwire = clBeginWir;
6187  cslp = clBeginSlp;
6188  cslpErr = clBeginSlpErr;
6189  ctick = clBeginTim;
6190  }
6191  else {
6192  cwire = clpar[2];
6193  cslp = clpar[1];
6194  cslpErr = clparerr[1];
6195  ctick = clpar[0];
6196  } // end
6197  }
6198  else {
6199  // tcl cluster
6200  if (end == 0) {
6201  cwire = tcl[icl].BeginWir;
6202  cslp = tcl[icl].BeginSlp;
6203  cslpErr = tcl[icl].BeginSlpErr;
6204  ctick = tcl[icl].BeginTim;
6205  }
6206  else {
6207  cwire = tcl[icl].EndWir;
6208  cslp = tcl[icl].EndSlp;
6209  cslpErr = tcl[icl].EndSlpErr;
6210  ctick = tcl[icl].EndTim;
6211  } // end
6212  }
6213 
6214  // Closest approach wire
6215  float docaW =
6216  (vtx[ivx].Wire + cslp * (vtx[ivx].Time - ctick) + cwire * cslp * cslp) / (1 + cslp * cslp);
6217  float dW = docaW - vtx[ivx].Wire;
6218  float chi = dW / vtx[ivx].WireErr;
6219  float totChi = chi * chi;
6220  dW = vtx[ivx].Wire - cwire;
6221  float dT = ctick + dW * cslp - vtx[ivx].Time;
6222  if (cslpErr < 1E-3) cslpErr = 1E-3;
6223  // increase slope error for large angle clusters
6224  cslpErr *= AngleFactor(cslp);
6225  // cluster slope projection error
6226  float dTErr = dW * cslpErr;
6227  // squared
6228  dTErr *= dTErr;
6229  // add the vertex time error^2 to the cluster projection error^2
6230  dTErr += vtx[ivx].TimeErr * vtx[ivx].TimeErr;
6231  if (dTErr < 1E-3) dTErr = 1E-3;
6232  totChi += dT * dT / dTErr;
6233  totChi /= 2;
6234 
6235  return totChi;
6236 
6237  } // ClusterVertexChi
6238 
6240  float ClusterCrawlerAlg::PointVertexChi(float wire, float tick, unsigned short ivx)
6241  {
6242  // Returns the Chisq/DOF between a (Wire, Tick) point and a vertex
6243 
6244  if (ivx > vtx.size()) return 9999;
6245 
6246  float dW = wire - vtx[ivx].Wire;
6247  float chi = dW / vtx[ivx].WireErr;
6248  float totChi = chi * chi;
6249  float dT = tick - vtx[ivx].Time;
6250  chi = dT / vtx[ivx].TimeErr;
6251  totChi += chi * chi;
6252 
6253  return totChi;
6254 
6255  } // PointVertexChi
6256 
6258  std::string ClusterCrawlerAlg::PrintHit(unsigned int iht)
6259  {
6260 
6261  if (iht > fHits.size() - 1) return "Bad Hit";
6262  return std::to_string(fHits[iht].WireID().Plane) + ":" +
6263  std::to_string(fHits[iht].WireID().Wire) + ":" +
6264  std::to_string((int)fHits[iht].PeakTime());
6265 
6266  } // PrintHit
6267 
6268 } // namespace cluster
Float_t x
Definition: compare.C:6
short int LocalIndex() const
How well do we believe we know this hit?
Definition: Hit.h:266
MaybeLogger_< ELseverityLevel::ELsev_info, true > LogVerbatim
Functions to help with numbers.
geo::SigType_t SignalType() const
Signal type for the plane of the hit.
Definition: Hit.h:282
constexpr auto const & right(const_AssnsIter< L, R, D, Dir > const &a, const_AssnsIter< L, R, D, Dir > const &b)
Definition: AssnsIter.h:102
Utilities related to art service access.
static unsigned int kWire
Encapsulate the construction of a single cyostat.
struct of temporary 2D vertices (end points)
unsigned int Nplanes() const
Number of planes in this tpc.
Definition: TPCGeo.h:137
float RMS() const
RMS of the hit shape, in tick units.
Definition: Hit.h:234
Declaration of signal hit object.
Float_t y
Definition: compare.C:6
void PrintClusters()
Double_t z
Definition: plot.C:276
Planes which measure X direction.
Definition: geo_types.h:140
The data type to uniquely identify a Plane.
Definition: geo_types.h:463
Geometry information for a single TPC.
Definition: TPCGeo.h:36
double Temperature() const
In kelvin.
float SigmaPeakAmplitude() const
Uncertainty on estimated amplitude of the hit at its peak, in ADC units.
Definition: Hit.h:242
float SigmaIntegral() const
Initial tdc tick for hit.
Definition: Hit.h:258
constexpr auto abs(T v)
Returns the absolute value of the argument.
int DegreesOfFreedom() const
Initial tdc tick for hit.
Definition: Hit.h:274
CryostatID_t Cryostat
Index of cryostat.
Definition: geo_types.h:211
float Integral() const
Integral under the calibrated signal waveform of the hit, in tick x ADC units.
Definition: Hit.h:254
geo::View_t View() const
View for the plane of the hit.
Definition: Hit.h:286
WireID_t Wire
Index of the wire within its plane.
Definition: geo_types.h:563
geo::WireID const & WireID() const
Initial tdc tick for hit.
Definition: Hit.h:290
Float_t tmp
Definition: plot.C:35
MaybeLogger_< ELseverityLevel::ELsev_error, false > LogError
Cluster finding and building.
float GoodnessOfFit() const
Degrees of freedom in the determination of the hit signal shape (-1 by default)
Definition: Hit.h:270
short int Multiplicity() const
How many hits could this one be shared with.
Definition: Hit.h:262
struct of temporary 3D vertices
float PeakAmplitude() const
The estimated amplitude of the hit at its peak, in ADC units.
Definition: Hit.h:238
double Length() const
Length is associated with z coordinate [cm].
Definition: TPCGeo.h:104
constexpr int cmp(WireID const &other) const
Returns < 0 if this is smaller than tpcid, 0 if equal, > 0 if larger.
Definition: geo_types.h:609
double Efield(unsigned int planegap=0) const
kV/cm
int TDCtick_t
Type representing a TDC tick.
Definition: RawTypes.h:25
decltype(auto) constexpr end(T &&obj)
ADL-aware version of std::end.
Definition: StdUtils.h:77
decltype(auto) constexpr size(T &&obj)
ADL-aware version of std::size.
Definition: StdUtils.h:101
Access the description of detector geometry.
Point_t GetCenter() const
Returns the center of the TPC volume in world coordinates [cm].
Definition: TPCGeo.h:247
Collection of exceptions for Geometry system.
void hits()
Definition: readHits.C:15
TCanvas * c1
Definition: plotHisto.C:7
TCanvas * c2
Definition: plot_hist.C:75
IDparameter< geo::WireID > WireID
Member type of validated geo::WireID parameter.
float DeadWireCount(const TCSlice &slc, const TrajPoint &tp1, const TrajPoint &tp2)
Definition: Utils.cxx:2094
decltype(auto) constexpr to_string(T &&obj)
ADL-aware version of std::to_string.
Float_t E
Definition: plot.C:20
T get(std::string const &key) const
Definition: ParameterSet.h:314
tick_as<> tick
Tick number, represented by std::ptrdiff_t.
Definition: electronics.h:73
double ConvertXToTicks(double X, int p, int t, int c) const
void MergeOverlap(std::string inFcnLabel, TCSlice &slc, const CTP_t &inCTP, bool prt)
Definition: TCShower.cxx:2371
std::string PrintHit(const TCHit &tch)
Definition: Utils.cxx:6344
bool SortByLowHit(unsigned int i, unsigned int j)
bool has_key(std::string const &key) const
double DriftVelocity(double efield=0., double temperature=0.) const
cm/us
The data type to uniquely identify a TPC.
Definition: geo_types.h:381
PlaneID_t Plane
Index of the plane within its TPC.
Definition: geo_types.h:481
float bin[41]
Definition: plottest35.C:14
raw::TDCtick_t StartTick() const
Initial tdc tick for hit.
Definition: Hit.h:218
float PeakTimeMinusRMS(float sigmas=+1.) const
Returns a time sigmas RMS away from the peak time.
Definition: Hit.h:300
float HitSummedADC() const
The sum of calibrated ADC counts of the hit (0. by default)
Definition: Hit.h:250
Detector simulation of raw signals on wires.
double ConvertTicksToX(double ticks, int p, int t, int c) const
raw::TDCtick_t EndTick() const
Final tdc tick for hit.
Definition: Hit.h:222
double HalfHeight() const
Height is associated with y coordinate [cm].
Definition: TPCGeo.h:100
bool greaterThan(CluLen c1, CluLen c2)
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
float ROISummedADC() const
The sum of calibrated ADC counts of the ROI (0. by default)
Definition: Hit.h:246
ROOT::Math::PositionVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Point_t
Type for representation of position in physical 3D space.
Definition: geo_vectors.h:180
float PeakTime() const
Time of the signal peak, in tick units.
Definition: Hit.h:226
Contains all timing reference information for the detector.
geo::PlaneID DecodeCTP(CTP_t CTP)
constexpr auto absDiff(A const &a, B const &b)
Returns the absolute value of the difference between two values.
Definition: NumericUtils.h:69
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
CTP_t EncodeCTP(unsigned int cryo, unsigned int tpc, unsigned int plane)
Definition: DataStructs.h:51
span(IterB &&b, IterE &&e, Adaptor &&adaptor) -> span< decltype(adaptor(std::forward< IterB >(b))), decltype(adaptor(std::forward< IterE >(e))) >
Exception thrown on invalid wire number.
Definition: Exceptions.h:39
decltype(auto) constexpr begin(T &&obj)
ADL-aware version of std::begin.
Definition: StdUtils.h:69
float SigmaPeakTime() const
Uncertainty for the signal peak, in tick units.
Definition: Hit.h:230
2D representation of charge deposited in the TDC/wire plane
Definition: Hit.h:46
float PeakTimePlusRMS(float sigmas=+1.) const
Returns a time sigmas RMS away from the peak time.
Definition: Hit.h:295
unsigned int ChannelID_t
Type representing the ID of a readout channel.
Definition: RawTypes.h:28
TPCID_t TPC
Index of the TPC within its cryostat.
Definition: geo_types.h:399
Float_t e
Definition: plot.C:35
second_as<> second
Type of time stored in seconds, in double precision.
Definition: spacetime.h:82
geo::WireID suggestedWireID() const
Returns a better wire ID.
Definition: Exceptions.h:87
double sampling_rate(DetectorClocksData const &data)
Returns the period of the TPC readout electronics clock.
recob::tracking::Plane Plane
Definition: TrackState.h:17
Float_t X
Definition: plot.C:37
raw::ChannelID_t Channel() const
ID of the readout channel the hit was extracted from.
Definition: Hit.h:278
art framework interface to geometry description
decltype(auto) constexpr empty(T &&obj)
ADL-aware version of std::empty.
Definition: StdUtils.h:109
Encapsulate the construction of a single detector plane.