LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
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.SummedADC(),
1693  chgsum, // hit_integral
1694  hit.SigmaIntegral(),
1695  NewMultiplicity, // multiplicity
1696  0, // local index
1697  6666, // GoodnessOfFit (flag for merged hit)
1698  hit.DegreesOfFreedom(),
1699  hit.View(),
1700  hit.SignalType(),
1701  hit.WireID());
1702  FixMultipletLocalIndices(MultipletRange.first, MultipletRange.second);
1703  didMerge = true;
1704 
1705  } // MergeHits()
1706 
1708  void ClusterCrawlerAlg::FixMultipletLocalIndices(size_t begin,
1709  size_t end,
1710  short int multiplicity /* = -1 */)
1711  {
1712  //
1713  // Resets multiplicity and local index of the hits in the range.
1714  // All hits are assumed to be in the same multiplet.
1715  // All hits that are not obsolete are given a multiplicity equal to the
1716  // number of non-obsolete hits in the multiplet, and the local index is
1717  // assigned as an increasing number starting from 0 with the first
1718  // non-obsolete hit on.
1719  //
1720 
1721  // first pass: determine the actual number of hits in the multiplet
1722  if (multiplicity < 0) {
1723  multiplicity = 0;
1724  for (size_t iHit = begin; iHit < end; ++iHit) {
1725  if (inClus[iHit] < 0) continue;
1726  ++multiplicity;
1727  } // for
1728  } // if no valid multiplicity is given
1729 
1730  // second pass: assign the correct multiplicity
1731  short int local_index = 0;
1732  for (size_t iHit = begin; iHit < end; ++iHit) {
1733  if (inClus[iHit] < 0) continue;
1734 
1735  // copy everything but overwrite the local index and multiplicity
1736  // TODO use a write wrapper!
1737  recob::Hit const& hit = fHits[iHit];
1738  fHits[iHit] = recob::Hit(hit.Channel(),
1739  hit.StartTick(),
1740  hit.EndTick(),
1741  hit.PeakTime(),
1742  hit.SigmaPeakTime(),
1743  hit.RMS(),
1744  hit.PeakAmplitude(),
1745  hit.SigmaPeakAmplitude(),
1746  hit.SummedADC(),
1747  hit.Integral(),
1748  hit.SigmaIntegral(),
1749  multiplicity, // multiplicity
1750  local_index, // local index
1751  hit.GoodnessOfFit(),
1752  hit.DegreesOfFreedom(),
1753  hit.View(),
1754  hit.SignalType(),
1755  hit.WireID());
1756 
1757  ++local_index;
1758  } // for
1759 
1760  } // FixMultipletLocalIndices()
1761 
1763  void ClusterCrawlerAlg::FindStarVertices()
1764  {
1765  // Make 2D vertices with a star topology with short back-to-back
1766  // clusters. Vertices must reside on the US end of the longest
1767  // cluster, so vertex finding uses the End information only.
1768  // This routine is called after all passes are completed
1769  // in the current CTP
1770  if (tcl.size() < 2) return;
1771 
1772  // This code has some issues...
1773  return;
1774 
1775  vtxprt = (fDebugPlane == (int)plane && fDebugHit < 0);
1776  if (vtxprt) {
1777  mf::LogVerbatim("CC") << "FindStarVertices";
1778  PrintClusters();
1779  }
1780 
1781  unsigned short vtxSizeIn = vtx.size();
1782 
1783  float fvw = 0.;
1784  float fvt = 0.;
1785  float dsl = 0, dth = 0;
1786  float es1 = 0, es2 = 0;
1787  float eth1 = 0, eth2 = 0;
1788  float bt1 = 0, bt2 = 0;
1789  float et1 = 0, et2 = 0;
1790  float bw1 = 0, bw2 = 0;
1791  float ew1 = 0, ew2 = 0;
1792  float lotime = 0, hitime = 0, nwirecut = 0;
1793  unsigned short tclsize = tcl.size();
1794  for (unsigned short it1 = 0; it1 < tclsize - 1; ++it1) {
1795  // ignore obsolete clusters
1796  if (tcl[it1].ID < 0) continue;
1797  if (tcl[it1].CTP != clCTP) continue;
1798  // long dead-straight cluster?
1799  if (tcl[it1].tclhits.size() > 100) {
1800  dth = tcl[it1].BeginAng - tcl[it1].EndAng;
1801  if (std::abs(dth) < 0.1) continue;
1802  }
1803  es1 = tcl[it1].EndSlp;
1804  eth1 = tcl[it1].EndAng;
1805  ew1 = tcl[it1].EndWir;
1806  et1 = tcl[it1].EndTim;
1807  bw1 = tcl[it1].BeginWir;
1808  bt1 = tcl[it1].BeginTim;
1809  for (unsigned short it2 = it1 + 1; it2 < tclsize; ++it2) {
1810  if (tcl[it2].ID < 0) continue;
1811  if (tcl[it2].CTP != clCTP) continue;
1812  // long dead-straight cluster?
1813  if (tcl[it2].tclhits.size() > 100) {
1814  dth = tcl[it2].BeginAng - tcl[it2].EndAng;
1815  if (std::abs(dth) < 0.05) continue;
1816  }
1817  es2 = tcl[it2].EndSlp;
1818  eth2 = tcl[it2].EndAng;
1819  ew2 = tcl[it2].EndWir;
1820  et2 = tcl[it2].EndTim;
1821  bw2 = tcl[it2].BeginWir;
1822  bt2 = tcl[it2].BeginTim;
1823  // require angle difference
1824  dth = std::abs(eth1 - eth2);
1825  if (dth < 0.3) continue;
1826  dsl = es2 - es1;
1827  fvw = (et1 - ew1 * es1 - et2 + ew2 * es2) / dsl;
1828  // intersection within the wire boundaries
1829  if (fvw < ew1 || fvw > bw1) continue;
1830  if (fvw < ew2 || fvw > bw2) continue;
1831  if (vtxprt)
1832  mf::LogVerbatim("CC") << "Chk clusters " << tcl[it1].ID << " " << tcl[it2].ID
1833  << " topo5 vtx wire " << fvw;
1834  // ensure that the intersection is close to the US end of the longer
1835  // cluster if it is more than 20 hits long
1836  if (tcl[it1].tclhits.size() > tcl[it2].tclhits.size()) {
1837  if (tcl[it1].tclhits.size() > 20) {
1838  nwirecut = 0.5 * tcl[it1].tclhits.size();
1839  if ((fvw - ew1) > nwirecut) continue;
1840  }
1841  }
1842  else {
1843  if (tcl[it2].tclhits.size() > 20) {
1844  nwirecut = 0.5 * tcl[it2].tclhits.size();
1845  if ((fvw - ew2) > nwirecut) continue;
1846  }
1847  }
1848  fvt = et1 + (fvw - ew1) * es1;
1849  // and time boundaries
1850  lotime = 9999;
1851  if (et1 < lotime) lotime = et1;
1852  if (bt1 < lotime) lotime = bt1;
1853  if (et2 < lotime) lotime = et2;
1854  if (bt2 < lotime) lotime = bt2;
1855  hitime = 0;
1856  if (et1 > hitime) hitime = et1;
1857  if (bt1 > hitime) hitime = bt1;
1858  if (et2 > hitime) hitime = et2;
1859  if (bt2 > hitime) hitime = bt2;
1860  if (fvt < lotime || fvt > hitime) continue;
1861  if (vtxprt)
1862  mf::LogVerbatim("CC") << " vertex time " << fvt << " lotime " << lotime << " hitime "
1863  << hitime;
1864  unsigned int vw = (0.5 + fvw);
1865  // ensure that the vertex is near a hit on both clusters
1866  unsigned int pos1 = 0;
1867  for (unsigned short ii = 0; ii < tcl[it1].tclhits.size(); ++ii) {
1868  if (fHits[tcl[it1].tclhits[ii]].WireID().Wire <= vw) {
1869  if (std::abs(int(fHits[tcl[it1].tclhits[ii]].PeakTime() - fvt)) < 10) pos1 = ii;
1870  break;
1871  }
1872  } // ii
1873  // vertex is not near a hit on cluster 1
1874  if (pos1 == 0) continue;
1875  unsigned short pos2 = 0;
1876  for (unsigned short ii = 0; ii < tcl[it2].tclhits.size(); ++ii) {
1877  if (fHits[tcl[it2].tclhits[ii]].WireID().Wire <= vw) {
1878  if (std::abs(int(fHits[tcl[it2].tclhits[ii]].PeakTime() - fvt)) < 10) pos2 = ii;
1879  break;
1880  }
1881  } // ii
1882  // vertex is not near a hit on cluster 2
1883  if (pos2 == 0) continue;
1884  // ensure we aren't near an existing vertex
1885  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
1886  if (std::abs(fvw - vtx[iv].Wire) < 2 && std::abs(fvt - vtx[iv].Time) < 10) continue;
1887  }
1888  // make a new vertex
1889  VtxStore newvx;
1890  newvx.Wire = fvw;
1891  newvx.WireErr = 1;
1892  newvx.Time = fvt;
1893  newvx.TimeErr = 1;
1894  newvx.Topo = 5;
1895  newvx.CTP = clCTP;
1896  newvx.Fixed = false;
1897  vtx.push_back(newvx);
1898  unsigned short ivx = vtx.size() - 1;
1899  if (vtxprt)
1900  mf::LogVerbatim("CC") << " new vertex " << ivx << " cluster " << tcl[it1].ID
1901  << " split pos " << pos1;
1902  if (!SplitCluster(it1, pos1, ivx)) continue;
1903  tcl[tcl.size() - 1].ProcCode += 1000;
1904  tcl[tcl.size() - 2].ProcCode += 1000;
1905  if (vtxprt)
1906  mf::LogVerbatim("CC") << " new vertex " << ivx << " cluster " << tcl[it2].ID
1907  << " split pos " << pos2;
1908  if (!SplitCluster(it2, pos2, ivx)) continue;
1909  tcl[tcl.size() - 1].ProcCode += 1000;
1910  tcl[tcl.size() - 2].ProcCode += 1000;
1911  FitVtx(ivx);
1912  break;
1913  } // it2
1914  } // it1
1915 
1916  if (vtx.size() > vtxSizeIn) {
1917  // try to split other clusters
1918  VtxClusterSplit();
1919  // try to attach other clusters to it
1920  VertexCluster(vtx.size() - 1);
1921  FitAllVtx(clCTP);
1922  } // new vertex
1923 
1924  if (vtxprt) {
1925  mf::LogVerbatim("CC") << "Vertices " << vtx.size();
1926  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
1927  if (vtx[iv].CTP != clCTP) continue;
1928  mf::LogVerbatim("CC") << "vtx " << iv << " wire " << vtx[iv].Wire << " time "
1929  << (int)vtx[iv].Time << " NClusters " << vtx[iv].NClusters << " topo "
1930  << vtx[iv].Topo;
1931  }
1932  PrintClusters();
1933  }
1934 
1935  } // FindStarVertices()
1936 
1938  void ClusterCrawlerAlg::VertexCluster(unsigned short iv)
1939  {
1940  // try to attach clusters to the specified vertex
1941  if (vtx[iv].NClusters == 0) return;
1942 
1943  short dwb, dwe, dtb, dte;
1944  bool sigOK;
1945 
1946  for (unsigned short icl = 0; icl < tcl.size(); ++icl) {
1947  if (tcl[icl].ID < 0) continue;
1948  if (tcl[icl].CTP != vtx[iv].CTP) continue;
1949 
1950  dwb = vtx[iv].Wire - tcl[icl].BeginWir;
1951  dtb = vtx[iv].Time - tcl[icl].BeginTim;
1952  dwe = vtx[iv].Wire - tcl[icl].EndWir;
1953  dte = vtx[iv].Time - tcl[icl].EndTim;
1954 
1955  float drb = dwb * dwb + dtb * dtb;
1956  float dre = dwe * dwe + dte * dte;
1957 
1958  bool bCloser = (drb < dre);
1959 
1960  // ignore clusters in showers
1961  if (bCloser) {
1962  if (tcl[icl].BeginChgNear > fChgNearCut) continue;
1963  }
1964  else {
1965  if (tcl[icl].EndChgNear > fChgNearCut) continue;
1966  }
1967 
1968  if (vtxprt)
1969  mf::LogVerbatim("CC") << "VertexCluster: Try icl ID " << tcl[icl].ID << " w vtx " << iv
1970  << " dwb " << dwb << " dwe " << dwe << " drb " << drb << " dre "
1971  << dre << " Begin closer? " << bCloser;
1972 
1973  if (tcl[icl].BeginVtx < 0 && bCloser && dwb > -3 && dwb < 3 && tcl[icl].EndVtx != iv) {
1974  sigOK = ChkSignal(tcl[icl].BeginWir, tcl[icl].BeginTim, vtx[iv].Wire, vtx[iv].Time);
1975  if (vtxprt)
1976  mf::LogVerbatim("CC") << " Attach cluster Begin to vtx? " << iv << " sigOK " << sigOK;
1977  if (sigOK) {
1978  if (vtxprt)
1979  mf::LogVerbatim("CC") << " check ClusterVertexChi " << ClusterVertexChi(icl, 0, iv);
1980  if (ClusterVertexChi(icl, 0, iv) < fVertex2DCut) {
1981  // do a fit and check the vertex error
1982  tcl[icl].BeginVtx = iv;
1983  FitVtx(iv);
1984  if (vtx[iv].ChiDOF > fVertex2DCut || vtx[iv].WireErr > fVertex2DWireErrCut) {
1985  tcl[icl].BeginVtx = -99;
1986  FitVtx(iv);
1987  }
1988  } // good DoCA
1989  } // sigOK
1990  } // check BEGIN
1991 
1992  if (tcl[icl].EndVtx < 0 && !bCloser && dwe > -3 && dwe < 3 && tcl[icl].BeginVtx != iv) {
1993  sigOK = ChkSignal(tcl[icl].EndWir, tcl[icl].EndTim, vtx[iv].Wire, vtx[iv].Time);
1994  if (vtxprt)
1995  mf::LogVerbatim("CC") << " Attach cluster End to vtx? " << iv << " sigOK " << sigOK;
1996  if (sigOK) {
1997  if (vtxprt)
1998  mf::LogVerbatim("CC") << " check ClusterVertexChi " << ClusterVertexChi(icl, 1, iv);
1999  if (ClusterVertexChi(icl, 1, iv) < 3) {
2000  // do a fit and check the vertex error
2001  tcl[icl].EndVtx = iv;
2002  FitVtx(iv);
2003  if (vtx[iv].ChiDOF > fVertex2DCut || vtx[iv].WireErr > fVertex2DWireErrCut) {
2004  tcl[icl].EndVtx = -99;
2005  FitVtx(iv);
2006  }
2007  } // good DoCA
2008  } // sigOK
2009  } // check END
2010  } // icl
2011  } // VertexCluster
2012 
2014  void ClusterCrawlerAlg::FitAllVtx(CTP_t inCTP)
2015  {
2016 
2017  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
2018  if (vtx[iv].CTP != inCTP) continue;
2019  FitVtx(iv);
2020  }
2021 
2022  } // FitAllVtx
2023 
2025  void ClusterCrawlerAlg::FindVertices()
2026  {
2027  // try to make 2D vertices
2028 
2029  if (tcl.size() < 2) return;
2030 
2031  // sort clusters starting with the longest
2032  std::vector<CluLen> clulens;
2033  CluLen clulen;
2034  for (unsigned short icl = 0; icl < tcl.size(); ++icl) {
2035  if (tcl[icl].ID < 0) continue;
2036  if (tcl[icl].CTP != clCTP) continue;
2037  if (tcl[icl].BeginVtx >= 0 && tcl[icl].EndVtx >= 0) continue;
2038  clulen.index = icl;
2039  clulen.length = tcl[icl].tclhits.size();
2040  clulens.push_back(clulen);
2041  }
2042  if (empty(clulens)) return;
2043  std::sort(clulens.begin(), clulens.end(), greaterThan);
2044 
2045  float nwires = fNumWires;
2046  float maxtime = fMaxTime;
2047 
2048  unsigned short vtxSizeIn = vtx.size();
2049 
2050  vtxprt = (fDebugPlane == (short)plane && fDebugHit < 0);
2051  if (vtxprt) {
2052  mf::LogVerbatim("CC") << "FindVertices plane " << plane << " pass " << pass;
2053  PrintClusters();
2054  }
2055 
2056  float es1 = 0, eth1 = 0, ew1 = 0, et1 = 0;
2057  float bs1 = 0, bth1 = 0, bw1 = 0, bt1 = 0;
2058  float es2 = 0, eth2 = 0, ew2 = 0, et2 = 0;
2059  float bs2 = 0, bth2 = 0, bw2 = 0, bt2 = 0;
2060  float dth = 0, dsl = 0, fvw = 0, fvt = 0;
2061  float angcut = 0;
2062  unsigned int vw = 0, pass1, pass2, ii1, it1, ii2, it2;
2063  bool SigOK = false;
2064  for (ii1 = 0; ii1 < clulens.size() - 1; ++ii1) {
2065  it1 = clulens[ii1].index;
2066  es1 = tcl[it1].EndSlp;
2067  eth1 = tcl[it1].EndAng;
2068  ew1 = tcl[it1].EndWir;
2069  et1 = tcl[it1].EndTim;
2070  bs1 = tcl[it1].BeginSlp;
2071  bth1 = tcl[it1].BeginAng;
2072  bw1 = tcl[it1].BeginWir;
2073  bt1 = tcl[it1].BeginTim;
2074  pass1 = tcl[it1].ProcCode - 10 * (tcl[it1].ProcCode / 10);
2075  for (ii2 = ii1 + 1; ii2 < clulens.size(); ++ii2) {
2076  it2 = clulens[ii2].index;
2077  // try to attach cluster to existing vertices at either end
2078  ClusterVertex(it2);
2079  // ignore if both clusters are short
2080  if (tcl[it1].tclhits.size() < 5 && tcl[it2].tclhits.size() < 5) continue;
2081  es2 = tcl[it2].EndSlp;
2082  eth2 = tcl[it2].EndAng;
2083  ew2 = tcl[it2].EndWir;
2084  et2 = tcl[it2].EndTim;
2085  bs2 = tcl[it2].BeginSlp;
2086  bth2 = tcl[it2].BeginAng;
2087  bw2 = tcl[it2].BeginWir;
2088  bt2 = tcl[it2].BeginTim;
2089  pass2 = tcl[it2].ProcCode - 10 * (tcl[it2].ProcCode / 10);
2090  if (pass1 < pass2) { angcut = fKinkAngCut[pass2]; }
2091  else {
2092  angcut = fKinkAngCut[pass1];
2093  }
2094  // topo 1: check for vtx US of the ends of both clusters
2095  dth = fabs(eth1 - eth2);
2096  if (tcl[it1].EndVtx < 0 && tcl[it2].EndVtx < 0 && dth > 0.1) {
2097  dsl = es2 - es1;
2098  // find vertex wire and vertex time in float precision (fvw, fvt)
2099  fvw = (et1 - ew1 * es1 - et2 + ew2 * es2) / dsl;
2100  // vertex wire in the detector?
2101  if (fvw > 0. && fvw < nwires) {
2102  // require vtx in the range of wires with hits AND
2103  vw = (0.5 + fvw);
2104  // vtx US of both clusters AND
2105  // vtx not too far US of both clusters
2106  if (vw >= fFirstWire - 1 && fvw <= ew1 + 3 && fvw <= ew2 + 3 && fvw > (ew1 - 10) &&
2107  fvw > (ew2 - 10)) {
2108  fvt = et1 + (fvw - ew1) * es1;
2109  if (vtxprt)
2110  mf::LogVerbatim("CC")
2111  << "Chk clusters topo1 " << tcl[it1].ID << " " << tcl[it2].ID << " vtx wire "
2112  << vw << " time " << (int)fvt << " dth " << dth;
2113  if (fvt > 0 && fvt < maxtime) {
2114  // Vertex wire US of cluster ends and time in the detector.
2115  // Check for signal at the vertex position and adjust the vertex by 1 wire
2116  // if necessary
2117  SigOK = ChkSignal(vw, fvt, vw, fvt);
2118  if (!SigOK) {
2119  fvw += 1.;
2120  vw = (0.5 + fvw);
2121  SigOK = ChkSignal(vw, fvt, vw, fvt);
2122  }
2123  // Check this against existing vertices and update
2124  if (SigOK) ChkVertex(fvw, fvt, it1, it2, 1);
2125  } // fvt in detector
2126  } // vw topo 1 check
2127  } // fvw in detector
2128  } // topo 1
2129  // topo 2: check for vtx US of it1 and DS of it2
2130  dth = std::abs(eth1 - bth2);
2131  if (tcl[it1].EndVtx < 0 && tcl[it2].BeginVtx < 0 && dth > angcut) {
2132  dsl = bs2 - es1;
2133  if (fabs(ew1 - bw2) < 3 && fabs(et1 - bt2) < 500) { fvw = 0.5 * (ew1 + bw2); }
2134  else {
2135  fvw = (et1 - ew1 * es1 - bt2 + bw2 * bs2) / dsl;
2136  }
2137  if (fvw > 0 && fvw < nwires) {
2138  // vertex wire in the detector
2139  vw = (0.5 + fvw);
2140  // require vtx US of cluster 1 End AND
2141  // vtx DS of cluster 2 Begin
2142  if (fvw <= ew1 + 2 && fvw >= bw2 - 2) {
2143  fvt = et1 + (vw - ew1) * es1;
2144  fvt += bt2 + (vw - bw2) * bs2;
2145  fvt /= 2;
2146  if (vtxprt)
2147  mf::LogVerbatim("CC")
2148  << "Chk clusters topo2 " << tcl[it1].ID << " " << tcl[it2].ID << " vtx wire "
2149  << vw << " time " << (int)fvt << " dth " << dth;
2150  if (fvt > 0. && fvt < maxtime) {
2151  ChkVertex(fvw, fvt, it1, it2, 2);
2152  } // fvt in detector
2153  } // vw topo 2 check
2154  } // fvw in detector
2155  } // topo 2
2156  // topo 3: check for vtx DS of it1 and US of it2
2157  dth = std::abs(bth1 - eth2);
2158  if (tcl[it1].BeginVtx < 0 && tcl[it2].EndVtx < 0 && dth > angcut) {
2159  dsl = bs1 - es2;
2160  if (fabs(bw1 - ew2) < 3 && fabs(bt1 - et2) < 500) { fvw = 0.5 * (bw1 + ew2); }
2161  else {
2162  fvw = (et2 - ew2 * es2 - bt1 + bw1 * bs1) / dsl;
2163  }
2164  if (fvw > 0 && fvw < nwires) {
2165  vw = (0.5 + fvw);
2166  // require vtx US of cluster 2 Begin AND
2167  // vtx DS of cluster 1 End
2168  if (fvw <= ew2 + 2 && fvw >= bw1 - 2) {
2169  fvt = et2 + (fvw - ew2) * es2;
2170  fvt += bt1 + (fvw - bw1) * es1;
2171  fvt /= 2;
2172  if (vtxprt)
2173  mf::LogVerbatim("CC")
2174  << "Chk clusters topo3 " << tcl[it1].ID << " " << tcl[it2].ID << " vtx wire "
2175  << vw << " time " << (int)fvt << " dth " << dth;
2176  if (fvt > 0. && fvt < maxtime) {
2177  ChkVertex(fvw, fvt, it1, it2, 3);
2178  } // fvt in detector
2179  } // vw topo 3 check
2180  } // fvw in detector
2181  } // topo 3
2182  // topo 4: check for vtx DS of it1 and DS of it2
2183  dth = std::abs(bth1 - bth2);
2184  if (tcl[it1].BeginVtx < 0 && tcl[it2].BeginVtx < 0 && dth > 0.1) {
2185  dsl = bs2 - bs1;
2186  // find vertex wire and vertex time in float precision (fvw, fvt)
2187  // convert to integer if within the detector (vw, vt)
2188  fvw = (bt1 - bw1 * bs1 - bt2 + bw2 * bs2) / dsl;
2189  if (vtxprt)
2190  mf::LogVerbatim("CC") << "Chk clusters topo4 " << bw1 << ":" << (int)bt1 << " " << bw2
2191  << ":" << (int)bt2 << " fvw " << fvw << " nwires " << nwires;
2192  if (fvw > 0 && fvw < nwires) {
2193  // vertex wire in the detector
2194  vw = (0.5 + fvw);
2195  // require vtx in the range of wires with hits AND vtx DS of both clusters AND
2196  // vtx not too far DS of both clusters
2197  float dwcut = 10;
2198  if (tcl[it1].tclhits.size() < 2 * dwcut) dwcut = tcl[it1].tclhits.size() / 2;
2199  if (tcl[it2].tclhits.size() < 2 * dwcut) dwcut = tcl[it2].tclhits.size() / 2;
2200  if (fvw <= fLastWire + 1 && fvw >= bw1 - dwcut && fvw <= bw1 + dwcut &&
2201  fvw >= bw2 - dwcut && fvw <= bw1 + dwcut) {
2202  fvt = bt1 + (fvw - bw1) * bs1;
2203  if (vtxprt)
2204  mf::LogVerbatim("CC")
2205  << " vtx wire " << vw << " time " << fvt << " dth " << dth << " dwcut " << dwcut;
2206  if (fvt > 0. && fvt < maxtime) {
2207  // vertex wire US of cluster ends and time in the detector
2208  // Check for signal at the vertex position and adjust the vertex by 1 wire
2209  // if necessary
2210  SigOK = ChkSignal(vw, fvt, vw, fvt);
2211  if (!SigOK) {
2212  fvw -= 1.;
2213  vw = (0.5 + fvw);
2214  SigOK = ChkSignal(vw, fvt, vw, fvt);
2215  }
2216  // Check this against existing vertices and update
2217  if (SigOK) ChkVertex(fvw, fvt, it1, it2, 4);
2218  } // fvt in detector
2219  } // vw topo 4 check
2220  } // fvw in detector
2221  } // topo4
2222  } // it2
2223  } // it1
2224 
2225  // Fix vertices where both ends of a cluster are assigned to the
2226  // same vertex. This can happen with short clusters
2227  for (unsigned short it = 0; it < tcl.size(); ++it) {
2228  if (tcl[it].ID < 0) continue;
2229  if (tcl[it].CTP != clCTP) continue;
2230  if (tcl[it].BeginVtx > -1 && tcl[it].BeginVtx == tcl[it].EndVtx) {
2231  unsigned short iv = tcl[it].BeginVtx;
2232  float dwir = tcl[it].BeginWir - vtx[iv].Wire;
2233  float dtim = tcl[it].BeginTim - vtx[iv].Time;
2234  float rbeg = dwir * dwir + dtim * dtim;
2235  dwir = tcl[it].EndWir - vtx[iv].Wire;
2236  dtim = tcl[it].EndTim - vtx[iv].Time;
2237  float rend = dwir * dwir + dtim * dtim;
2238  if (rend < rbeg) { tcl[it].EndVtx = -99; }
2239  else {
2240  tcl[it].BeginVtx = -99;
2241  }
2242  } // tcl[it].BeginVtx == tcl[it].EndVtx
2243  } // it
2244 
2245  if (vtx.size() > vtxSizeIn) FitAllVtx(clCTP);
2246 
2247  // "delete" any vertices that have only one cluster
2248  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
2249  if (vtx[ivx].CTP != clCTP) continue;
2250  if (vtx[ivx].NClusters == 1) {
2251  vtx[ivx].NClusters = 0;
2252  for (unsigned short icl = 0; icl < tcl.size(); ++icl) {
2253  if (tcl[icl].CTP != clCTP) continue;
2254  if (tcl[icl].BeginVtx == ivx) {
2255  tcl[icl].BeginVtx = -99;
2256  break;
2257  }
2258  if (tcl[icl].EndVtx == ivx) {
2259  tcl[icl].EndVtx = -99;
2260  break;
2261  }
2262  } // icl
2263  } // vtx[ivx].NClusters == 1
2264  } // ivx
2265 
2266  } // FindVertices()
2267 
2269  void ClusterCrawlerAlg::ClusterVertex(unsigned short it)
2270  {
2271  // try to attach cluster it to an existing vertex
2272 
2273  if (vtx.size() == 0) return;
2274 
2275  unsigned short iv, jv;
2276  short dwib, dwjb, dwie, dwje;
2277  bool matchEnd, matchBegin;
2278 
2279  for (iv = 0; iv < vtx.size(); ++iv) {
2280  // ignore vertices in the wrong cryostat/TPC/Plane
2281  if (vtx[iv].CTP != clCTP) continue;
2282  // ignore deleted vertices
2283  if (vtx[iv].NClusters == 0) continue;
2284  // determine which end to match - begin or end. Handle short tracks
2285  matchEnd = false;
2286  matchBegin = false;
2287  if (tcl[it].tclhits.size() < 6) {
2288  // See which end is closer to this vertex vs other vertices
2289  dwib = std::abs(vtx[iv].Wire - tcl[it].BeginWir);
2290  if (dwib > 2) dwib = 2;
2291  dwie = std::abs(vtx[iv].Wire - tcl[it].EndWir);
2292  if (dwie > 2) dwie = 2;
2293  dwjb = 999;
2294  dwje = 999;
2295  for (jv = 0; jv < vtx.size(); ++jv) {
2296  if (iv == jv) continue;
2297  if (std::abs(vtx[jv].Time - tcl[it].BeginTim) < 50) {
2298  if (std::abs(vtx[jv].Wire - tcl[it].BeginWir) < dwjb)
2299  dwjb = std::abs(vtx[jv].Wire - tcl[it].BeginWir);
2300  } // std::abs(vtx[jv].Time - tcl[it].BeginTim) < 50
2301  if (std::abs(vtx[jv].Time - tcl[it].EndTim) < 50) {
2302  if (std::abs(vtx[jv].Wire - tcl[it].EndWir) < dwje)
2303  dwje = std::abs(vtx[jv].Wire - tcl[it].EndWir);
2304  } // std::abs(vtx[jv].Time - tcl[it].EndTim) < 50
2305  } // jv
2306  matchEnd = tcl[it].EndVtx != iv && dwie < 3 && dwie < dwje && dwie < dwib;
2307  matchBegin = tcl[it].BeginVtx != iv && dwib < 3 && dwib < dwjb && dwib < dwie;
2308  }
2309  else {
2310  matchEnd = tcl[it].EndVtx < 0 && vtx[iv].Wire <= tcl[it].EndWir + 2;
2311  matchBegin = tcl[it].BeginVtx < 0 && vtx[iv].Wire >= tcl[it].BeginWir - 2;
2312  }
2313  if (matchEnd) {
2314  if (vtxprt)
2315  mf::LogVerbatim("CC") << " Match End chi " << ClusterVertexChi(it, 1, iv) << " to vtx "
2316  << iv << " signalOK "
2317  << ChkSignal(
2318  vtx[iv].Wire, vtx[iv].Time, tcl[it].EndWir, tcl[it].EndTim);
2319  if (ClusterVertexChi(it, 1, iv) < fVertex2DCut &&
2320  ChkSignal(vtx[iv].Wire, vtx[iv].Time, tcl[it].EndWir, tcl[it].EndTim)) {
2321  // good match
2322  tcl[it].EndVtx = iv;
2323  // re-fit it
2324  FitVtx(iv);
2325  if (vtxprt)
2326  mf::LogVerbatim("CC") << " Add End " << tcl[it].ID << " to vtx " << iv << " NClusters "
2327  << vtx[iv].NClusters;
2328  if (vtx[iv].ChiDOF < fVertex2DCut && vtx[iv].WireErr < fVertex2DWireErrCut) return;
2329  if (vtxprt)
2330  mf::LogVerbatim("CC") << " Bad fit. ChiDOF " << vtx[iv].ChiDOF << " WireErr "
2331  << vtx[iv].WireErr << " Undo it";
2332  tcl[it].EndVtx = -99;
2333  FitVtx(iv);
2334  } // tChi < 3
2335  } // matchEnd
2336 
2337  if (matchBegin) {
2338  if (vtxprt)
2339  mf::LogVerbatim("CC") << " Match Begin chi " << ClusterVertexChi(it, 0, iv) << " to vtx "
2340  << iv << " signalOK "
2341  << ChkSignal(vtx[iv].Wire,
2342  vtx[iv].Time,
2343  tcl[it].BeginWir,
2344  tcl[it].BeginTim);
2345  if (ClusterVertexChi(it, 0, iv) < fVertex2DCut &&
2346  ChkSignal(vtx[iv].Wire, vtx[iv].Time, tcl[it].BeginWir, tcl[it].BeginTim)) {
2347  // good match
2348  tcl[it].BeginVtx = iv;
2349  // re-fit it
2350  FitVtx(iv);
2351  if (vtxprt)
2352  mf::LogVerbatim("CC") << " Add Begin " << tcl[it].ID << " to vtx " << iv
2353  << " NClusters " << vtx[iv].NClusters;
2354  if (vtx[iv].ChiDOF < fVertex2DCut && vtx[iv].WireErr < fVertex2DWireErrCut) return;
2355  if (vtxprt)
2356  mf::LogVerbatim("CC") << " Bad fit. ChiDOF " << vtx[iv].ChiDOF << " WireErr "
2357  << vtx[iv].WireErr << " Undo it";
2358  tcl[it].BeginVtx = -99;
2359  FitVtx(iv);
2360  } // tChi < 3
2361  } // matchBegin
2362  } // iv
2363  } // ClusterVertex()
2364 
2366  void ClusterCrawlerAlg::ChkVertex(float fvw,
2367  float fvt,
2368  unsigned short it1,
2369  unsigned short it2,
2370  short topo)
2371  {
2372  // Checks the vertex (vw, fvt) against the existing set of vertices.
2373  // If there a match, clusters it1 and/or it2 are associated with it
2374  // if there is signal between the existing vertex and the start of
2375  // the cluster. The topo flag indicates the type of vertex that was
2376  // found: 1 = US of it1 && US of it2, 2 = US of it1 && DS of it2,
2377  // 3 = DS of it1 && US of it2, 4 = DS of it1 and DS of it2.
2378  // didit is set true if a cluster is attached to a (new or old) vertex
2379 
2380  if (vtxprt)
2381  mf::LogVerbatim("CC") << " ChkVertex " << tcl[it1].EndWir << ":" << (int)tcl[it1].EndTim
2382  << " - " << tcl[it1].BeginWir << ":" << (int)tcl[it1].BeginTim
2383  << " and " << tcl[it2].EndWir << ":" << (int)tcl[it2].EndTim << " - "
2384  << tcl[it2].BeginWir << ":" << (int)tcl[it2].BeginTim << " topo "
2385  << topo << " fvw " << fvw << " fvt " << fvt;
2386 
2387  unsigned int vw = (unsigned int)(0.5 + fvw);
2388  unsigned int iht;
2389 
2390  // don't make vertices using short tracks that are close to
2391  // long straight clusters. These are most likely due to numerous
2392  // short delta ray clusters
2393  if (tcl[it1].tclhits.size() < 10 && tcl[it2].tclhits.size() < 10) {
2394  for (unsigned short it = 0; it < tcl.size(); ++it) {
2395  if (it == it1 || it == it2) continue;
2396  if (tcl[it].ID < 0) continue;
2397  if (tcl[it].CTP != clCTP) continue;
2398  if (tcl[it].tclhits.size() < 100) continue;
2399  if (std::abs(tcl[it].BeginAng - tcl[it].EndAng) > 0.1) continue;
2400  // don't reject because it is near the end of a long cluster
2401  if (vw < tcl[it].EndWir + 5) continue;
2402  if (vw > tcl[it].BeginWir - 5) continue;
2403  for (unsigned short ii = 0; ii < tcl[it].tclhits.size(); ++ii) {
2404  iht = tcl[it].tclhits[ii];
2405  if (fHits[iht].WireID().Wire <= vw) {
2406  if (std::abs(fHits[iht].PeakTime() - fvt) < 60) return;
2407  } // fHits[iht].WireID().Wire <= vWire
2408  } // ii
2409  } // it
2410  }
2411 
2412  // check vertex and clusters for proximity to existing vertices
2413  unsigned short nFitOK = 0;
2414  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
2415  if (vtx[iv].CTP != clCTP) continue;
2416  // make this a really loose cut since the errors on the prospective vertex aren't known yet
2417  if (PointVertexChi(fvw, fvt, iv) > 300) continue;
2418  if (vtxprt)
2419  mf::LogVerbatim("CC") << " vtx " << iv << " PointVertexChi "
2420  << PointVertexChi(fvw, fvt, iv);
2421  // got a match. Check the appropriate cluster end and attach
2422  if ((topo == 1 || topo == 2) && tcl[it1].EndVtx < 0) {
2423  if (ChkSignal(vw, fvt, tcl[it1].EndWir, tcl[it1].EndTim)) {
2424  // try to fit
2425  tcl[it1].EndVtx = iv;
2426  FitVtx(iv);
2427  if (vtxprt)
2428  mf::LogVerbatim("CC") << " FitVtx " << iv << " WireErr " << vtx[iv].WireErr
2429  << " ChiDOF " << vtx[iv].ChiDOF;
2430  if (vtx[iv].WireErr < fVertex2DWireErrCut && vtx[iv].ChiDOF < 5) { ++nFitOK; }
2431  else {
2432  // bad fit
2433  tcl[it1].EndVtx = -99;
2434  FitVtx(iv);
2435  } // check fit
2436  } // ChkSignal
2437  }
2438  else if ((topo == 3 || topo == 4) && tcl[it1].BeginVtx < 0) {
2439  if (ChkSignal(vw, fvt, tcl[it1].BeginWir, tcl[it1].BeginTim)) {
2440  tcl[it1].BeginVtx = iv;
2441  FitVtx(iv);
2442  if (vtxprt)
2443  mf::LogVerbatim("CC") << " FitVtx " << iv << " WireErr " << vtx[iv].WireErr
2444  << " ChiDOF " << vtx[iv].ChiDOF;
2445  if (vtx[iv].WireErr < fVertex2DWireErrCut && vtx[iv].ChiDOF < 5) { ++nFitOK; }
2446  else {
2447  // bad fit
2448  tcl[it1].BeginVtx = -99;
2449  FitVtx(iv);
2450  } // bad fit
2451  } // ChkSignal
2452  } // cluster it2
2453  if ((topo == 1 || topo == 3) && tcl[it2].EndVtx < 0) {
2454  if (ChkSignal(vw, fvt, tcl[it2].EndWir, tcl[it2].EndTim)) {
2455  tcl[it2].EndVtx = iv;
2456  FitVtx(iv);
2457  if (vtxprt)
2458  mf::LogVerbatim("CC") << " FitVtx " << iv << " WireErr " << vtx[iv].WireErr
2459  << " ChiDOF " << vtx[iv].ChiDOF;
2460  if (vtx[iv].WireErr < fVertex2DWireErrCut && vtx[iv].ChiDOF < 5) { ++nFitOK; }
2461  else {
2462  // bad fit
2463  tcl[it2].EndVtx = -99;
2464  FitVtx(iv);
2465  } // bad fit
2466  } // ChkSignal
2467  }
2468  else if ((topo == 2 || topo == 4) && tcl[it2].BeginVtx < 0) {
2469  if (ChkSignal(vw, fvt, tcl[it2].BeginWir, tcl[it2].BeginTim)) {
2470  tcl[it2].BeginVtx = iv;
2471  FitVtx(iv);
2472  if (vtxprt)
2473  mf::LogVerbatim("CC") << " FitVtx " << iv << " WireErr " << vtx[iv].WireErr
2474  << " ChiDOF " << vtx[iv].ChiDOF;
2475  if (vtx[iv].WireErr < fVertex2DWireErrCut && vtx[iv].ChiDOF < 5) { ++nFitOK; }
2476  else {
2477  // bad fit
2478  tcl[it2].BeginVtx = -99;
2479  FitVtx(iv);
2480  } // bad fit
2481  } // ChkSignal
2482  } // cluster it2
2483  if (nFitOK > 0) {
2484  if (vtxprt) mf::LogVerbatim("CC") << " Attached " << nFitOK << " clusters to vertex " << iv;
2485  return;
2486  }
2487  } // iv
2488 
2489  // no match to existing vertices. Ensure that there is a wire signal between
2490  // the vertex and the appropriate ends of the clusters
2491  bool SigOK = false;
2492  if (topo == 1 || topo == 2) { SigOK = ChkSignal(vw, fvt, tcl[it1].EndWir, tcl[it1].EndTim); }
2493  else {
2494  SigOK = ChkSignal(vw, fvt, tcl[it1].BeginWir, tcl[it1].BeginTim);
2495  }
2496  if (!SigOK) return;
2497 
2498  if (topo == 1 || topo == 3) { SigOK = ChkSignal(vw, fvt, tcl[it2].EndWir, tcl[it2].EndTim); }
2499  else {
2500  SigOK = ChkSignal(vw, fvt, tcl[it2].BeginWir, tcl[it2].BeginTim);
2501  }
2502  if (!SigOK) return;
2503 
2504  VtxStore newvx;
2505  newvx.Wire = vw;
2506  newvx.Time = fvt;
2507  newvx.Topo = topo;
2508  newvx.CTP = clCTP;
2509  newvx.Fixed = false;
2510  vtx.push_back(newvx);
2511  unsigned short iv = vtx.size() - 1;
2512  if (topo == 1 || topo == 2) {
2513  if (tcl[it1].EndVtx >= 0) {
2514  vtx.pop_back();
2515  return;
2516  }
2517  tcl[it1].EndVtx = iv;
2518  }
2519  else {
2520  if (tcl[it1].BeginVtx >= 0) {
2521  vtx.pop_back();
2522  return;
2523  }
2524  tcl[it1].BeginVtx = iv;
2525  }
2526  if (topo == 1 || topo == 3) {
2527  if (tcl[it2].EndVtx >= 0) {
2528  vtx.pop_back();
2529  return;
2530  }
2531  tcl[it2].EndVtx = iv;
2532  }
2533  else {
2534  if (tcl[it2].BeginVtx >= 0) {
2535  vtx.pop_back();
2536  return;
2537  }
2538  tcl[it2].BeginVtx = iv;
2539  }
2540  // fit it
2541  FitVtx(iv);
2542  // reject it if the fit is bad
2543  if (vtx[iv].ChiDOF < 5 && vtx[iv].WireErr < fVertex2DWireErrCut && vtx[iv].TimeErr < 20) {
2544  if (vtxprt)
2545  mf::LogVerbatim("CC") << " New vtx " << iv << " in plane " << plane << " topo " << topo
2546  << " cls " << tcl[it1].ID << " " << tcl[it2].ID << " W:T " << (int)vw
2547  << ":" << (int)fvt << " NClusters " << vtx[iv].NClusters;
2548  // Try to refine the cluster hits and vertex position
2549  if (fRefineVertexClusters) RefineVertexClusters(iv);
2550  }
2551  else {
2552  if (vtxprt)
2553  mf::LogVerbatim("CC") << " Bad vtx fit " << vtx[iv].ChiDOF << " wire err "
2554  << vtx[iv].WireErr << " TimeErr " << vtx[iv].TimeErr;
2555  // clobber the vertex and references to it
2556  vtx.pop_back();
2557  if (tcl[it1].BeginVtx == iv) tcl[it1].BeginVtx = -99;
2558  if (tcl[it1].EndVtx == iv) tcl[it1].EndVtx = -99;
2559  if (tcl[it2].BeginVtx == iv) tcl[it2].BeginVtx = -99;
2560  if (tcl[it2].EndVtx == iv) tcl[it2].EndVtx = -99;
2561  } // bad fit
2562 
2563  } // ChkVertex()
2564 
2566  bool ClusterCrawlerAlg::ChkSignal(unsigned int iht, unsigned int jht)
2567  {
2568  if (iht > fHits.size() - 1) return false;
2569  if (jht > fHits.size() - 1) return false;
2570  unsigned int wire1 = fHits[iht].WireID().Wire;
2571  float time1 = fHits[iht].PeakTime();
2572  unsigned int wire2 = fHits[jht].WireID().Wire;
2573  float time2 = fHits[jht].PeakTime();
2574  return ChkSignal(wire1, time1, wire2, time2);
2575  } // ChkSignal
2576 
2578  bool ClusterCrawlerAlg::ChkSignal(unsigned int wire1,
2579  float time1,
2580  unsigned int wire2,
2581  float time2)
2582  {
2583  // returns true if there is a signal on the line between
2584  // (wire1, time1) and (wire2, time2).
2585  // Be sure to set time1 < time2 if you are checking for signals on a single wire
2586 
2587  // Gaussian amplitude in bins of size 0.15
2588  const float gausAmp[20] = {1, 0.99, 0.96, 0.90, 0.84, 0.75, 0.67, 0.58, 0.49, 0.40,
2589  0.32, 0.26, 0.20, 0.15, 0.11, 0.08, 0.06, 0.04, 0.03, 0.02};
2590 
2591  // return true if fMinAmp is set ignore wire signal checking in this plane
2592  if (fMinAmp[plane] <= 0) return true;
2593 
2594  // get the begin and end right
2595  unsigned int wireb = wire1;
2596  float timeb = time1;
2597  unsigned int wiree = wire2;
2598  float timee = time2;
2599  // swap them?
2600  if (wiree > wireb) {
2601  wireb = wire2;
2602  timeb = time2;
2603  wiree = wire1;
2604  timee = time1;
2605  }
2606  if (wiree < fFirstWire || wiree > fLastWire) return false;
2607  if (wireb < fFirstWire || wireb > fLastWire) return false;
2608 
2609  int wire0 = wiree;
2610  // checking a single wire?
2611  float slope = 0;
2612  bool oneWire = false;
2613  float prTime, prTimeLo = 0, prTimeHi = 0;
2614  if (wireb == wiree) {
2615  oneWire = true;
2616  if (time1 < time2) {
2617  prTimeLo = time1;
2618  prTimeHi = time2;
2619  }
2620  else {
2621  prTimeLo = time2;
2622  prTimeHi = time1;
2623  }
2624  }
2625  else {
2626  slope = (timeb - timee) / (wireb - wiree);
2627  }
2628 
2629  int bin;
2630  unsigned short nmissed = 0;
2631  for (unsigned int wire = wiree; wire < wireb + 1; ++wire) {
2632  if (oneWire) { prTime = (prTimeLo + prTimeHi) / 2; }
2633  else {
2634  prTime = timee + (wire - wire0) * slope;
2635  }
2636  // skip dead wires
2637  if (WireHitRange[wire].first == -1) continue;
2638  // no hits on this wire
2639  if (WireHitRange[wire].first == -2) ++nmissed;
2640  unsigned int firsthit = WireHitRange[wire].first;
2641  unsigned int lasthit = WireHitRange[wire].second;
2642  float amp = 0;
2643  for (unsigned int khit = firsthit; khit < lasthit; ++khit) {
2644  if (oneWire) {
2645  // TODO: This sometimes doesn't work with overlapping hits
2646  // A not totally satisfactory solution
2647  if (prTime < fHits[khit].StartTick()) continue;
2648  if (prTime > fHits[khit].EndTick()) continue;
2649  return true;
2650  }
2651  else {
2652  // skip checking if we are far away from prTime on the positive side
2653  if (fHits[khit].PeakTime() - prTime > 500) continue;
2654  bin = std::abs(fHits[khit].PeakTime() - prTime) / fHits[khit].RMS();
2655  bin /= 0.15;
2656  if (bin > 19) continue;
2657  if (bin < 0) continue;
2658  // add amplitude from all hits
2659  amp += fHits[khit].PeakAmplitude() * gausAmp[bin];
2660  }
2661  } // khit
2662  if (amp < fMinAmp[plane]) ++nmissed;
2663  } // wire
2664  if (nmissed > fAllowNoHitWire) return false;
2665  return true;
2666 
2667  } // ChkSignal()
2668 
2670  bool ClusterCrawlerAlg::SplitCluster(unsigned short icl, unsigned short pos, unsigned short ivx)
2671  {
2672  // split cluster icl into two clusters
2673 
2674  if (tcl[icl].ID < 0) return false;
2675 
2676  // declare icl obsolete
2677  MakeClusterObsolete(icl);
2678 
2679  unsigned short ii, iclnew;
2680  unsigned int iht;
2681 
2682  if (pos > 2) {
2683  // Have enough hits to make a cluster at the Begin end
2684  // Create the first cluster (DS) using the Begin info
2685  clBeginSlp = tcl[icl].BeginSlp;
2686  clBeginSlpErr = tcl[icl].BeginSlpErr;
2687  clBeginAng = tcl[icl].BeginAng;
2688  clBeginWir = tcl[icl].BeginWir;
2689  clBeginTim = tcl[icl].BeginTim;
2690  clBeginChg = tcl[icl].BeginChg;
2691  clStopCode = 5;
2692  clProcCode = tcl[icl].ProcCode;
2693  fcl2hits.clear();
2694  chifits.clear();
2695  hitNear.clear();
2696  chgNear.clear();
2697  for (ii = 0; ii < pos; ++ii) {
2698  iht = tcl[icl].tclhits[ii];
2699  fcl2hits.push_back(iht);
2700  }
2701  // determine the pass in which this cluster was created
2702  pass = tcl[icl].ProcCode - 10 * (tcl[icl].ProcCode / 10);
2703  if (pass > fNumPass - 1) pass = fNumPass - 1;
2704  // fit the end hits
2705  FitCluster();
2706  clEndSlp = clpar[1];
2707  clEndSlpErr = clparerr[1];
2708  clEndAng = std::atan(fScaleF * clEndSlp);
2709  // find the charge at the end
2710  FitClusterChg();
2711  clEndChg = fAveChg;
2712  if (!TmpStore()) {
2713  RestoreObsoleteCluster(icl);
2714  return false;
2715  }
2716  // associate the End with the supplied vertex
2717  iclnew = tcl.size() - 1;
2718  tcl[iclnew].EndVtx = ivx;
2719  tcl[iclnew].BeginVtx = tcl[icl].BeginVtx;
2720  if (vtxprt)
2721  mf::LogVerbatim("CC") << "SplitCluster made cluster " << iclnew << " attached to Begin vtx "
2722  << ivx;
2723  } // pos > 2
2724 
2725  if (pos < tcl[icl].tclhits.size() - 3) {
2726  // have enough hits to make a cluster at the End
2727  // now create the second cluster (US)
2728  clEndSlp = tcl[icl].EndSlp;
2729  clEndSlpErr = tcl[icl].EndSlpErr;
2730  clEndAng = std::atan(fScaleF * clEndSlp);
2731  clEndWir = tcl[icl].EndWir;
2732  clEndTim = tcl[icl].EndTim;
2733  clEndChg = tcl[icl].EndChg;
2734  clStopCode = 5;
2735  clProcCode = tcl[icl].ProcCode;
2736  fcl2hits.clear();
2737  chifits.clear();
2738  hitNear.clear();
2739  chgNear.clear();
2740  bool didFit = false;
2741  for (ii = pos; ii < tcl[icl].tclhits.size(); ++ii) {
2742  iht = tcl[icl].tclhits[ii];
2743  if (inClus[iht] != 0) {
2744  RestoreObsoleteCluster(icl);
2745  return false;
2746  }
2747  fcl2hits.push_back(iht);
2748  // define the Begin parameters
2749  if (fcl2hits.size() == fMaxHitsFit[pass] || fcl2hits.size() == fMinHits[pass]) {
2750  FitCluster();
2751  clBeginSlp = clpar[1];
2752  clBeginAng = std::atan(fScaleF * clBeginSlp);
2753  clBeginSlpErr = clparerr[1];
2754  didFit = true;
2755  }
2756  if ((unsigned short)fcl2hits.size() == fNHitsAve[pass] + 1) {
2757  FitClusterChg();
2758  clBeginChg = fAveChg;
2759  didFit = true;
2760  }
2761  } // ii
2762  // do a fit using all hits if one wasn't done
2763  if (!didFit) {
2764  FitCluster();
2765  FitClusterChg();
2766  clBeginChg = fAveChg;
2767  }
2768  if (!TmpStore()) {
2769  // clobber the previously stored cluster
2770  MakeClusterObsolete(tcl.size() - 1);
2771  RestoreObsoleteCluster(icl);
2772  return false;
2773  }
2774  // associate the End with the supplied vertex
2775  iclnew = tcl.size() - 1;
2776  tcl[iclnew].BeginVtx = ivx;
2777  tcl[iclnew].EndVtx = tcl[icl].EndVtx;
2778  if (vtxprt)
2779  mf::LogVerbatim("CC") << "SplitCluster made cluster " << iclnew << " attached to End vtx "
2780  << ivx;
2781  }
2782 
2783  return true;
2784  } // SplitCluster()
2785 
2787  void ClusterCrawlerAlg::ChkMerge()
2788  {
2789  // Try to merge clusters. Clusters that have been subsumed in other
2790  // clusters, i.e. no longer valid, have ID < 0
2791 
2792  if (tcl.size() < 2) return;
2793  // The size of the ClusterStore vector will increase while merging
2794  // is on-going so the upper limit on it1 is fixed tcl.size() - 1
2795  // before merging starts
2796 
2797  prt = (fDebugPlane == (short)plane && fDebugWire < 0);
2798  if (prt) mf::LogVerbatim("CC") << "ChkMerge on pass " << pass;
2799 
2800  unsigned short it1, it2, nh1, pass1, pass2;
2801  float bs1, bth1, bt1, bc1, es1, eth1, et1, ec1;
2802  float bs2, bth2, bt2, bc2, es2, eth2, et2, ec2;
2803  int bw1, ew1, bw2, ew2, ndead;
2804  float dth, dw, angcut, chgrat, chgcut, dtim, timecut, bigslp;
2805  bool bothLong, NoVtx;
2806 
2807  int skipcut, tclsize = tcl.size();
2808  int maxOverlap = 3;
2809 
2810  for (it1 = 0; it1 < tclsize - 1; ++it1) {
2811  // ignore already merged clusters
2812  if (tcl[it1].ID < 0) continue;
2813  if (tcl[it1].CTP != clCTP) continue;
2814  bs1 = tcl[it1].BeginSlp;
2815  // convert slope to angle
2816  bth1 = std::atan(fScaleF * bs1);
2817  // more compact notation for begin/end, wire/time/chg/slp/theta, 1/2
2818  bw1 = tcl[it1].BeginWir;
2819  bt1 = tcl[it1].BeginTim;
2820  bc1 = tcl[it1].BeginChg;
2821  es1 = tcl[it1].EndSlp;
2822  eth1 = tcl[it1].EndAng;
2823  ew1 = tcl[it1].EndWir;
2824  et1 = tcl[it1].EndTim;
2825  ec1 = tcl[it1].EndChg;
2826  nh1 = tcl[it1].tclhits.size();
2827  pass1 = tcl[it1].ProcCode - 10 * (tcl[it1].ProcCode / 10);
2828  if (pass1 > fNumPass) pass1 = fNumPass;
2829  for (it2 = it1 + 1; it2 < tclsize; ++it2) {
2830  // ignore already merged clusters
2831  if (tcl[it1].ID < 0) continue;
2832  if (tcl[it2].ID < 0) continue;
2833  // only merge if they are in the right cryostat/TPC/plane
2834  if (tcl[it2].CTP != clCTP) continue;
2835  // Don't bother if these clusters, if merged, would fail the
2836  // cluster hit fraction cut
2837  if (!ChkMergedClusterHitFrac(it1, it2)) { continue; }
2838  bs2 = tcl[it2].BeginSlp;
2839  bth2 = std::atan(fScaleF * bs2);
2840  bw2 = tcl[it2].BeginWir;
2841  bt2 = tcl[it2].BeginTim;
2842  bc2 = tcl[it2].BeginChg;
2843  es2 = tcl[it2].EndSlp;
2844  eth2 = tcl[it2].EndAng;
2845  ew2 = tcl[it2].EndWir;
2846  et2 = tcl[it2].EndTim;
2847  ec2 = tcl[it2].EndChg;
2848  pass2 = tcl[it2].ProcCode - 10 * (tcl[it2].ProcCode / 10);
2849  if (pass2 > fNumPass) pass2 = fNumPass;
2850  // use the more promiscuous pass for cuts
2851  angcut = fKinkAngCut[pass1];
2852  if (fKinkAngCut[pass2] > angcut) angcut = fKinkAngCut[pass2];
2853  skipcut = fMaxWirSkip[pass1];
2854  if (fMaxWirSkip[pass2] > skipcut) skipcut = fMaxWirSkip[pass2];
2855  chgcut = fMergeChgCut[pass1];
2856  if (fMergeChgCut[pass2] > chgcut) chgcut = fMergeChgCut[pass2];
2857  // tweak the cuts for long straight tracks
2858  bothLong = (nh1 > 100 && tcl[it2].tclhits.size() > 100);
2859 
2860  // look for US and DS broken clusters w similar angle.
2861  // US cluster 2 merge with DS cluster 1?
2862  // This is the most likely occurrence given the order in which
2863  // clusters are created so put it first.
2864  dth = std::abs(bth2 - eth1);
2865  ndead = DeadWireCount(bw2, ew1);
2866  dw = ew1 - bw2 - ndead;
2867  // require no vertex between
2868  NoVtx = (tcl[it1].EndVtx < 0) && (tcl[it2].BeginVtx < 0);
2869  if (prt && bw2 < (ew1 + maxOverlap))
2870  mf::LogVerbatim("CC") << "Chk1 ID1-2 " << tcl[it1].ID << "-" << tcl[it2].ID << " " << ew1
2871  << ":" << (int)et1 << " " << bw2 << ":" << (int)bt2 << " dw " << dw
2872  << " ndead " << ndead << " skipcut " << skipcut << " dth " << dth
2873  << " angcut " << angcut;
2874  if (NoVtx && bw2 < (ew1 + maxOverlap) && dw < skipcut && dth < angcut) {
2875  chgrat = 2 * fabs(bc2 - ec1) / (bc2 + ec1);
2876  // ignore the charge cut for long tracks with small dth
2877  if (bothLong && dth < 0.05) chgrat = 0.;
2878  // project bw2,bt2 to ew1
2879  dtim = fabs(bt2 + (ew1 - bw2) * bs2 - et1);
2880  bigslp = std::abs(bs2);
2881  if (std::abs(es1) > bigslp) bigslp = std::abs(es1);
2882  timecut = fTimeDelta[pass2] * AngleFactor(bigslp);
2883  if (prt)
2884  mf::LogVerbatim("CC") << " dtim " << dtim << " timecut " << (int)timecut << " ec1 "
2885  << ec1 << " bc2 " << bc2 << " chgrat " << chgrat << " chgcut "
2886  << chgcut << " es1 " << es1 << " ChkSignal "
2887  << ChkSignal(ew1, et1, bw2, bt2);
2888  if (chgrat < chgcut && dtim < timecut) {
2889  // ensure there is a signal between cluster ends
2890  if (ChkSignal(ew1, et1, bw2, bt2)) {
2891  DoMerge(it2, it1, 10);
2892  tclsize = tcl.size();
2893  break;
2894  }
2895  } // chgrat < chgcut ...
2896  } // US cluster 2 merge with DS cluster 1?
2897 
2898  // look for US and DS broken clusters w similar angle
2899  // US cluster 1 merge with DS cluster 2?
2900  dth = fabs(bth1 - eth2);
2901  ndead = DeadWireCount(bw1, ew2);
2902  dw = ew2 - bw1 - ndead;
2903  if (prt && bw1 < (ew2 + maxOverlap) && dw < skipcut)
2904  mf::LogVerbatim("CC") << "Chk2 ID1-2 " << tcl[it1].ID << "-" << tcl[it2].ID << " " << bw1
2905  << ":" << (int)bt1 << " " << bw2 << ":" << (int)et2 << " dw " << dw
2906  << " ndead " << ndead << " skipcut " << skipcut << " dth " << dth
2907  << " angcut " << angcut;
2908  // require no vertex between
2909  NoVtx = (tcl[it2].EndVtx < 0) && (tcl[it1].BeginVtx < 0);
2910  if (NoVtx && bw1 < (ew2 + maxOverlap) && dw < skipcut && dth < angcut) {
2911  chgrat = 2 * fabs((bc1 - ec2) / (bc1 + ec2));
2912  // ignore the charge cut for long tracks with small dth
2913  if (bothLong && dth < 0.05) chgrat = 0.;
2914  // project sw1,st1 to ew2
2915  dtim = std::abs(bt1 + (ew2 - bw1) * bs1 - et2);
2916  bigslp = std::abs(bs1);
2917  if (std::abs(bs2) > bigslp) bigslp = std::abs(bs2);
2918  timecut = fTimeDelta[pass2] * AngleFactor(bigslp);
2919  if (prt)
2920  mf::LogVerbatim("CC") << " dtim " << dtim << " err " << dtim << " timecut "
2921  << (int)timecut << " chgrat " << chgrat << " chgcut " << chgcut
2922  << " ChkSignal " << ChkSignal(bw1, bt1, ew2, et2);
2923  // TODO: we should be checking for a signal here like we did above
2924  if (chgrat < chgcut && dtim < timecut) {
2925  if (ChkSignal(bw1, bt1, ew2, et2)) {
2926  DoMerge(it1, it2, 10);
2927  tclsize = tcl.size();
2928  break;
2929  }
2930  } // chgrat < chgcut ...
2931  } // US cluster 1 merge with DS cluster 2
2932 
2933  if (bw2 < bw1 && ew2 > ew1) {
2934  // look for small cl2 within the wire boundary of cl1
2935  // with similar times and slopes for both clusters
2936  dth = fabs(eth2 - eth1);
2937  dtim = fabs(et1 + (ew2 - ew1 - 1) * es1 - et2);
2938  bigslp = std::abs(es1);
2939  if (std::abs(es1) > bigslp) bigslp = std::abs(es1);
2940  timecut = fTimeDelta[pass2] * AngleFactor(bigslp);
2941  // count the number of wires with no hits on cluster 1
2942  short nmiss1 = bw1 - ew1 + 1 - tcl[it1].tclhits.size();
2943  // compare with the number of hits in cluster 2
2944  short nin2 = tcl[it2].tclhits.size();
2945  if (prt)
2946  mf::LogVerbatim("CC") << "cl2: " << ew2 << ":" << (int)et2 << " - " << bw2 << ":"
2947  << (int)bt2 << " within cl1 " << ew1 << ":" << (int)et1 << " - "
2948  << bw1 << ":" << (int)bt1 << " ? dth " << dth << " dtim " << dtim
2949  << " nmissed " << nmiss1 << " timecut " << timecut
2950  << " FIX THIS CODE";
2951  // make rough cuts before calling ChkMerge12
2952  // this may not work well for long wandering clusters
2953  // TODO fix this code
2954  bool didit = false;
2955  if (dth < 1 && dtim < timecut && nmiss1 >= nin2) ChkMerge12(it1, it2, didit);
2956  if (didit) {
2957  tclsize = tcl.size();
2958  break;
2959  } //didit
2960  } // small cl2 within the wire boundary of cl1
2961 
2962  if (bw1 < bw2 && ew1 > ew2) {
2963  // look for small cl1 within the wire boundary of cl2
2964  // with similar times and slopes for both clusters
2965  dth = std::abs(eth2 - eth1);
2966  dtim = std::abs(et2 + (ew1 - ew2 - 1) * es2 - et1);
2967  bigslp = std::abs(es1);
2968  if (std::abs(es1) > bigslp) bigslp = std::abs(es1);
2969  timecut = fTimeDelta[pass2] * AngleFactor(bigslp);
2970  // count the number of wires with no hits on cluster 2
2971  short nmiss2 = bw2 - ew2 + 1 - tcl[it2].tclhits.size();
2972  // compare with the number of hits in cluster 1
2973  short nin1 = tcl[it1].tclhits.size();
2974  if (prt)
2975  mf::LogVerbatim("CC") << "cl1: " << ew1 << ":" << (int)et1 << " - " << bw1 << ":"
2976  << (int)bt1 << " within cl2 " << ew2 << ":" << (int)et2 << " - "
2977  << bw2 << ":" << (int)bt2 << " ? dth " << dth << " dtim " << dtim
2978  << " nmissed " << nmiss2 << " timecut " << timecut
2979  << " FIX THIS CODE";
2980  // make rough cuts before calling ChkMerge12
2981  // this may not work well for long wandering clusters
2982  bool didit = false;
2983  if (dth < 1 && dtim < timecut && nmiss2 >= nin1) ChkMerge12(it2, it1, didit);
2984  if (didit) {
2985  tclsize = tcl.size();
2986  break;
2987  } // didit
2988  } // small cl1 within the wire boundary of cl2
2989 
2990  if (tcl[it1].ID < 0) break;
2991  } // cluster 2
2992  if (tcl[it1].ID < 0) continue;
2993  } // cluster 1
2994  }
2995 
2997  void ClusterCrawlerAlg::ChkMerge12(unsigned short it1, unsigned short it2, bool& didit)
2998  {
2999  // Calling routine has done a rough check that cluster it2 is a candidate
3000  // for merging with cluster it1. The wire range spanned by it2 lies
3001  // within the wire range of it1 and the clusters are reasonably close
3002  // together in time.
3003 
3004  // assume that no merging was done
3005  didit = false;
3006 
3007  if (prt) mf::LogVerbatim("CC") << "ChkMerge12 " << tcl[it1].ID << " " << tcl[it2].ID;
3008 
3009  ClusterStore& cl1 = tcl[it1];
3010  // fill a vector spanning the length of cluster 1 and filled with the hit
3011  // time
3012  int ew1 = tcl[it1].EndWir;
3013  int bw1 = tcl[it1].BeginWir;
3014  unsigned int iht, hit;
3015  int wire;
3016  std::vector<unsigned int> cl1hits(bw1 + 1 - ew1);
3017  // put in the hit IDs
3018  for (iht = 0; iht < cl1.tclhits.size(); ++iht) {
3019  hit = cl1.tclhits[iht];
3020  wire = fHits[hit].WireID().Wire;
3021  if (wire - ew1 < 0 || wire - ew1 > (int)cl1hits.size()) { return; }
3022  cl1hits[wire - ew1] = hit;
3023  }
3024  unsigned int ew2 = tcl[it2].EndWir;
3025  float et2 = tcl[it2].EndTim;
3026  // look for the closest wire with a hit on cluster 1
3027  unsigned int wiron1 = 0;
3028  // count the number of missing hits
3029  short nmiss = 0;
3030  for (wire = ew2 - 1; wire > ew1; --wire) {
3031  if (cl1hits[wire - ew1] > 0) {
3032  wiron1 = wire;
3033  break;
3034  }
3035  ++nmiss;
3036  } // wire
3037  if (prt) mf::LogVerbatim("CC") << "chk next US wire " << wiron1 << " missed " << nmiss;
3038  if (wiron1 == 0) return;
3039  if (nmiss > fMaxWirSkip[pass]) return;
3040 
3041  // compare the wires with hits on cluster 2 with the gap in cluster 1
3042  // the number of hit wires that fit in the gap
3043  unsigned int hiton2;
3044  int wiron2;
3045  unsigned short nfit = 0;
3046  for (iht = 0; iht < tcl[it2].tclhits.size(); ++iht) {
3047  hiton2 = tcl[it2].tclhits[iht];
3048  wiron2 = fHits[hiton2].WireID().Wire;
3049  if (wiron2 < ew1 || wiron2 > bw1) return;
3050  if (cl1hits[wiron2 - ew1] == 0) ++nfit;
3051  }
3052  // require complete filling of the gap
3053  if (nfit < tcl[it2].tclhits.size()) return;
3054 
3055  // decode the pass for both clusters and select the matching cuts
3056  unsigned short pass1 = tcl[it1].ProcCode - 10 * (tcl[it1].ProcCode / 10);
3057  unsigned short pass2 = tcl[it2].ProcCode - 10 * (tcl[it2].ProcCode / 10);
3058  unsigned short cpass = pass1;
3059  // use the tighter cuts
3060  if (pass2 < pass1) cpass = pass2;
3061 
3062  // ***** Check End of Cluster 2 matching with middle of cluster 1
3063  if ((int)wiron1 - ew1 < 0) return;
3064  unsigned int hiton1 = cl1hits[wiron1 - ew1];
3065  if (hiton1 > fHits.size() - 1) { return; }
3066  // check the time difference
3067  float timon1 = fHits[hiton1].PeakTime();
3068  float dtim = std::abs(et2 + (wiron1 - ew2) * tcl[it2].EndSlp - timon1);
3069  if (dtim > fTimeDelta[cpass]) return;
3070  // check the slope difference. First do a local fit on cluster 1 near
3071  // the matching point
3072  FitClusterMid(it1, hiton1, 3);
3073  if (clChisq > 20.) return;
3074  // fit parameters are now in clpar.
3075  // check for angle consistency
3076  float dth = std::abs(std::atan(fScaleF * clpar[1]) - std::atan(fScaleF * tcl[it2].EndSlp));
3077  if (prt) mf::LogVerbatim("CC") << "US dtheta " << dth << " cut " << fKinkAngCut[cpass];
3078  if (dth > fKinkAngCut[cpass]) return;
3079  // make a charge ratio cut. fAveChg was calculated in FitClusterMid
3080  float chgrat = 2 * std::abs(fAveChg - tcl[it2].EndChg) / (fAveChg + tcl[it2].EndChg);
3081  if (prt) mf::LogVerbatim("CC") << "US chgrat " << chgrat << " cut " << fMergeChgCut[pass];
3082  // ensure that there is a signal on any missing wires at the US end of 1
3083  bool SigOK;
3084  SigOK = ChkSignal(wiron1, timon1, ew2, et2);
3085  if (prt) mf::LogVerbatim("CC") << "US SigOK? " << SigOK;
3086  if (!SigOK) return;
3087 
3088  // ***** Check Begin of Cluster 2 matching with middle of cluster 1
3089  unsigned int bw2 = tcl[it2].BeginWir;
3090  float bt2 = tcl[it2].BeginTim;
3091  nmiss = 0;
3092  wiron1 = 0;
3093  for (wire = bw2 + 1; wire < bw1; ++wire) {
3094  if (cl1hits[wire - ew1] > 0) {
3095  wiron1 = wire;
3096  break;
3097  }
3098  ++nmiss;
3099  }
3100  if (wiron1 == 0) return;
3101  if (nmiss > fMaxWirSkip[pass]) return;
3102  // fit this section of cluster 1 with 4 hits starting at the hit on the
3103  // closest wire and moving DS
3104  hiton1 = cl1hits[wiron1 - ew1];
3105  if (hiton1 > fHits.size() - 1) { return; }
3106  timon1 = fHits[hiton1].PeakTime();
3107  dtim = std::abs(bt2 - (wiron1 - bw2) * tcl[it2].BeginSlp - timon1);
3108  if (dtim > fTimeDelta[cpass]) return;
3109  FitClusterMid(it1, hiton1, -3);
3110  if (clChisq > 20.) return;
3111  // check for angle consistency
3112  dth = std::abs(std::atan(fScaleF * clpar[1]) - std::atan(fScaleF * tcl[it2].BeginSlp));
3113  if (prt) mf::LogVerbatim("CC") << "DS dtheta " << dth << " cut " << fKinkAngCut[cpass];
3114  if (dth > fKinkAngCut[cpass]) return;
3115  // make a charge ratio cut
3116  chgrat = 2 * std::abs(fAveChg - tcl[it2].BeginChg) / (fAveChg + tcl[it2].BeginChg);
3117  if (prt) mf::LogVerbatim("CC") << "DS chgrat " << chgrat << " cut " << fMergeChgCut[pass];
3118  // ensure that there is a signal on any missing wires at the US end of 1
3119  SigOK = ChkSignal(wiron1, timon1, bw2, bt2);
3120  if (prt) mf::LogVerbatim("CC") << "DS SigOK? " << SigOK;
3121  if (!SigOK) return;
3122 
3123  if (prt) mf::LogVerbatim("CC") << "Merge em";
3124  // success. Merge them
3125  DoMerge(it1, it2, 100);
3126  didit = true;
3127  } // ChkMerge12()
3128 
3130  void ClusterCrawlerAlg::DoMerge(unsigned short it1, unsigned short it2, short inProcCode)
3131  {
3132  // Merge clusters.
3133 
3134  ClusterStore& cl1 = tcl[it1];
3135  ClusterStore& cl2 = tcl[it2];
3136 
3137  if (cl1.tclhits.size() < 3) return;
3138  if (cl2.tclhits.size() < 3) return;
3139 
3140  unsigned int lowire, hiwire, whsize, ii, iht, indx;
3141  // do a fit across the boundary between cl1 and cl2 to
3142  // ensure that they truly should be merged
3143  unsigned int fithit;
3144  // Find the low and high wire for both clusters.
3145  // Assume that cluster 1 is DS
3146  bool cl1DS = true;
3147  hiwire = cl1.BeginWir;
3148  fithit = cl1.tclhits[cl1.tclhits.size() - 2];
3149  if (cl2.BeginWir > hiwire) {
3150  hiwire = cl2.BeginWir;
3151  fithit = cl2.tclhits[cl2.tclhits.size() - 2];
3152  cl1DS = false;
3153  }
3154  lowire = cl1.EndWir;
3155  if (cl2.EndWir < lowire) lowire = cl2.EndWir;
3156 
3157  // make a vector of wire hits
3158  whsize = hiwire + 2 - lowire;
3159  std::vector<int> wirehit(whsize, -1);
3160  // put in the hit IDs for cluster 2
3161  for (ii = 0; ii < cl2.tclhits.size(); ++ii) {
3162  iht = cl2.tclhits[ii];
3163  indx = fHits[iht].WireID().Wire - lowire;
3164  wirehit[indx] = iht;
3165  } // iht
3166  // now cluster 1
3167  for (ii = 0; ii < cl1.tclhits.size(); ++ii) {
3168  iht = cl1.tclhits[ii];
3169  indx = fHits[iht].WireID().Wire - lowire;
3170  wirehit[indx] = iht;
3171  } // iht
3172  // make the new cluster
3173  fcl2hits.clear();
3174  for (std::vector<int>::reverse_iterator rit = wirehit.rbegin(); rit != wirehit.rend(); ++rit) {
3175  if (*rit < 0) continue;
3176  fcl2hits.push_back(*rit);
3177  } // rit
3178 
3179  // fit the 6 hits that are near the merging point
3180  short nhitfit = 6;
3181  FitClusterMid(fcl2hits, fithit, nhitfit);
3182  if (clChisq > 5) return;
3183 
3184  // mark cl1 and cl2 obsolete
3185  MakeClusterObsolete(it1);
3186  MakeClusterObsolete(it2);
3187 
3188  short endVtx = 0;
3189  short begVtx = 0;
3190  short del1Vtx = -99;
3191  short del2Vtx = -99;
3192  if (cl1DS) {
3193  // use cluster 1 Begin info
3194  clBeginSlp = cl1.BeginSlp;
3195  clBeginSlpErr = cl1.BeginSlpErr;
3196  clBeginAng = cl1.BeginAng;
3197  clBeginWir = cl1.BeginWir;
3198  clBeginTim = cl1.BeginTim;
3199  clBeginChg = cl1.BeginChg;
3200  clBeginChgNear = cl1.BeginChgNear;
3201  begVtx = cl1.BeginVtx;
3202  del1Vtx = cl1.EndVtx;
3203  // and cluster 2 End info
3204  clEndSlp = cl2.EndSlp;
3205  clEndSlpErr = cl2.EndSlpErr;
3206  clEndAng = cl2.EndAng;
3207  clEndWir = cl2.EndWir;
3208  clEndTim = cl2.EndTim;
3209  clEndChg = cl2.EndChg;
3210  clEndChgNear = cl2.EndChgNear;
3211  endVtx = cl2.EndVtx;
3212  del2Vtx = cl2.BeginVtx;
3213  clStopCode = cl2.StopCode;
3214  }
3215  else {
3216  // use cluster 2 Begin info
3217  clBeginSlp = cl2.BeginSlp;
3218  clBeginSlpErr = cl2.BeginSlpErr;
3219  clBeginAng = cl2.BeginAng;
3220  clBeginWir = cl2.BeginWir;
3221  clBeginTim = cl2.BeginTim;
3222  clBeginChg = cl2.BeginChg;
3223  clBeginChgNear = cl2.BeginChgNear;
3224  begVtx = cl2.BeginVtx;
3225  del2Vtx = cl2.EndVtx;
3226  // and cluster 1 End info
3227  clEndSlp = cl1.EndSlp;
3228  clEndSlpErr = cl1.EndSlpErr;
3229  clEndWir = cl1.EndWir;
3230  clEndTim = cl1.EndTim;
3231  clEndChg = cl1.EndChg;
3232  clEndChgNear = cl1.EndChgNear;
3233  endVtx = cl1.EndVtx;
3234  del1Vtx = cl1.BeginVtx;
3235  clStopCode = cl1.StopCode;
3236  }
3237 
3238  // append it to the tcl vector
3239  clCTP = cl1.CTP;
3240  if (!TmpStore()) return;
3241  unsigned short itnew = tcl.size() - 1;
3242  if (prt)
3243  mf::LogVerbatim("CC") << "DoMerge " << cl1.ID << " " << cl2.ID << " -> " << tcl[itnew].ID;
3244  // stuff the processor code with the current pass
3245  tcl[itnew].ProcCode = inProcCode + pass;
3246  // transfer the vertex info
3247  // delete a vertex between these two?
3248  if (del1Vtx >= 0 && del1Vtx == del2Vtx) vtx[del1Vtx].NClusters = 0;
3249  // preserve the vertex assignments
3250  tcl[itnew].BeginVtx = begVtx;
3251  tcl[itnew].EndVtx = endVtx;
3252  } // DoMerge
3253 
3255  void ClusterCrawlerAlg::PrintVertices()
3256  {
3257 
3258  mf::LogVerbatim myprt("CC");
3259 
3260  if (vtx3.size() > 0) {
3261  // print out 3D vertices
3262  myprt
3263  << "****** 3D vertices ******************************************__2DVtx_Indx__*******\n";
3264  myprt
3265  << "Vtx Cstat TPC Proc X Y Z XEr YEr ZEr pln0 pln1 pln2 Wire\n";
3266  for (unsigned short iv = 0; iv < vtx3.size(); ++iv) {
3267  myprt << std::right << std::setw(3) << std::fixed << iv << std::setprecision(1);
3268  myprt << std::right << std::setw(7) << vtx3[iv].CStat;
3269  myprt << std::right << std::setw(5) << vtx3[iv].TPC;
3270  myprt << std::right << std::setw(5) << vtx3[iv].ProcCode;
3271  myprt << std::right << std::setw(8) << vtx3[iv].X;
3272  myprt << std::right << std::setw(8) << vtx3[iv].Y;
3273  myprt << std::right << std::setw(8) << vtx3[iv].Z;
3274  myprt << std::right << std::setw(5) << vtx3[iv].XErr;
3275  myprt << std::right << std::setw(5) << vtx3[iv].YErr;
3276  myprt << std::right << std::setw(5) << vtx3[iv].ZErr;
3277  myprt << std::right << std::setw(5) << vtx3[iv].Ptr2D[0];
3278  myprt << std::right << std::setw(5) << vtx3[iv].Ptr2D[1];
3279  myprt << std::right << std::setw(5) << vtx3[iv].Ptr2D[2];
3280  myprt << std::right << std::setw(5) << vtx3[iv].Wire;
3281  if (vtx3[iv].Wire < 0) { myprt << " Matched in all planes"; }
3282  else {
3283  myprt << " Incomplete";
3284  }
3285  myprt << "\n";
3286  }
3287  } // vtx3.size
3288 
3289  if (vtx.size() > 0) {
3290  // print out 2D vertices
3291  myprt << "************ 2D vertices ************\n";
3292  myprt << "Vtx CTP wire error tick error ChiDOF NCl topo cluster IDs\n";
3293  for (unsigned short iv = 0; iv < vtx.size(); ++iv) {
3294  if (fDebugPlane < 3 && fDebugPlane != (int)vtx[iv].CTP) continue;
3295  myprt << std::right << std::setw(3) << std::fixed << iv << std::setprecision(1);
3296  myprt << std::right << std::setw(6) << vtx[iv].CTP;
3297  myprt << std::right << std::setw(8) << vtx[iv].Wire << " +/- ";
3298  myprt << std::right << std::setw(4) << vtx[iv].WireErr;
3299  myprt << std::right << std::setw(8) << vtx[iv].Time << " +/- ";
3300  myprt << std::right << std::setw(4) << vtx[iv].TimeErr;
3301  myprt << std::right << std::setw(8) << vtx[iv].ChiDOF;
3302  myprt << std::right << std::setw(5) << vtx[iv].NClusters;
3303  myprt << std::right << std::setw(6) << vtx[iv].Topo;
3304  myprt << " ";
3305  // display the cluster IDs
3306  for (unsigned short ii = 0; ii < tcl.size(); ++ii) {
3307  if (fDebugPlane < 3 && fDebugPlane != (int)tcl[ii].CTP) continue;
3308  if (tcl[ii].ID < 0) continue;
3309  if (tcl[ii].BeginVtx == (short)iv) myprt << std::right << std::setw(4) << tcl[ii].ID;
3310  if (tcl[ii].EndVtx == (short)iv) myprt << std::right << std::setw(4) << tcl[ii].ID;
3311  }
3312  myprt << "\n";
3313  } // iv
3314  } // vtx.size
3315 
3316  } // PrintVertices
3317 
3320  {
3321 
3322  // prints clusters to the screen for code development
3323  mf::LogVerbatim myprt("CC");
3324 
3325  PrintVertices();
3326 
3327  float aveRMS, aveRes;
3328  myprt << "*************************************** Clusters "
3329  "*********************************************************************\n";
3330  myprt << " ID CTP nht Stop Proc beg_W:T bAng bSlp Err bChg end_W:T eAng eSlp "
3331  "Err eChg bVx eVx aveRMS Qual cnt\n";
3332  for (unsigned short ii = 0; ii < tcl.size(); ++ii) {
3333  // print clusters in all planes (fDebugPlane = 3) or in a selected plane
3334  if (fDebugPlane < 3 && fDebugPlane != (int)tcl[ii].CTP) continue;
3335  myprt << std::right << std::setw(4) << tcl[ii].ID;
3336  myprt << std::right << std::setw(3) << tcl[ii].CTP;
3337  myprt << std::right << std::setw(5) << tcl[ii].tclhits.size();
3338  myprt << std::right << std::setw(4) << tcl[ii].StopCode;
3339  myprt << std::right << std::setw(6) << tcl[ii].ProcCode;
3340  unsigned int iTime = tcl[ii].BeginTim;
3341  myprt << std::right << std::setw(6) << tcl[ii].BeginWir << ":" << iTime;
3342  if (iTime < 10) { myprt << " "; }
3343  else if (iTime < 100) {
3344  myprt << " ";
3345  }
3346  else if (iTime < 1000)
3347  myprt << " ";
3348  myprt << std::right << std::setw(7) << std::fixed << std::setprecision(2) << tcl[ii].BeginAng;
3349  if (std::abs(tcl[ii].BeginSlp) < 100) {
3350  myprt << std::right << std::setw(6) << std::fixed << std::setprecision(2)
3351  << tcl[ii].BeginSlp;
3352  }
3353  else {
3354  myprt << std::right << std::setw(6) << (int)tcl[ii].BeginSlp;
3355  }
3356  myprt << std::right << std::setw(6) << std::fixed << std::setprecision(2)
3357  << tcl[ii].BeginSlpErr;
3358  myprt << std::right << std::setw(5) << (int)tcl[ii].BeginChg;
3359  iTime = tcl[ii].EndTim;
3360  myprt << std::right << std::setw(6) << tcl[ii].EndWir << ":" << iTime;
3361  if (iTime < 10) { myprt << " "; }
3362  else if (iTime < 100) {
3363  myprt << " ";
3364  }
3365  else if (iTime < 1000)
3366  myprt << " ";
3367  myprt << std::right << std::setw(7) << std::fixed << std::setprecision(2) << tcl[ii].EndAng;
3368  if (std::abs(tcl[ii].EndSlp) < 100) {
3369  myprt << std::right << std::setw(6) << std::fixed << std::setprecision(2) << tcl[ii].EndSlp;
3370  }
3371  else {
3372  myprt << std::right << std::setw(6) << (int)tcl[ii].EndSlp;
3373  }
3374  myprt << std::right << std::setw(6) << std::fixed << std::setprecision(2)
3375  << tcl[ii].EndSlpErr;
3376  myprt << std::right << std::setw(5) << (int)tcl[ii].EndChg;
3377  myprt << std::right << std::setw(5) << tcl[ii].BeginVtx;
3378  myprt << std::right << std::setw(5) << tcl[ii].EndVtx;
3379  aveRMS = 0;
3380  unsigned int iht = 0;
3381  for (unsigned short jj = 0; jj < tcl[ii].tclhits.size(); ++jj) {
3382  iht = tcl[ii].tclhits[jj];
3383  aveRMS += fHits[iht].RMS();
3384  }
3385  aveRMS /= (float)tcl[ii].tclhits.size();
3386  myprt << std::right << std::setw(5) << std::fixed << std::setprecision(1) << aveRMS;
3387  aveRes = 0;
3388  // find cluster tracking resolution
3389  unsigned int hit0, hit1, hit2, cnt = 0;
3390  float arg;
3391  for (unsigned short iht = 1; iht < tcl[ii].tclhits.size() - 1; ++iht) {
3392  hit1 = tcl[ii].tclhits[iht];
3393  hit0 = tcl[ii].tclhits[iht - 1];
3394  hit2 = tcl[ii].tclhits[iht + 1];
3395  // require hits on adjacent wires
3396  if (fHits[hit1].WireID().Wire + 1 != fHits[hit0].WireID().Wire) continue;
3397  if (fHits[hit2].WireID().Wire + 1 != fHits[hit1].WireID().Wire) continue;
3398  arg = (fHits[hit0].PeakTime() + fHits[hit2].PeakTime()) / 2 - fHits[hit1].PeakTime();
3399  aveRes += arg * arg;
3400  ++cnt;
3401  }
3402  if (cnt > 1) {
3403  aveRes /= (float)cnt;
3404  aveRes = sqrt(aveRes);
3405  // convert to a quality factor
3406  aveRes /= (aveRMS * fHitErrFac);
3407  myprt << std::right << std::setw(6) << std::fixed << std::setprecision(1) << aveRes;
3408  myprt << std::right << std::setw(5) << std::fixed << cnt;
3409  }
3410  else {
3411  myprt << " NA";
3412  myprt << std::right << std::setw(5) << std::fixed << cnt;
3413  }
3414  myprt << "\n";
3415  } // ii
3416 
3417  } // PrintClusters()
3418 
3420  void ClusterCrawlerAlg::TmpGet(unsigned short it1)
3421  {
3422  // copies temp cluster it1 into the fcl2hits vector, etc. This is
3423  // effectively the inverse of cl2TmpStore
3424 
3425  if (it1 > tcl.size()) return;
3426 
3427  clBeginSlp = tcl[it1].BeginSlp;
3428  clBeginSlpErr = tcl[it1].BeginSlpErr;
3429  clBeginAng = tcl[it1].BeginAng;
3430  clBeginWir = tcl[it1].BeginWir;
3431  clBeginTim = tcl[it1].BeginTim;
3432  clBeginChg = tcl[it1].BeginChg;
3433  clBeginChgNear = tcl[it1].BeginChgNear;
3434  clEndSlp = tcl[it1].EndSlp;
3435  clEndSlpErr = tcl[it1].EndSlpErr;
3436  clEndAng = tcl[it1].EndAng;
3437  clEndWir = tcl[it1].EndWir;
3438  clEndTim = tcl[it1].EndTim;
3439  clEndChg = tcl[it1].EndChg;
3440  clEndChgNear = tcl[it1].EndChgNear;
3441  clStopCode = tcl[it1].StopCode;
3442  clProcCode = tcl[it1].ProcCode;
3443  clCTP = tcl[it1].CTP;
3444  fcl2hits = tcl[it1].tclhits;
3445  }
3446 
3448  bool ClusterCrawlerAlg::TmpStore()
3449  {
3450 
3451  if (fcl2hits.size() < 2) return false;
3452  if (fcl2hits.size() > fHits.size()) return false;
3453 
3454  if (NClusters == SHRT_MAX) return false;
3455 
3456  ++NClusters;
3457 
3458  unsigned int hit0 = fcl2hits[0];
3459  unsigned int hit;
3460  unsigned int tCST = fHits[hit0].WireID().Cryostat;
3461  unsigned int tTPC = fHits[hit0].WireID().TPC;
3462  unsigned int tPlane = fHits[hit0].WireID().Plane;
3463  unsigned int lastWire = 0;
3464 
3465  for (unsigned short it = 0; it < fcl2hits.size(); ++it) {
3466  hit = fcl2hits[it];
3467  if (inClus[hit] != 0) {
3468  --NClusters;
3469  return false;
3470  }
3471  // check for WireID() consistency
3472  if (fHits[hit].WireID().Cryostat != tCST || fHits[hit].WireID().TPC != tTPC ||
3473  fHits[hit].WireID().Plane != tPlane) {
3474  --NClusters;
3475  return false;
3476  }
3477  // check for decreasing wire number
3478  if (clProcCode < 900 && it > 0 && fHits[hit].WireID().Wire >= lastWire) {
3479  --NClusters;
3480  return false;
3481  }
3482  lastWire = fHits[hit].WireID().Wire;
3483  inClus[hit] = NClusters;
3484  }
3485 
3486  // ensure that the cluster begin/end info is correct
3487 
3488  // define the begin/end charge if it wasn't done already
3489  if (clEndChg <= 0) {
3490  // use the next to the last two hits. The End hit may have low charge
3491  unsigned int ih0 = fcl2hits.size() - 2;
3492  hit = fcl2hits[ih0];
3493  clEndChg = fHits[hit].Integral();
3494  hit = fcl2hits[ih0 - 1];
3495  clEndChg += fHits[hit].Integral();
3496  clEndChg = clEndChg / 2.;
3497  }
3498  if (clBeginChg <= 0) {
3499  // use the 2nd and third hit. The Begin hit may have low charge
3500  hit = fcl2hits[1];
3501  clBeginChg = fHits[hit].Integral();
3502  hit = fcl2hits[2];
3503  clBeginChg += fHits[hit].Integral();
3504  clBeginChg = clBeginChg / 2.;
3505  }
3506 
3507  hit0 = fcl2hits[0];
3508  hit = fcl2hits[fcl2hits.size() - 1];
3509 
3510  // store the cluster in the temporary ClusterStore struct
3511  ClusterStore clstr;
3512 
3513  clstr.ID = NClusters;
3514  clstr.BeginSlp = clBeginSlp;
3515  clstr.BeginSlpErr = clBeginSlpErr;
3516  clstr.BeginAng = std::atan(fScaleF * clBeginSlp);
3517  clstr.BeginWir = fHits[hit0].WireID().Wire;
3518  clstr.BeginTim = fHits[hit0].PeakTime();
3519  clstr.BeginChg = clBeginChg;
3520  clstr.BeginChgNear = clBeginChgNear;
3521  clstr.EndSlp = clEndSlp;
3522  clstr.EndSlpErr = clEndSlpErr;
3523  clstr.EndAng = std::atan(fScaleF * clEndSlp);
3524  clstr.EndWir = fHits[hit].WireID().Wire;
3525  clstr.EndTim = fHits[hit].PeakTime();
3526  clstr.EndChg = clEndChg;
3527  clstr.EndChgNear = clEndChgNear;
3528  clstr.StopCode = clStopCode;
3529  clstr.ProcCode = clProcCode;
3530  clstr.BeginVtx = -99;
3531  clstr.EndVtx = -99;
3532  clstr.CTP = EncodeCTP(tCST, tTPC, tPlane);
3533  clstr.tclhits = fcl2hits;
3534  tcl.push_back(clstr);
3535 
3536  return true;
3537  } // TmpStore()
3538 
3540  void ClusterCrawlerAlg::LACrawlUS()
3541  {
3542  // Crawl a large angle cluster upstream. Similar to CrawlUS but require
3543  // that a hit be added on each wire
3544 
3545  unsigned int dhit = fcl2hits[0];
3546  short dwir = fHits[dhit].WireID().Wire;
3547  clLA = true;
3548  prt = false;
3549  if (fDebugPlane == (short)plane && dwir == fDebugWire && fDebugHit > 0)
3550  prt = std::abs(fHits[dhit].PeakTime() - fDebugHit) < 40;
3551 
3552  if (prt) {
3553  mf::LogVerbatim myprt("CC");
3554  myprt << "******************* LACrawlUS PASS " << pass << " Hits ";
3555  for (unsigned short ii = 0; ii < fcl2hits.size(); ++ii) {
3556  unsigned int iht = fcl2hits[fcl2hits.size() - 1 - ii];
3557  myprt << fHits[iht].WireID().Wire << ":" << (int)fHits[iht].PeakTime() << " ";
3558  }
3559  }
3560 
3561  bool SigOK = true;
3562  bool HitOK = true;
3563  short nmissed = 0;
3564  // count the number of kinks encountered. Hits US of the kink are removed
3565  // and crawling continues unless another kink is encountered
3566  unsigned short kinkOnWire = 0;
3567  unsigned int it = fcl2hits.size() - 1;
3568  unsigned int lasthit = fcl2hits[it];
3569  unsigned int lastwire = fHits[lasthit].WireID().Wire;
3570  unsigned int prevHit, prevWire;
3571  bool ChkCharge = false;
3572  for (unsigned int nextwire = lastwire - 1; nextwire >= fFirstWire; --nextwire) {
3573  if (prt)
3574  mf::LogVerbatim("CC") << "LACrawlUS: next wire " << nextwire << " HitRange "
3575  << WireHitRange[nextwire].first;
3576  // stop crawling if there is a nearby vertex
3577  if (CrawlVtxChk(nextwire)) {
3578  if (prt) mf::LogVerbatim("CC") << "LACrawlUS: stop at vertex";
3579  clStopCode = 6;
3580  break;
3581  }
3582  // AddLAHit will merge the hit on nextwire if necessary
3583  AddLAHit(nextwire, ChkCharge, HitOK, SigOK);
3584  if (prt)
3585  mf::LogVerbatim("CC") << "LACrawlUS: HitOK " << HitOK << " SigOK " << SigOK
3586  << " nmissed SigOK " << nmissed << " cut " << fAllowNoHitWire;
3587  if (SigOK) nmissed = 0;
3588  if (!SigOK) {
3589  ++nmissed;
3590  if (nmissed > fAllowNoHitWire) {
3591  clStopCode = 1;
3592  break;
3593  }
3594  continue;
3595  }
3596  // If a hit was added after a gap, check to see if there is indeed
3597  // a wire signal in the gap
3598  if (HitOK) {
3599  prevHit = fcl2hits[fcl2hits.size() - 2];
3600  prevWire = fHits[prevHit].WireID().Wire;
3601  if (prevWire > nextwire + 2) {
3602  if (!ChkSignal(fcl2hits[fcl2hits.size() - 1], fcl2hits[fcl2hits.size() - 2])) {
3603  // no hit so trim the last hit and quit
3604  FclTrimUS(1);
3605  break;
3606  } // no signal
3607  } // prevWire > nextwire + 2
3608  } // HitOK
3609  // Merge all of the hit multiplets in the fcl2hits array into single
3610  // hits when enough hits have been found to call this a credible large
3611  // angle cluster. The last hit was already merged in AddHit
3612  if (fcl2hits.size() == 4) {
3613  bool didMerge;
3614  for (unsigned short kk = 0; kk < fcl2hits.size() - 1; ++kk) {
3615  unsigned int hit = fcl2hits[kk];
3616  if (mergeAvailable[hit]) MergeHits(hit, didMerge);
3617  }
3618  // update the fit
3619  FitCluster();
3620  clBeginSlp = clpar[1];
3621  // start checking the charge ratio when adding new hits
3622  ChkCharge = true;
3623  continue;
3624  } // fcl2hits.size() == 4
3625  unsigned short chsiz = chifits.size() - 1;
3626  // chsiz is fcl2hits.size() - 1...
3627  if (chsiz < 6) continue;
3628  if (fKinkChiRat[pass] <= 0) continue;
3629  if (chifits.size() != fcl2hits.size()) {
3630  mf::LogError("CC") << "LACrawlUS: chifits size error " << chifits.size() << " "
3631  << fcl2hits.size();
3632  return;
3633  }
3634  if (prt)
3635  mf::LogVerbatim("CC") << "Kink chk " << chifits[chsiz] << " " << chifits[chsiz - 1] << " "
3636  << chifits[chsiz - 2] << " " << chifits[chsiz - 3];
3637  if (chifits[chsiz - 1] > fKinkChiRat[pass] * chifits[chsiz - 2] &&
3638  chifits[chsiz] > fKinkChiRat[pass] * chifits[chsiz - 1]) {
3639  // find the kink angle (crudely) from the 0th and 2nd hit
3640  unsigned int ih0 = fcl2hits.size() - 1;
3641  unsigned int hit0 = fcl2hits[ih0];
3642  unsigned int ih2 = ih0 - 2;
3643  unsigned int hit2 = fcl2hits[ih2];
3644  float dt02 = fHits[hit2].PeakTime() - fHits[hit0].PeakTime();
3645  float dw02 = fHits[hit2].WireID().Wire - fHits[hit0].WireID().Wire;
3646  float th02 = std::atan(fScaleF * dt02 / dw02);
3647  // and the 3rd and 5th hit
3648  unsigned int ih3 = ih2 - 1;
3649  unsigned int hit3 = fcl2hits[ih3];
3650  unsigned int ih5 = ih3 - 2;
3651  unsigned int hit5 = fcl2hits[ih5];
3652  float dt35 = fHits[hit5].PeakTime() - fHits[hit3].PeakTime();
3653  float dw35 = fHits[hit5].WireID().Wire - fHits[hit3].WireID().Wire;
3654  float th35 = std::atan(fScaleF * dt35 / dw35);
3655  float dth = std::abs(th02 - th35);
3656  if (prt)
3657  mf::LogVerbatim("CC") << " Kink angle " << std::setprecision(3) << dth << " cut "
3658  << fKinkAngCut[pass];
3659  if (dth > fKinkAngCut[pass]) {
3660  // hit a kink. Lop of the first 3 hits, refit and keep crawling?
3661  FclTrimUS(3);
3662  FitCluster();
3663  // See if this is a second kink and it is close to the first
3664  // kink (which had hits removed).
3665  if (kinkOnWire > 0) {
3666  if (kinkOnWire - nextwire < 4) {
3667  if (prt)
3668  mf::LogVerbatim("CC")
3669  << "Hit a second kink. kinkOnWire = " << kinkOnWire << " Stopping";
3670  // set the kink stop code
3671  clStopCode = 3;
3672  break;
3673  }
3674  }
3675  kinkOnWire = nextwire;
3676  if (prt) mf::LogVerbatim("CC") << "Removed kink hits";
3677  } // kinkang check
3678  } // chifits test
3679  } // nextwire
3680 
3681  CheckClusterHitFrac(prt);
3682 
3683  clProcCode += 300;
3684  if (prt) mf::LogVerbatim("CC") << "LACrawlUS done. Nhits = " << fcl2hits.size();
3685  prt = false;
3686  } // LACrawlUS
3687 
3689  void ClusterCrawlerAlg::CrawlUS()
3690  {
3691  // Crawl along a trail of hits moving upstream
3692 
3693  if (fcl2hits.size() < 2) return;
3694 
3695  unsigned int dhit = fcl2hits[0];
3696  int dwir = fHits[dhit].WireID().Wire;
3697  clLA = false;
3698 
3699  prt = false;
3700  if (fDebugPlane == (short)plane && dwir == fDebugWire && fDebugHit > 0)
3701  prt = std::abs(fHits[dhit].PeakTime() - fDebugHit) < 20;
3702 
3703  if (prt) {
3704  mf::LogVerbatim myprt("CC");
3705  myprt << "******************* Start CrawlUS on pass " << pass << " Hits: ";
3706  for (unsigned short ii = 0; ii < fcl2hits.size(); ++ii) {
3707  unsigned int iht = fcl2hits[fcl2hits.size() - 1 - ii];
3708  myprt << fHits[iht].WireID().Wire << ":" << (int)fHits[iht].PeakTime() << " ";
3709  }
3710  myprt << "\n";
3711  }
3712 
3713  // SigOK = true if there is a ADC signal near the projected cluster position
3714  bool SigOK = true;
3715  bool HitOK = true;
3716  // count the number of missed hits on adjacent wires
3717  short nmissed = 0;
3718  // count the number of added hits after skipping
3719  short nHitAfterSkip = 0;
3720  bool DidaSkip = false;
3721  bool PostSkip = false;
3722  unsigned int it = fcl2hits.size() - 1;
3723  unsigned int lasthit = fcl2hits[it];
3724  if (lasthit > fHits.size() - 1) {
3725  mf::LogError("CC") << "CrawlUS bad lasthit " << lasthit;
3726  return;
3727  }
3728  unsigned int lastwire = fHits[lasthit].WireID().Wire;
3729  if (prt) mf::LogVerbatim("CC") << "CrawlUS: last wire " << lastwire << " hit " << lasthit;
3730 
3731  unsigned int lastWireWithSignal = lastwire;
3732  unsigned short nDroppedHit = 0;
3733 
3734  for (unsigned int nextwire = lastwire - 1; nextwire > fFirstWire; --nextwire) {
3735  if (prt)
3736  mf::LogVerbatim("CC") << "CrawlUS: next wire " << nextwire << " HitRange "
3737  << WireHitRange[nextwire].first;
3738  // stop crawling if there is a nearby vertex
3739  if (CrawlVtxChk(nextwire)) {
3740  if (prt) mf::LogVerbatim("CC") << "CrawlUS: stop at vertex";
3741  clStopCode = 6;
3742  break;
3743  }
3744  // Switch to large angle crawling?
3745  if (std::abs(clpar[1]) > fLAClusSlopeCut) {
3746  if (prt) mf::LogVerbatim("CC") << ">>>>> CrawlUS: Switching to LACrawlUS";
3747  LACrawlUS();
3748  return;
3749  }
3750  // add hits and check for PH and width consistency
3751  AddHit(nextwire, HitOK, SigOK);
3752  if (prt)
3753  mf::LogVerbatim("CC") << "CrawlUS: HitOK " << HitOK << " SigOK " << SigOK << " nmissed "
3754  << nmissed;
3755  if (SigOK) lastWireWithSignal = nextwire;
3756  if (!HitOK) {
3757  // no hit on this wire. Was there a signal or dead wire?
3758  if (SigOK) {
3759  // no hit on the wire but there is a signal
3760  ++nmissed;
3761  // see if we are in the PostSkip phase and missed more than 1 wire
3762  if (PostSkip && nmissed > fMinWirAfterSkip[pass]) {
3763  // cluster is really short
3764  if ((int)(fcl2hits.size() - nHitAfterSkip) < 4) {
3765  fcl2hits.clear();
3766  return;
3767  }
3768  if (prt) mf::LogVerbatim("CC") << " PostSkip && nmissed = " << nmissed;
3769  clStopCode = 2;
3770  FclTrimUS(nHitAfterSkip);
3771  FitCluster();
3772  if (clChisq > 90.) {
3773  fcl2hits.clear();
3774  return;
3775  }
3776  FitCluster();
3777  return;
3778  } // PostSkip && nmissed >
3779  if (nmissed > 1) {
3780  DidaSkip = true;
3781  PostSkip = false;
3782  }
3783  } // SigOK
3784  else {
3785  // SigOK is false
3786  clStopCode = 0;
3787  lasthit = fcl2hits[fcl2hits.size() - 1];
3788  if ((lastWireWithSignal - nextwire) > fAllowNoHitWire) {
3789  if (prt)
3790  mf::LogVerbatim("CC")
3791  << "No hit or signal on wire " << nextwire << " last wire with signal "
3792  << lastWireWithSignal << " exceeding fAllowNoHitWire " << fAllowNoHitWire
3793  << " Break!";
3794  break;
3795  }
3796  } // else SigOK false
3797  } // !HitOK
3798  else {
3799  if (prt)
3800  mf::LogVerbatim("CC") << " CrawlUS check clChisq " << clChisq << " cut " << fChiCut[pass];
3801  if (clChisq > fChiCut[pass]) {
3802  if (fcl2hits.size() < 3) {
3803  fcl2hits.clear();
3804  return;
3805  }
3806  // a fit error occurred. Lop off the leading hit and refit
3807  if (prt) mf::LogVerbatim("CC") << "ADD- Bad clChisq " << clChisq << " dropping hit";
3808  FclTrimUS(1);
3809  FitCluster();
3810  ++nDroppedHit;
3811  if (nDroppedHit > 4) {
3812  if (prt)
3813  mf::LogVerbatim("CC")
3814  << " Too many dropped hits: " << nDroppedHit << " Stop crawling";
3815  break;
3816  } // too many dropped hits
3817  if (clChisq > fChiCut[pass]) {
3818  if (prt)
3819  mf::LogVerbatim("CC")
3820  << " Bad clChisq " << clChisq << " after dropping hit. Stop crawling";
3821  break;
3822  }
3823  FitClusterChg();
3824  continue;
3825  } // clChisq > fChiCut[0]
3826  // monitor the onset of a kink. Look for a progressive increase
3827  // in chisq for the previous 0 - 2 hits.
3828  if (chifits.size() > 5 && fKinkChiRat[pass] > 0) {
3829  if (chifits.size() != fcl2hits.size()) {
3830  mf::LogError("CC") << "CrawlUS: chifits size error " << chifits.size() << " "
3831  << fcl2hits.size();
3832  return;
3833  }
3834  unsigned short chsiz = chifits.size() - 1;
3835  if (prt)
3836  mf::LogVerbatim("CC") << "Kink chk " << chifits[chsiz] << " " << chifits[chsiz - 1]
3837  << " " << chifits[chsiz - 2] << " " << chifits[chsiz - 3];
3838  if (chifits[chsiz - 1] > fKinkChiRat[pass] * chifits[chsiz - 2] &&
3839  chifits[chsiz] > fKinkChiRat[pass] * chifits[chsiz - 1]) {
3840  if (fcl2hits.size() != chifits.size()) {
3841  mf::LogError("CC") << "bad kink check size " << chifits.size() << " "
3842  << fcl2hits.size() << " plane " << plane << " cluster " << dwir
3843  << ":" << dhit;
3844  continue;
3845  }
3846  if (EndKinkAngle() > fKinkAngCut[pass]) {
3847  if (prt)
3848  mf::LogVerbatim("CC")
3849  << "******************* Stopped tracking - kink angle " << EndKinkAngle() << " > "
3850  << fKinkAngCut[pass] << " Removing 3 hits";
3851  // kill the last 3 hits and refit
3852  FclTrimUS(3);
3853  FitCluster();
3854  FitClusterChg();
3855  } // kinkang check
3856  } // chifits check
3857  } // chifits.size() > 5
3858  // done with kink check
3859  // update the cluster Begin information?
3860  if (fcl2hits.size() == fMaxHitsFit[pass] || fcl2hits.size() == fMinHits[pass]) {
3861  clBeginSlp = clpar[1];
3862  clBeginSlpErr = clparerr[1];
3863  }
3864  // define the begin cluster charge if it's not defined yet
3865  if (clBeginChg <= 0 && fAveChg > 0) {
3866  clBeginChg = fAveChg;
3867  if (prt) mf::LogVerbatim("CC") << " Set clBeginChg " << clBeginChg;
3868  }
3869  // reset nmissed
3870  nmissed = 0;
3871  // start counting hits added after skipping
3872  if (DidaSkip) {
3873  // start PostSkip phase
3874  PostSkip = true;
3875  DidaSkip = false;
3876  nHitAfterSkip = 0;
3877  } // DidaSkip
3878  // check for PostSkip phase
3879  if (PostSkip) {
3880  // end the PostSkip phase if there are enough hits
3881  ++nHitAfterSkip;
3882  if (nHitAfterSkip == fMinWirAfterSkip[pass]) PostSkip = false;
3883  }
3884  // check for bad chisq
3885  if (clChisq > fChiCut[pass]) {
3886  if (prt) mf::LogVerbatim("CC") << "<<ADD- Bad chisq " << clChisq;
3887  // remove the last few hits if there is a systematic increase in chisq and re-fit
3888  float chirat;
3889  unsigned short lopped = 0;
3890  for (unsigned short nlop = 0; nlop < 4; ++nlop) {
3891  unsigned short cfsize = chifits.size() - 1;
3892  chirat = chifits[cfsize] / chifits[cfsize - 1];
3893  if (prt)
3894  mf::LogVerbatim("CC")
3895  << "chirat " << chirat << " last hit " << fcl2hits[fcl2hits.size() - 1];
3896  if (chirat < 1.2) break;
3897  if (prt) mf::LogVerbatim("CC") << "<<ADD- Bad chisq. Bad chirat " << chirat;
3898  FclTrimUS(1);
3899  ++lopped;
3900  if (fcl2hits.size() < 6) break;
3901  if (chifits.size() < 6) break;
3902  } // nlop
3903  if (fcl2hits.size() < 6) {
3904  clStopCode = 4;
3905  if (prt) mf::LogVerbatim("CC") << "Bad fit chisq - short cluster. Break";
3906  break;
3907  }
3908  if (lopped == 0 && fcl2hits.size() > 5) {
3909  if (prt) mf::LogVerbatim("CC") << "<<ADD- Bad chisq. remove 1 hit";
3910  FclTrimUS(1);
3911  ++lopped;
3912  }
3913  FitCluster();
3914  FitClusterChg();
3915  if (prt)
3916  mf::LogVerbatim("CC") << "Bad fit chisq - removed " << lopped << " hits. Continue";
3917  } // clChisq > fChiCut[pass]
3918  } // !HitOK check
3919  } // nextwire
3920  if (prt)
3921  mf::LogVerbatim("CC") << "******************* CrawlUS normal stop. size " << fcl2hits.size();
3922 
3923  bool reFit = false;
3924  // end kink angle check
3925  if (fcl2hits.size() > 5) {
3926  // check for a kink at the US end
3927  if (prt)
3928  mf::LogVerbatim("CC") << "EndKinkAngle check " << EndKinkAngle() << " cut "
3929  << fKinkAngCut[pass];
3930  if (EndKinkAngle() > fKinkAngCut[pass]) {
3931  if (prt) mf::LogVerbatim("CC") << "EndKinkAngle removes 3 hits ";
3932  FclTrimUS(3);
3933  reFit = true;
3934  }
3935  } // fcl2hits.size() > 5
3936 
3937  // count the number of hits on adjacent wires at the leading edge and
3938  // ensure that the count is consistent with fMinWirAfterSkip
3939  if ((unsigned short)fcl2hits.size() > fMinWirAfterSkip[pass] + 3) {
3940  unsigned int ih0 = fcl2hits.size() - 1;
3941  unsigned int hit0 = fcl2hits[ih0];
3942  unsigned int uswir = fHits[hit0].WireID().Wire;
3943  unsigned int nxtwir;
3944  unsigned short nAdjHit = 0;
3945  for (unsigned short ii = ih0 - 1; ii > 0; --ii) {
3946  nxtwir = fHits[fcl2hits[ii]].WireID().Wire;
3947  ++nAdjHit;
3948  if (nxtwir != uswir + 1) break;
3949  // break if there are enough hits
3950  if (nAdjHit == fMinWirAfterSkip[pass]) break;
3951  uswir = nxtwir;
3952  } // ii
3953  // lop off hits?
3954  if (nAdjHit < fMinWirAfterSkip[pass]) {
3955  if (prt) mf::LogVerbatim("CC") << "fMinWirAfterSkip removes " << nAdjHit << " hits ";
3956  FclTrimUS(nAdjHit);
3957  reFit = true;
3958  }
3959  } // fcl2hits.size() > fMinWirAfterSkip[pass] + 3
3960 
3961  // check for a bad hit on the end
3962  if (!reFit && fcl2hits.size() > 3) {
3963  float chirat = chifits[chifits.size() - 1] / chifits[chifits.size() - 2];
3964  if (prt)
3965  mf::LogVerbatim("CC") << "Last hit chirat " << chirat << " cut " << fKinkChiRat[pass];
3966  if (prt)
3967  mf::LogVerbatim("CC") << "Check " << clChisq << " " << chifits[chifits.size() - 1] << " "
3968  << chifits[chifits.size() - 2];
3969  if (chirat > fKinkChiRat[pass]) {
3970  if (prt) mf::LogVerbatim("CC") << "<<ADD- at end";
3971  FclTrimUS(1);
3972  reFit = true;
3973  }
3974  } // !reFit
3975 
3976  if (reFit) {
3977  FitCluster();
3978  FitClusterChg();
3979  }
3980  CheckClusterHitFrac(prt);
3981  if (prt)
3982  mf::LogVerbatim("CC") << "******************* CrawlUS done. Size " << fcl2hits.size()
3983  << " min length for this pass " << fMinHits[pass];
3984 
3985  prt = false;
3986  } // CrawlUS()
3987 
3989  float ClusterCrawlerAlg::EndKinkAngle()
3990  {
3991  // find the kink angle (crudely) from the 0th and 2nd hit on the cluster under construction
3992 
3993  unsigned int ih0 = fcl2hits.size() - 1;
3994  unsigned int hit0 = fcl2hits[ih0];
3995  unsigned int ih2 = ih0 - 2;
3996  unsigned int hit2 = fcl2hits[ih2];
3997  float dt02 = fHits[hit2].PeakTime() - fHits[hit0].PeakTime();
3998  float dw02 = fHits[hit2].WireID().Wire - fHits[hit0].WireID().Wire;
3999  float th02 = std::atan(fScaleF * dt02 / dw02);
4000  // and the 3rd and 5th hit
4001  unsigned int ih3 = ih2 - 1;
4002  unsigned int hit3 = fcl2hits[ih3];
4003  unsigned int ih5 = ih3 - 2;
4004  unsigned int hit5 = fcl2hits[ih5];
4005  float dt35 = fHits[hit5].PeakTime() - fHits[hit3].PeakTime();
4006  float dw35 = fHits[hit5].WireID().Wire - fHits[hit3].WireID().Wire;
4007  float th35 = std::atan(fScaleF * dt35 / dw35);
4008  return std::abs(th02 - th35);
4009  }
4010 
4012  bool ClusterCrawlerAlg::ChkMergedClusterHitFrac(unsigned short it1, unsigned short it2)
4013  {
4014  // This routine is called before two tcl clusters, it1 and it2, are slated to be
4015  // merged to see if they will satisfy the minimum hit fraction criterion
4016  if (it1 > tcl.size() - 1) return false;
4017  if (it2 > tcl.size() - 1) return false;
4018  unsigned int eWire = 99999;
4019  unsigned int bWire = 0, wire;
4020  if (tcl[it1].BeginWir > bWire) bWire = tcl[it1].BeginWir;
4021  if (tcl[it2].BeginWir > bWire) bWire = tcl[it2].BeginWir;
4022  if (tcl[it1].EndWir < eWire) eWire = tcl[it1].EndWir;
4023  if (tcl[it2].EndWir < eWire) eWire = tcl[it2].EndWir;
4024  unsigned int mergedClusterLength = bWire - eWire + 1;
4025  // define a vector of size = length of the wire range
4026  std::vector<bool> cHits(mergedClusterLength, false);
4027  // set the elements true if there is a hit
4028  unsigned int ii, iht, indx;
4029  for (ii = 0; ii < tcl[it1].tclhits.size(); ++ii) {
4030  iht = tcl[it1].tclhits[ii];
4031  if (iht > fHits.size() - 1) {
4032  mf::LogError("CC") << "ChkMergedClusterHitFrac bad iht ";
4033  return false;
4034  }
4035  indx = fHits[iht].WireID().Wire - eWire;
4036  cHits[indx] = true;
4037  } // ii
4038  for (ii = 0; ii < tcl[it2].tclhits.size(); ++ii) {
4039  iht = tcl[it2].tclhits[ii];
4040  if (iht > fHits.size() - 1) {
4041  mf::LogError("CC") << "ChkMergedClusterHitFrac bad iht ";
4042  return false;
4043  }
4044  indx = fHits[iht].WireID().Wire - eWire;
4045  cHits[indx] = true;
4046  } // ii
4047  // set cHits true if the wire is dead
4048  for (ii = 0; ii < cHits.size(); ++ii) {
4049  wire = eWire + ii;
4050  if (WireHitRange[wire].first == -1) cHits[ii] = true;
4051  }
4052  // count the number of wires with hits
4053  float nhits = std::count(cHits.begin(), cHits.end(), true);
4054  float hitFrac = nhits / (float)cHits.size();
4055  return (hitFrac > fMinHitFrac);
4056  } // ChkMergedClusterHitFrac
4057 
4059  void ClusterCrawlerAlg::CheckClusterHitFrac(bool prt)
4060  {
4061 
4062  // Find the fraction of the wires on the cluster that have
4063  // hits.
4064  unsigned int iht = fcl2hits[fcl2hits.size() - 1];
4065  clEndWir = fHits[iht].WireID().Wire;
4066  clBeginWir = fHits[fcl2hits[0]].WireID().Wire;
4067  float hitFrac = (float)(fcl2hits.size() + DeadWireCount()) / (float)(clBeginWir - clEndWir + 1);
4068 
4069  if (hitFrac < fMinHitFrac) {
4070  if (prt)
4071  mf::LogVerbatim("CC") << "CheckClusterHitFrac: Poor hit fraction " << hitFrac
4072  << " clBeginWir " << clBeginWir << " clEndWir " << clEndWir
4073  << " size " << fcl2hits.size() << " DeadWireCount "
4074  << DeadWireCount();
4075  fcl2hits.clear();
4076  return;
4077  } // hitFrac
4078 
4079  /* TODO: Does this make sense?
4080  // lop off the last hit if it is part of a hit multiplet
4081  if(fHits[iht].Multiplicity() > 1) {
4082  fcl2hits.resize(fcl2hits.size() - 1);
4083  }
4084 */
4085  // check for short track ghosts
4086  if (fcl2hits.size() < 5) {
4087  unsigned short nsing = 0;
4088  for (unsigned short iht = 0; iht < fcl2hits.size(); ++iht)
4089  if (fHits[fcl2hits[iht]].Multiplicity() == 1) ++nsing;
4090  hitFrac = (float)nsing / (float)fcl2hits.size();
4091  if (hitFrac < fMinHitFrac) {
4092  fcl2hits.clear();
4093  if (prt)
4094  mf::LogVerbatim("CC") << "CheckClusterHitFrac: Poor short track hit fraction " << hitFrac;
4095  return;
4096  } // hitFrac
4097  } // short ghost track check
4098 
4099  // analyze the pattern of nearby charge
4100  // will need the cluster charge so calculate it here if it isn't defined yet
4101  if (clBeginChg <= 0) {
4102  unsigned int iht, nht = 0;
4103  for (unsigned short ii = 0; ii < fcl2hits.size(); ++ii) {
4104  iht = fcl2hits[ii];
4105  clBeginChg += fHits[iht].Integral();
4106  ++nht;
4107  if (nht == 8) break;
4108  }
4109  clBeginChg /= (float)nht;
4110  } // clBeginChg == 0
4111  // handle short vs long clusters
4112  unsigned short cnt = chgNear.size() / 2;
4113  // get the average charge from <= 30 hits at each end
4114  if (chgNear.size() > 60) cnt = 30;
4115  clBeginChgNear = 0;
4116  clEndChgNear = 0;
4117  for (unsigned short ids = 0; ids < cnt; ++ids) {
4118  clBeginChgNear += chgNear[ids];
4119  clEndChgNear += chgNear[chgNear.size() - 1 - ids];
4120  }
4121  clBeginChgNear /= (float)cnt;
4122  clEndChgNear /= (float)cnt;
4123 
4124  } // CheckClusterHitFrac()
4125 
4127  void ClusterCrawlerAlg::FitClusterMid(unsigned short it1, unsigned int ihtin, short nhit)
4128  {
4129  FitClusterMid(tcl[it1].tclhits, ihtin, nhit);
4130  } // FitClusterMid
4131 
4133  void ClusterCrawlerAlg::FitClusterMid(std::vector<unsigned int>& hitVec,
4134  unsigned int ihtin,
4135  short nhit)
4136  {
4137  // Fits hits on temp cluster it1 to a line starting at hit ihtin and including
4138  // nhit hits incrementing towards the hit vector End when nhit > 0 and
4139  // decrementing towards the hit vector Begin when nhit < 0.
4140  // The fit params are stashed in the clpar and clparerr arrays.
4141  // fAveChg is re-calculated as well.
4142 
4143  // set chisq bad in case something doesn't work out
4144  clChisq = 99.;
4145 
4146  if (hitVec.size() < 3) return;
4147 
4148  std::vector<float> xwir;
4149  std::vector<float> ytim;
4150  std::vector<float> ytimerr2;
4151 
4152  unsigned short ii, hitcnt = 0, nht = 0, usnhit;
4153  float wire0 = 0;
4154  unsigned int iht;
4155  bool UseEm = false;
4156  fAveChg = 0.;
4157  fChgSlp = 0.;
4158 
4159  if (nhit > 0) {
4160  usnhit = nhit;
4161  // find the first desired hit and move towards the End
4162  for (ii = 0; ii < hitVec.size(); ++ii) {
4163  iht = hitVec[ii];
4164  if (iht > fHits.size() - 1) {
4165  mf::LogError("CC") << "FitClusterMid bad iht " << iht;
4166  return;
4167  }
4168  // look for the desired first hit. Use this as the origin wire
4169  if (iht == ihtin) {
4170  UseEm = true;
4171  wire0 = fHits[iht].WireID().Wire;
4172  }
4173  // use hits after finding the first desired hit
4174  if (UseEm) {
4175  xwir.push_back((float)fHits[iht].WireID().Wire - wire0);
4176  ytim.push_back(fHits[iht].PeakTime());
4177  // pass the error^2 to the fitter
4178  float terr = fHitErrFac * fHits[iht].RMS();
4179  ytimerr2.push_back(terr * terr);
4180  fAveChg += fHits[iht].Integral();
4181  ++hitcnt;
4182  if (hitcnt == usnhit) break;
4183  }
4184  }
4185  nht = hitcnt;
4186  }
4187  else {
4188  usnhit = -nhit;
4189  // find the first desired hit and move towards the Begin
4190  for (auto ii = hitVec.crbegin(); ii != hitVec.crend(); ++ii) {
4191  iht = *ii;
4192  if (iht > fHits.size() - 1) {
4193  mf::LogVerbatim("CC") << "FitClusterMid bad iht " << iht;
4194  return;
4195  }
4196  // look for the desired first hit. Use this as the origin wire
4197  if (iht == ihtin) {
4198  UseEm = true;
4199  wire0 = fHits[iht].WireID().Wire;
4200  }
4201  // use hits after finding the first desired hit
4202  if (UseEm) {
4203  xwir.push_back((float)fHits[iht].WireID().Wire - wire0);
4204  ytim.push_back(fHits[iht].PeakTime());
4205  float terr = fHitErrFac * fHits[iht].RMS();
4206  ytimerr2.push_back(terr * terr);
4207  fAveChg += fHits[iht].Integral();
4208  ++hitcnt;
4209  if (hitcnt == usnhit) break;
4210  }
4211  }
4212  nht = hitcnt;
4213  }
4214 
4215  if (nht < 2) return;
4216  fAveChg = fAveChg / (float)nht;
4217  fChgSlp = 0.;
4218 
4219  float intcpt = 0.;
4220  float slope = 0.;
4221  float intcpterr = 0.;
4222  float slopeerr = 0.;
4223  float chidof = 0.;
4224  fLinFitAlg.LinFit(xwir, ytim, ytimerr2, intcpt, slope, intcpterr, slopeerr, chidof);
4225  clChisq = chidof;
4226  if (clChisq > fChiCut[0]) return;
4227  clpar[0] = intcpt;
4228  clpar[1] = slope;
4229  clpar[2] = wire0;
4230  clparerr[0] = intcpterr;
4231  clparerr[1] = slopeerr;
4232  }
4233 
4235  void ClusterCrawlerAlg::FitCluster()
4236  {
4237  // Fits the hits on the US end of a cluster. This routine assumes that
4238  // wires are numbered from lower (upstream) to higher (downstream) and
4239  // that the hits in the fclhits vector are sorted so that upstream hits
4240  // are at the end of the vector
4241 
4242  clChisq = 999.;
4243 
4244  if (pass > fNumPass - 1) {
4245  mf::LogError("CC") << "FitCluster called on invalid pass " << pass;
4246  return;
4247  }
4248 
4249  unsigned short ii, nht = 0;
4250  // fit all hits or truncate?
4251  nht = fcl2hits.size();
4252  if (clLA) {
4253  // Fit large angle cluster
4254  if (nht > fLAClusMaxHitsFit) nht = fLAClusMaxHitsFit;
4255  }
4256  else {
4257  if (nht > fMaxHitsFit[pass]) nht = fMaxHitsFit[pass];
4258  }
4259  if (nht < 2) return;
4260 
4261  std::vector<float> xwir;
4262  std::vector<float> ytim;
4263  std::vector<float> ytimerr2;
4264  // apply an angle dependent scale factor.
4265  float angfactor = AngleFactor(clpar[1]);
4266 
4267  unsigned int wire;
4268  unsigned int wire0 = fHits[fcl2hits[fcl2hits.size() - 1]].WireID().Wire;
4269  unsigned int ihit;
4270  float terr, qave = 0, qwt;
4271  for (ii = 0; ii < nht; ++ii) {
4272  ihit = fcl2hits[fcl2hits.size() - 1 - ii];
4273  qave += fHits[ihit].Integral();
4274  } // ii
4275  qave /= (float)nht;
4276  for (ii = 0; ii < nht; ++ii) {
4277  ihit = fcl2hits[fcl2hits.size() - 1 - ii];
4278  wire = fHits[ihit].WireID().Wire;
4279  xwir.push_back(wire - wire0);
4280  ytim.push_back(fHits[ihit].PeakTime());
4281  // Scale error by hit multiplicity to account for bias in hit
4282  // multiplet fitting
4283  terr = fHitErrFac * fHits[ihit].RMS() * fHits[ihit].Multiplicity();
4284  if (fAveChg > 0) {
4285  // increase the error for large charge hits
4286  qwt = (fHits[ihit].Integral() / qave) - 1;
4287  if (qwt < 1) qwt = 1;
4288  terr *= qwt;
4289  }
4290  if (terr < 0.01) terr = 0.01;
4291  ytimerr2.push_back(angfactor * terr * terr);
4292  }
4293  CalculateAveHitWidth();
4294  if (prt) {
4295  mf::LogVerbatim myprt("CC");
4296  myprt << "FitCluster W:T ";
4297  unsigned short cnt = 0;
4298  for (std::vector<unsigned int>::reverse_iterator it = fcl2hits.rbegin();
4299  it != fcl2hits.rend();
4300  ++it) {
4301  unsigned int ihit = *it;
4302  unsigned short wire = fHits[ihit].WireID().Wire;
4303  myprt << wire << ":" << (short)fHits[ihit].PeakTime() << " ";
4304  ++cnt;
4305  if (cnt == 8) {
4306  myprt << " .... ";
4307  break;
4308  }
4309  }
4310  } // prt
4311 
4312  if (xwir.size() < 2) return;
4313 
4314  float intcpt = 0.;
4315  float slope = 0.;
4316  float intcpterr = 0.;
4317  float slopeerr = 0.;
4318  float chidof = 0.;
4319  fLinFitAlg.LinFit(xwir, ytim, ytimerr2, intcpt, slope, intcpterr, slopeerr, chidof);
4320  clChisq = chidof;
4321  if (chidof > fChiCut[0]) return;
4322  clpar[0] = intcpt;
4323  clpar[1] = slope;
4324  clpar[2] = wire0;
4325  clparerr[0] = intcpterr;
4326  clparerr[1] = slopeerr;
4327 
4328  if (prt)
4329  mf::LogVerbatim("CC") << "nht " << nht << " fitpar " << (int)clpar[0] << "+/-"
4330  << (int)intcpterr << " " << clpar[1] << "+/-" << slopeerr << " clChisq "
4331  << clChisq;
4332  }
4334  float ClusterCrawlerAlg::AngleFactor(float slope)
4335  {
4336  // returns an angle dependent cluster projection error factor for fitting
4337  // and hit finding
4338 
4339  float slp = std::abs(slope);
4340  if (slp > 15) slp = 15;
4341  // return a value between 1 and 4
4342  float angfac = 1 + 0.03 * slp * slp;
4343  return angfac;
4344  }
4345 
4347  void ClusterCrawlerAlg::CalculateAveHitWidth()
4348  {
4349  fAveHitWidth = 0;
4350  for (unsigned short ii = 0; ii < fcl2hits.size(); ++ii)
4351  fAveHitWidth += fHits[fcl2hits[ii]].EndTick() - fHits[fcl2hits[ii]].StartTick();
4352  fAveHitWidth /= (float)fcl2hits.size();
4353  } // CalculateAveHitWidth
4354 
4356  void ClusterCrawlerAlg::FitClusterChg()
4357  {
4358  // Fits the charge of hits on the fcl2hits vector to a line, or simply
4359  // uses the average of 1 or 2 hits as determined by NHitsAve
4360 
4361  if (fcl2hits.size() == 0) return;
4362  unsigned int ih0 = fcl2hits.size() - 1;
4363 
4364  if (pass >= fNumPass) {
4365  mf::LogError("CC") << "FitClusterChg bad pass " << pass;
4366  return;
4367  }
4368 
4369  // Handle Large Angle clusters
4370  if (clLA) {
4371  // simple average of the charge (and the hit width
4372  // while we are here)
4373  unsigned short nhave = fLAClusMaxHitsFit;
4374  if (nhave > fcl2hits.size()) nhave = fcl2hits.size();
4375  fAveChg = 0;
4376  fChgSlp = 0;
4377  fAveHitWidth = 0;
4378  unsigned int iht;
4379  for (unsigned short ii = 0; ii < nhave; ++ii) {
4380  iht = fcl2hits[fcl2hits.size() - 1 - ii];
4381  fAveChg += fHits[iht].Integral();
4382  fAveHitWidth += (fHits[iht].EndTick() - fHits[iht].StartTick());
4383  } // ii
4384  fAveChg /= (float)fcl2hits.size();
4385  fAveHitWidth /= (float)fcl2hits.size();
4386  return;
4387  } // clLA
4388 
4389  // number of hits at the leading edge that we will fit
4390  unsigned short fitLen = fNHitsAve[pass];
4391  // start fitting charge when there are at least 6 hits if we are tracking
4392  // long clusters
4393  if (fitLen > 5 && // Fit 6 hits when tracking long clusters AND
4394  fcl2hits.size() > 5 && // there are at least 6 hits AND
4395  fcl2hits.size() < fitLen) // there are less than fNHitsAve[pass]
4396  fitLen = 5;
4397 
4398  // don't find the average charge --> no charge cut is made
4399  if (fNHitsAve[pass] < 1) return;
4400 
4401  if (fNHitsAve[pass] == 1) {
4402  // simply use the charge and width the last hit
4403  fAveChg = fHits[fcl2hits[ih0]].Integral();
4404  fChgSlp = 0.;
4405  }
4406  else if (fNHitsAve[pass] == 2) {
4407  // average the last two points if requested
4408  fAveChg = (fHits[fcl2hits[ih0]].Integral() + fHits[fcl2hits[ih0 - 1]].Integral()) / 2.;
4409  fChgSlp = 0.;
4410  }
4411  else if ((unsigned short)fcl2hits.size() > fitLen) {
4412  // do a real fit
4413  std::vector<float> xwir;
4414  std::vector<float> ychg;
4415  std::vector<float> ychgerr2;
4416  // origin of the fit
4417  unsigned int wire0 = fHits[fcl2hits[fcl2hits.size() - 1]].WireID().Wire;
4418  // find the mean and rms of the charge
4419  unsigned short npt = 0;
4420  unsigned short imlast = 0;
4421  float ave = 0.;
4422  float rms = 0.;
4423  // this loop intentionally ignores the Begin hit
4424  for (unsigned int ii = fcl2hits.size() - 1; ii > 0; --ii) {
4425  ++npt;
4426  float chg = fHits[fcl2hits[ii]].Integral();
4427  ave += chg;
4428  rms += chg * chg;
4429  if (npt == fitLen) {
4430  imlast = ii;
4431  break;
4432  }
4433  }
4434  float fnpt = npt;
4435  ave /= fnpt;
4436  rms = std::sqrt((rms - fnpt * ave * ave) / (fnpt - 1));
4437  float chgcut = ave + rms;
4438  float chg;
4439  unsigned int wire;
4440  for (unsigned short ii = fcl2hits.size() - 1; ii > imlast; --ii) {
4441  wire = fHits[fcl2hits[ii]].WireID().Wire;
4442  chg = fHits[fcl2hits[ii]].Integral();
4443  if (chg > chgcut) continue;
4444  xwir.push_back((float)(wire - wire0));
4445  ychg.push_back(chg);
4446  ychgerr2.push_back(chg);
4447  }
4448  if (ychg.size() < 3) return;
4449  float intcpt;
4450  float slope;
4451  float intcpterr;
4452  float slopeerr;
4453  float chidof;
4454  fLinFitAlg.LinFit(xwir, ychg, ychgerr2, intcpt, slope, intcpterr, slopeerr, chidof);
4455  if (prt)
4456  mf::LogVerbatim("CC") << "FitClusterChg wire " << wire0 << " chidof " << (int)chidof
4457  << " npt " << xwir.size() << " charge = " << (int)intcpt
4458  << " slope = " << (int)slope << " first ave " << (int)ave << " rms "
4459  << (int)rms;
4460  if (chidof > 100.) return;
4461  // fit must have gone wrong if the fStepCrawlChgRatCut average is greater than
4462  // the average using all points
4463  if (intcpt > ave) return;
4464  // ensure that change does not exceed 30%
4465  if (fAveChg > 0) {
4466  ave = intcpt / fAveChg;
4467  if (ave > 1.3) return;
4468  if (ave < 0.77) return;
4469  }
4470  fAveChg = intcpt;
4471  fChgSlp = slope;
4472  }
4473  } // fitchg
4474 
4476  void ClusterCrawlerAlg::AddLAHit(unsigned int kwire, bool& ChkCharge, bool& HitOK, bool& SigOK)
4477  {
4478  // A variant of AddHit for large angle clusters
4479 
4480  SigOK = false;
4481  HitOK = false;
4482 
4483  // not in the range of wires with hits
4484  if (kwire < fFirstWire || kwire > fLastWire) return;
4485 
4486  if (fcl2hits.size() == 0) return;
4487 
4488  // skip bad wire and assume the track was there
4489  if (WireHitRange[kwire].first == -1) {
4490  SigOK = true;
4491  return;
4492  }
4493  // return SigOK false if no hit on a good wire
4494  if (WireHitRange[kwire].first == -2) return;
4495 
4496  unsigned int firsthit = WireHitRange[kwire].first;
4497  unsigned int lasthit = WireHitRange[kwire].second;
4498 
4499  // max allowable time difference between projected cluster and a hit
4500  float timeDiff = 40 * AngleFactor(clpar[1]);
4501  float dtime;
4502 
4503  // the last hit added to the cluster
4504  unsigned int lastClHit = UINT_MAX;
4505  if (fcl2hits.size() > 0) {
4506  lastClHit = fcl2hits[fcl2hits.size() - 1];
4507  if (lastClHit == 0) {
4508  fAveChg = fHits[lastClHit].Integral();
4509  fAveHitWidth = fHits[lastClHit].EndTick() - fHits[lastClHit].StartTick();
4510  }
4511  } // fcl2hits.size() > 0
4512 
4513  // the projected time of the cluster on this wire
4514  float prtime = clpar[0] + ((float)kwire - clpar[2]) * clpar[1];
4515  float chgWinLo = prtime - fChgNearWindow;
4516  float chgWinHi = prtime + fChgNearWindow;
4517  float chgrat, hitWidth;
4518  float hitWidthCut = 0.5 * fAveHitWidth;
4519  float cnear = 0;
4520  // float fom;
4521  if (prt)
4522  mf::LogVerbatim("CC") << "AddLAHit: wire " << kwire << " prtime " << prtime
4523  << " max timeDiff " << timeDiff << " fAveChg " << fAveChg;
4524  unsigned int imbest = 0;
4525  unsigned int khit;
4526  for (khit = firsthit; khit < lasthit; ++khit) {
4527  // obsolete hit?
4528  if (inClus[khit] < 0) continue;
4529  dtime = std::abs(fHits[khit].PeakTime() - prtime);
4530  hitWidth = fHits[khit].EndTick() - fHits[khit].StartTick();
4531  chgrat = 1;
4532  if (ChkCharge && fAveChg > 0) { chgrat = fHits[khit].Integral() / fAveChg; }
4533  if (prt)
4534  mf::LogVerbatim("CC") << " Chk W:T " << kwire << ":" << (short)fHits[khit].PeakTime()
4535  << " dT " << std::fixed << std::setprecision(1) << dtime << " InClus "
4536  << inClus[khit] << " mult " << fHits[khit].Multiplicity() << " width "
4537  << (int)hitWidth << " MergeAvail " << mergeAvailable[khit] << " Chi2 "
4538  << std::fixed << std::setprecision(2) << fHits[khit].GoodnessOfFit()
4539  << " Charge " << (int)fHits[khit].Integral() << " chgrat "
4540  << std::fixed << std::setprecision(1) << chgrat << " index " << khit;
4541  // count charge in the window
4542  if (fHits[khit].PeakTime() > chgWinLo && fHits[khit].PeakTime() < chgWinHi)
4543  cnear += fHits[khit].Integral();
4544  // projected time outside the Signal time window?
4545  if (prtime < fHits[khit].StartTick() - timeDiff) continue;
4546  if (prtime > fHits[khit].EndTick() + timeDiff) continue;
4547  SigOK = true;
4548  // hit used?
4549  if (inClus[khit] > 0) continue;
4550  // ignore narrow width hits
4551  if (hitWidth < hitWidthCut) continue;
4552  // ignore very low charge hits
4553  if (chgrat < 0.1) continue;
4554  if (dtime < timeDiff) {
4555  HitOK = true;
4556  imbest = khit;
4557  timeDiff = dtime;
4558  }
4559  } // khit
4560 
4561  if (prt && !HitOK) mf::LogVerbatim("CC") << " no hit found ";
4562 
4563  if (!HitOK) return;
4564 
4565  if (prt)
4566  mf::LogVerbatim("CC") << " Pick hit time " << (int)fHits[imbest].PeakTime() << " hit index "
4567  << imbest;
4568 
4569  // merge hits in a multiplet?
4570  short hnear = 0;
4571  if (lastClHit != UINT_MAX && fHits[imbest].Multiplicity() > 1) {
4572  bool doMerge = true;
4573  // Standard code
4574  // don't merge if we are close to a vertex
4575  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
4576  if (vtx[ivx].CTP != clCTP) continue;
4577  if (prt)
4578  mf::LogVerbatim("CC") << " close vtx chk W:T " << vtx[ivx].Wire << ":"
4579  << (int)vtx[ivx].Time;
4580  if (std::abs(kwire - vtx[ivx].Wire) < 5 &&
4581  std::abs(int(fHits[imbest].PeakTime() - vtx[ivx].Time)) < 20) {
4582  if (prt) mf::LogVerbatim("CC") << " Close to a vertex. Don't merge hits";
4583  doMerge = false;
4584  }
4585  } // ivx
4586  // Decide which hits in the multiplet to merge. Hits that are well
4587  // separated from each other should not be merged
4588  if (doMerge) {
4589  unsigned short nused = 0;
4590  // the total charge of the hit multiplet
4591  float multipletChg = 0.;
4592  float chicut = AngleFactor(clpar[1]) * fHitMergeChiCut * fHits[lastClHit].RMS();
4593  // look for a big separation between adjacent hits
4594  std::pair<size_t, size_t> MultipletRange = FindHitMultiplet(imbest);
4595  for (size_t jht = MultipletRange.first; jht < MultipletRange.second; ++jht) {
4596  if (inClus[jht] < 0) continue;
4597  if (inClus[jht] == 0)
4598  multipletChg += fHits[jht].Integral();
4599  else
4600  ++nused;
4601  // check the neighbor hit separation
4602  if (jht > MultipletRange.first) {
4603  // pick the larger RMS of the two hits
4604  float hitRMS = fHits[jht].RMS();
4605  if (fHits[jht - 1].RMS() > hitRMS) hitRMS = fHits[jht - 1].RMS();
4606  const float tdiff =
4607  std::abs(fHits[jht].PeakTime() - fHits[jht - 1].PeakTime()) / hitRMS;
4608  if (prt) mf::LogVerbatim("CC") << " Hit RMS chisq " << tdiff << " chicut " << chicut;
4609  if (tdiff > chicut) doMerge = false;
4610  } // jht > 0
4611  } // jht
4612  if (prt) {
4613  if (!doMerge) mf::LogVerbatim("CC") << " Hits are well separated. Don't merge them ";
4614  }
4615  if (doMerge && nused == 0) {
4616  // compare the charge with the last hit added?
4617  if (ChkCharge) {
4618  // there is a nearby hit
4619  hnear = 1;
4620  float chgrat = multipletChg / fHits[lastClHit].Integral();
4621  if (prt)
4622  mf::LogVerbatim("CC") << " merge hits charge check " << (int)multipletChg
4623  << " Previous hit charge " << (int)fHits[lastClHit].Integral();
4624  if (chgrat > 2) doMerge = false;
4625  }
4626  } // doMerge && nused == 0
4627  } // doMerge true
4628  if (doMerge) {
4629  // there is a nearby hit and it will be merged
4630  hnear = -1;
4631  bool didMerge;
4632  MergeHits(imbest, didMerge);
4633  } // doMerge
4634  } // Hits[imbest].Multiplicity() > 1
4635 
4636  // attach to the cluster and fit
4637  fcl2hits.push_back(imbest);
4638  FitCluster();
4639  FitClusterChg();
4640  chifits.push_back(clChisq);
4641  hitNear.push_back(hnear);
4642  // remove the charge of the just added hit
4643  cnear -= fHits[imbest].Integral();
4644  if (cnear < 0) cnear = 0;
4645  // divide by the just added hit charge
4646  cnear /= fHits[imbest].Integral();
4647  chgNear.push_back(cnear);
4648  if (prt) {
4649  hitWidth = fHits[imbest].EndTick() - fHits[imbest].StartTick();
4650  mf::LogVerbatim("CC") << " >>LADD" << pass << " W:T " << PrintHit(imbest) << " dT "
4651  << timeDiff << " clChisq " << clChisq << " Chg "
4652  << (int)fHits[imbest].Integral() << " AveChg " << (int)fAveChg
4653  << " width " << (int)hitWidth << " AveWidth " << (int)fAveHitWidth
4654  << " fcl2hits size " << fcl2hits.size();
4655  } // prt
4656  // decide what to do with a bad fit
4657  if (clChisq > fChiCut[pass]) {
4658  FclTrimUS(1);
4659  FitCluster();
4660  HitOK = false;
4661  SigOK = false;
4662  if (prt) mf::LogVerbatim("CC") << " LADD- Removed bad hit. Stopped tracking";
4663  }
4664  } // AddLAHit()
4665 
4667  bool ClusterCrawlerAlg::ClusterHitsOK(short nHitChk)
4668  {
4669  // Check StartTick and EndTick of hits on adjacent wires overlap as illustrated below.
4670  // >>>>>> This is OK
4671  // Wire StartTick EndTick
4672  // n |--------------|
4673  // n+1 |--------------|
4674  // n+2 |--------------|
4675  // >>>>>> This is NOT OK
4676  // n |------|
4677  // n+1 |-----|
4678  // n+2 |------|
4679 
4680  if (fcl2hits.size() == 0) return true;
4681 
4682  unsigned short nHitToChk = fcl2hits.size();
4683  if (nHitChk > 0) nHitToChk = nHitChk + 1;
4684  unsigned short ii, indx;
4685 
4686  // require that they overlap
4687  // add a tolerance to the StartTick - EndTick overlap
4688  raw::TDCtick_t tol = 30;
4689  // expand the tolerance for induction planes
4690  if (plane < geom->TPC(geo::TPCID(cstat, tpc)).Nplanes() - 1) tol = 40;
4691 
4692  bool posSlope =
4693  (fHits[fcl2hits[0]].PeakTime() > fHits[fcl2hits[fcl2hits.size() - 1]].PeakTime());
4694 
4695  if (prt) {
4696  for (ii = 0; ii < nHitToChk; ++ii) {
4697  indx = fcl2hits.size() - 1 - ii;
4698  mf::LogVerbatim("CC") << "ClusterHitsOK chk " << fHits[fcl2hits[indx]].WireID().Wire
4699  << " start " << fHits[fcl2hits[indx]].StartTick() << " peak "
4700  << fHits[fcl2hits[indx]].PeakTime() << " end "
4701  << fHits[fcl2hits[indx]].EndTick() << " posSlope " << posSlope;
4702  }
4703  }
4704 
4705  raw::TDCtick_t hiStartTick, loEndTick;
4706  for (ii = 0; ii < nHitToChk - 1; ++ii) {
4707  indx = fcl2hits.size() - 1 - ii;
4708  // ignore if not on adjacent wires
4709  if (lar::util::absDiff(fHits[fcl2hits[indx]].WireID().Wire,
4710  fHits[fcl2hits[indx - 1]].WireID().Wire) > 1)
4711  continue;
4712  hiStartTick =
4713  std::max(fHits[fcl2hits[indx]].StartTick(), fHits[fcl2hits[indx - 1]].StartTick());
4714  loEndTick = std::min(fHits[fcl2hits[indx]].EndTick(), fHits[fcl2hits[indx - 1]].EndTick());
4715  if (posSlope) {
4716  if (loEndTick + tol < hiStartTick) { return false; }
4717  }
4718  else {
4719  if (loEndTick + tol < hiStartTick) { return false; }
4720  }
4721  } // ii
4722  return true;
4723  } // ClusterHitsOK
4724 
4726  void ClusterCrawlerAlg::AddHit(unsigned int kwire, bool& HitOK, bool& SigOK)
4727  {
4728  // Add a hit to the cluster if it meets several criteria:
4729  // similar pulse height to the cluster (if fAveChg is defined)
4730  // closest hit to the project cluster position.
4731  // Return SigOK if there is a nearby hit that was missed due to the cuts
4732 
4733  SigOK = false;
4734  HitOK = false;
4735 
4736  // not in the range of wires with hits
4737  if (kwire < fFirstWire || kwire > fLastWire) return;
4738 
4739  unsigned int lastClHit = UINT_MAX;
4740  if (fcl2hits.size() > 0) lastClHit = fcl2hits[fcl2hits.size() - 1];
4741 
4742  // the last hit added to the cluster
4743  unsigned int wire0 = clpar[2];
4744 
4745  // return if no signal and no hit
4746  if (fAllowNoHitWire == 0) {
4747  if (WireHitRange[kwire].first == -2) return;
4748  }
4749  else {
4750  // allow a number of wires with no hits
4751  if (WireHitRange[kwire].first == -2 && (wire0 - kwire) > fAllowNoHitWire) {
4752  SigOK = true;
4753  return;
4754  }
4755  }
4756  // skip bad wire, but assume the track was there
4757  if (WireHitRange[kwire].first == -1) {
4758  SigOK = true;
4759  return;
4760  }
4761 
4762  unsigned int firsthit = WireHitRange[kwire].first;
4763  unsigned int lasthit = WireHitRange[kwire].second;
4764 
4765  // the projected time of the cluster on this wire
4766  float dw = (float)kwire - (float)wire0;
4767  float prtime = clpar[0] + dw * clpar[1];
4768  if (prtime < 0 || (unsigned int)prtime > fMaxTime) return;
4769  // Find the projected time error including the projection error and the
4770  // error from the last hit added
4771  float prtimerr2 = std::abs(dw) * clparerr[1] * clparerr[1];
4772 
4773  // apply an angle dependent scale factor to the hit error. Default is very large error
4774  float hiterr = 10;
4775  if (lastClHit != UINT_MAX) hiterr = 3 * fHitErrFac * fHits[lastClHit].RMS();
4776  float err = std::sqrt(prtimerr2 + hiterr * hiterr);
4777  // Time window for accepting a hit.
4778  float hitWin = fClProjErrFac * err;
4779 
4780  float prtimeLo = prtime - hitWin;
4781  float prtimeHi = prtime + hitWin;
4782  float chgWinLo = prtime - fChgNearWindow;
4783  float chgWinHi = prtime + fChgNearWindow;
4784  if (prt) {
4785  mf::LogVerbatim("CC") << "AddHit: wire " << kwire << " prtime Lo " << (int)prtimeLo
4786  << " prtime " << (int)prtime << " Hi " << (int)prtimeHi << " prtimerr "
4787  << sqrt(prtimerr2) << " hiterr " << hiterr << " fAveChg "
4788  << (int)fAveChg << " fAveHitWidth " << std::setprecision(3)
4789  << fAveHitWidth;
4790  }
4791  // loop through the hits
4792  unsigned int imbest = INT_MAX;
4793  float best = 9999., dtime;
4794  float cnear = 0;
4795  float hitTime, hitChg, hitStartTick, hitEndTick;
4796  for (unsigned int khit = firsthit; khit < lasthit; ++khit) {
4797  // obsolete hit?
4798  if (inClus[khit] < 0) continue;
4799  hitTime = fHits[khit].PeakTime();
4800  dtime = std::abs(hitTime - prtime);
4801  if (dtime > 1000) continue;
4802  hitStartTick = fHits[khit].StartTick();
4803  hitEndTick = fHits[khit].EndTick();
4804  // weight by the charge difference
4805  if (fAveChg > 0) dtime *= std::abs(fHits[khit].Integral() - fAveChg) / fAveChg;
4806  if (prt && std::abs(dtime) < 100) {
4807  mf::LogVerbatim("CC") << " Chk W:T " << PrintHit(khit) << " dT " << std::fixed
4808  << std::setprecision(1) << (hitTime - prtime) << " InClus "
4809  << inClus[khit] << " mult " << fHits[khit].Multiplicity() << " RMS "
4810  << std::fixed << std::setprecision(1) << fHits[khit].RMS() << " Chi2 "
4811  << std::fixed << std::setprecision(1) << fHits[khit].GoodnessOfFit()
4812  << " Charge " << (int)fHits[khit].Integral() << " Peak " << std::fixed
4813  << std::setprecision(1) << fHits[khit].PeakAmplitude() << " LoT "
4814  << (int)hitStartTick << " HiT " << (int)hitEndTick << " index "
4815  << khit;
4816  }
4817  // count charge in the window
4818  if (fHits[khit].StartTick() > chgWinLo && fHits[khit].EndTick() < chgWinHi)
4819  cnear += fHits[khit].Integral();
4820  // check for signal
4821  if (prtimeHi < hitStartTick) continue;
4822  if (prtimeLo > hitEndTick) continue;
4823  SigOK = true;
4824  // check for good hit
4825  if (hitTime < prtimeLo) continue;
4826  if (hitTime > prtimeHi) continue;
4827  // hit used?
4828  if (inClus[khit] > 0) continue;
4829  if (dtime < best) {
4830  best = dtime;
4831  imbest = khit;
4832  }
4833  } // khit
4834 
4835  if (!SigOK) {
4836  if (fAllowNoHitWire == 0) return;
4837  if (prt)
4838  mf::LogVerbatim("CC") << " wire0 " << wire0 << " kwire " << kwire << " max "
4839  << fAllowNoHitWire << " imbest " << imbest;
4840  if ((wire0 - kwire) > fAllowNoHitWire) return;
4841  SigOK = true;
4842  }
4843 
4844  if (imbest == INT_MAX) return;
4845 
4846  recob::Hit const& hit = fHits[imbest];
4847  hitChg = hit.Integral();
4848 
4849  if (prt) mf::LogVerbatim("CC") << " Best hit time " << (int)hit.PeakTime();
4850 
4851  short hnear = 0;
4852  // merge hits in a doublet?
4853  bool didMerge = false;
4854  if (lastClHit != UINT_MAX && fAveHitWidth > 0 && fHitMergeChiCut > 0 &&
4855  hit.Multiplicity() == 2) {
4856  bool doMerge = true;
4857  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
4858  if (std::abs(kwire - vtx[ivx].Wire) < 10 &&
4859  std::abs(int(hit.PeakTime() - vtx[ivx].Time)) < 20) {
4860  doMerge = false;
4861  break;
4862  }
4863  } // ivx
4864  // quit if localindex does not make sense.
4865  if (hit.LocalIndex() != 0 && imbest == 0) doMerge = false;
4866  if (doMerge) {
4867  // find the neighbor hit
4868  unsigned int oht;
4869  if (hit.LocalIndex() == 0) { oht = imbest + 1; }
4870  else {
4871  oht = imbest - 1;
4872  } // hit.LocalIndex() == 0
4873  // check the hit time separation
4874  recob::Hit const& other_hit = fHits[oht];
4875  float hitSep = std::abs(hit.PeakTime() - other_hit.PeakTime());
4876  hitSep /= hit.RMS();
4877  // check the the charge similarity
4878  float totChg = hitChg + other_hit.Integral();
4879  float lastHitChg = fAveChg;
4880  if (lastHitChg < 0) lastHitChg = fHits[lastClHit].Integral();
4881  hnear = 1;
4882  if (prt)
4883  mf::LogVerbatim("CC") << " Chk hit merge hitsep " << hitSep << " dChg "
4884  << std::abs(totChg - lastHitChg) << " Cut "
4885  << std::abs(hit.Integral() - lastHitChg);
4886  if (inClus[oht] == 0 && hitSep < fHitMergeChiCut) {
4887  if (prt) mf::LogVerbatim("CC") << " Merging hit doublet " << imbest;
4888  MergeHits(imbest, didMerge);
4889  if (prt && !didMerge) mf::LogVerbatim("CC") << " Hit merge failed ";
4890  } // not in a cluster, hitSep OK, total charge OK
4891  } // doMerge
4892  } // fHitMergeChiCut > 0 && hit.Multiplicity() == 2
4893 
4894  // Make a charge similarity cut if the average charge is defined
4895  bool fitChg = true;
4896  if (fAveChg > 0.) {
4897 
4898  float chgrat = (hitChg - fAveChg) / fAveChg;
4899  if (prt) mf::LogVerbatim("CC") << " Chgrat " << std::setprecision(2) << chgrat;
4900 
4901  // charge is way too high?
4902  if (chgrat > 3 * fChgCut[pass]) {
4903  if (prt)
4904  mf::LogVerbatim("CC") << " fails 3 x high charge cut " << fChgCut[pass] << " on pass "
4905  << pass;
4906  return;
4907  }
4908 
4909  // Determine if the last hit added was a large (low) charge hit
4910  // This will be used to prevent adding large (low) charge hits on two
4911  // consecutive fits. This cut is only applied to hits on adjacent wires
4912  float bigchgcut = 1.5 * fChgCut[pass];
4913  bool lasthitbig = false;
4914  bool lasthitlow = false;
4915  if (lastClHit != UINT_MAX && lar::util::absDiff(wire0, kwire) == 1) {
4916  float lastchgrat = (fHits[lastClHit].Integral() - fAveChg) / fAveChg;
4917  lasthitbig = (lastchgrat > bigchgcut);
4918  lasthitlow = (lastchgrat < -fChgCut[pass]);
4919  }
4920 
4921  // the last hit added was low charge and this one is as well
4922  if (lasthitlow && chgrat < -fChgCut[pass]) {
4923  if (prt) mf::LogVerbatim("CC") << " fails low charge cut. Stop crawling.";
4924  SigOK = false;
4925  return;
4926  } // lasthitlow
4927 
4928  // the last hit was high charge and this one is also
4929  if (lasthitbig && chgrat > fChgCut[pass]) {
4930  if (prt)
4931  mf::LogVerbatim("CC") << " fails 2nd hit high charge cut. Last hit was high also. ";
4932  return;
4933  } // lasthitbig
4934 
4935  // require that large charge hits have a very good projection error
4936  if (chgrat > fChgCut[pass]) {
4937  if (best > 2 * err) {
4938  if (prt) mf::LogVerbatim("CC") << " high charge && bad dT= " << best << " err= " << err;
4939  return;
4940  }
4941  } // chgrat > fChgCut[pass]
4942 
4943  // decide whether to fit the charge
4944  fitChg = (chgrat < std::abs(fChgCut[pass]));
4945  } // fAveChg > 0
4946 
4947  // we now have a hit that meets all the criteria. Fit it
4948  fcl2hits.push_back(imbest);
4949  // This is strictly only necessary when calling AddHit for seed clusters
4950  if (fcl2hits.size() == 3) std::sort(fcl2hits.begin(), fcl2hits.end(), SortByLowHit);
4951  FitCluster();
4952  chifits.push_back(clChisq);
4953  hitNear.push_back(hnear);
4954  // remove the charge of the just added hit
4955  cnear -= fHits[imbest].Integral();
4956  if (cnear < 0) cnear = 0;
4957  // divide by the just added hit charge
4958  cnear /= fHits[imbest].Integral();
4959  chgNear.push_back(cnear);
4960  // nearby hit check
4961  // ChkClusterNearbyHits(prt);
4962  HitOK = true;
4963 
4964  if (chgNear.size() != fcl2hits.size()) {
4965  mf::LogError("CC") << "AddHit: Bad length";
4966  return;
4967  }
4968 
4969  if (prt)
4970  mf::LogVerbatim("CC") << " >>ADD" << pass << " W:T " << PrintHit(imbest) << " dT " << best
4971  << " clChisq " << clChisq << " Chg " << (int)fHits[imbest].Integral()
4972  << " AveChg " << (int)fAveChg << " fcl2hits size " << fcl2hits.size();
4973 
4974  if (!fitChg) return;
4975  if (prt) mf::LogVerbatim("CC") << " Fit charge ";
4976  FitClusterChg();
4977  } // AddHit()
4978 
4980  void ClusterCrawlerAlg::ChkClusterNearbyHits(bool prt)
4981  {
4982  // analyze the hitnear vector
4983  // 0 = no nearby hit exists
4984  // 1 = a nearby hit exists but was not merged
4985  // -1 = a nearby hit was merged
4986 
4987  if (fHitMergeChiCut <= 0) return;
4988 
4989  if (hitNear.size() != fcl2hits.size()) {
4990  mf::LogWarning("CC") << "Coding error: hitNear size != fcl2hits";
4991  return;
4992  }
4993 
4994  // Analyze the last 6 hits added but don't consider the first few hits
4995  if (hitNear.size() < 12) return;
4996 
4997  // TODO move into loops
4998  unsigned short ii, indx;
4999  unsigned short merged = 0;
5000  unsigned short notmerged = 0;
5001  for (ii = 0; ii < 6; ++ii) {
5002  indx = hitNear.size() - 1 - ii;
5003  if (hitNear[indx] > 0) ++notmerged;
5004  if (hitNear[indx] < 0) ++merged;
5005  }
5006 
5007  if (prt)
5008  mf::LogVerbatim("CC") << "ChkClusterNearbyHits: nearby hits merged " << merged
5009  << " not merged " << notmerged;
5010 
5011  if (notmerged < 2) return;
5012 
5013  // a number of nearby hits were not merged while crawling, so the
5014  // average charge is probably wrong. Look at the last 6 hits added
5015  // and merge them if they are close
5016  bool didMerge;
5017  for (ii = 0; ii < 6; ++ii) {
5018  indx = fcl2hits.size() - 1 - ii;
5019  const unsigned int iht = fcl2hits[indx];
5020  recob::Hit const& hit = fHits[iht];
5021  if (hit.Multiplicity() == 2) {
5022  // quit if localindex does not make sense.
5023  if (hit.LocalIndex() != 0 && iht == 0) continue;
5024  // hit doublet. Get the index of the other hit
5025  unsigned int oht;
5026  if (hit.LocalIndex() == 0) { oht = iht + 1; }
5027  else {
5028  oht = iht - 1;
5029  } // hit.LocalIndex() == 0
5030  recob::Hit const& other_hit = fHits[oht];
5031  // TODO use Hit::TimeDistanceAsRMS()
5032  float hitSep = std::abs(hit.PeakTime() - other_hit.PeakTime());
5033  hitSep /= hit.RMS();
5034  if (hitSep < fHitMergeChiCut && inClus[oht] == 0) {
5035  if (prt)
5036  mf::LogVerbatim("CC") << "Merging hit doublet " << iht << " W:T "
5037  << fHits[iht].WireID().Wire << ":" << fHits[iht].PeakTime();
5038  MergeHits(iht, didMerge);
5039  if (didMerge) hitNear[indx] = -1;
5040  } // hitSep OK and not in a cluster
5041  } // hit doublet
5042  } // ii
5043 
5044  // now re-fit
5045  FitCluster();
5046  FitClusterChg();
5047 
5048  if (prt) mf::LogVerbatim("CC") << "ChkClusterNearbyHits refit cluster. fAveChg= " << fAveChg;
5049 
5050  } // ChkClusterHitNear()
5051 
5053  void ClusterCrawlerAlg::FitVtx(unsigned short iv)
5054  {
5055  std::vector<float> x;
5056  std::vector<float> y;
5057  std::vector<float> ey2;
5058  float arg;
5059 
5060  // don't fit fixed vertices
5061  if (vtx[iv].Fixed) return;
5062 
5063  // Set this large in case something bad happens
5064  vtx[iv].ChiDOF = 99;
5065 
5066  // make a list of clusters
5067  unsigned short icl;
5068  std::vector<unsigned short> vcl;
5069  for (icl = 0; icl < tcl.size(); ++icl) {
5070  if (tcl[icl].ID < 0) continue;
5071  if (tcl[icl].CTP != vtx[iv].CTP) continue;
5072  if (tcl[icl].EndVtx == iv) vcl.push_back(icl);
5073  if (tcl[icl].BeginVtx == iv) vcl.push_back(icl);
5074  }
5075 
5076  vtx[iv].NClusters = vcl.size();
5077 
5078  if (vcl.size() == 0) return;
5079 
5080  // don't let the time error be less than the expected
5081  // time error of hits on a cluster. This is crude by
5082  // probably good enough
5083  icl = vcl[0];
5084  unsigned short indx = tcl[icl].tclhits.size() - 1;
5085  unsigned int hit = tcl[icl].tclhits[indx];
5086  float minTimeErr = fHitErrFac * fHits[hit].RMS() * fHits[hit].Multiplicity();
5087 
5088  if (vcl.size() == 1) {
5089  icl = vcl[0];
5090  // Put the vertex at the appropriate end of the cluster
5091  if (tcl[icl].EndVtx == iv) {
5092  vtx[iv].Wire = tcl[icl].EndWir;
5093  vtx[iv].WireErr = 1;
5094  vtx[iv].Time = tcl[icl].EndTim;
5095  // set the vertex time error to the hit error used for fitting
5096  indx = tcl[icl].tclhits.size() - 1;
5097  hit = tcl[icl].tclhits[indx];
5098  vtx[iv].TimeErr = fHitErrFac * fHits[hit].RMS() * fHits[hit].Multiplicity();
5099  vtx[iv].ChiDOF = 0;
5100  }
5101  if (tcl[icl].BeginVtx == iv) {
5102  vtx[iv].Wire = tcl[icl].BeginWir;
5103  vtx[iv].WireErr = 1;
5104  vtx[iv].Time = tcl[icl].BeginTim;
5105  // set the vertex time error to the hit error used for fitting
5106  hit = tcl[icl].tclhits[0];
5107  vtx[iv].TimeErr = fHitErrFac * fHits[hit].RMS() * fHits[hit].Multiplicity();
5108  vtx[iv].ChiDOF = 0;
5109  }
5110  return;
5111  } // size 1
5112 
5113  std::vector<double> slps;
5114  std::vector<double> slperrs;
5115  for (unsigned short ii = 0; ii < vcl.size(); ++ii) {
5116  icl = vcl[ii];
5117  if (tcl[icl].EndVtx == iv) {
5118  x.push_back(tcl[icl].EndSlp);
5119  slps.push_back(tcl[icl].EndSlp);
5120  slperrs.push_back(tcl[icl].EndSlpErr);
5121  arg = tcl[icl].EndSlp * tcl[icl].EndWir - tcl[icl].EndTim;
5122  y.push_back(arg);
5123  if (tcl[icl].EndSlpErr > 0.) { arg = tcl[icl].EndSlpErr * tcl[icl].EndWir; }
5124  else {
5125  arg = .1 * tcl[icl].EndWir;
5126  }
5127  ey2.push_back(arg * arg);
5128  }
5129  else if (tcl[icl].BeginVtx == iv) {
5130  x.push_back(tcl[icl].BeginSlp);
5131  slps.push_back(tcl[icl].BeginSlp);
5132  slperrs.push_back(tcl[icl].BeginSlpErr);
5133  arg = tcl[icl].BeginSlp * tcl[icl].BeginWir - tcl[icl].BeginTim;
5134  y.push_back(arg);
5135  if (tcl[icl].BeginSlpErr > 0.) { arg = tcl[icl].BeginSlpErr * tcl[icl].BeginWir; }
5136  else {
5137  arg = .1 * tcl[icl].BeginWir;
5138  }
5139  ey2.push_back(arg * arg);
5140  }
5141  } // ii
5142  if (x.size() < 2) return;
5143 
5144  // calculate error
5145  double sumerr = 0, cnt = 0;
5146  for (unsigned short ii = 0; ii < slps.size() - 1; ++ii) {
5147  for (unsigned short jj = ii + 1; jj < slps.size(); ++jj) {
5148  arg = std::min(slperrs[ii], slperrs[jj]);
5149  arg /= (slps[ii] - slps[jj]);
5150  sumerr += arg * arg;
5151  ++cnt;
5152  } // jj
5153  } // ii
5154  sumerr /= cnt;
5155 
5156  float vTime = 0.;
5157  float vTimeErr = 0.;
5158  float vWire = 0.;
5159  float vWireErr = 0.;
5160  float chiDOF;
5161  fLinFitAlg.LinFit(x, y, ey2, vTime, vWire, vTimeErr, vWireErr, chiDOF);
5162  if (chiDOF > 900) return;
5163  vTime = -vTime;
5164  // a crazy time from the fit?
5165  if (vTime < 0 || vTime > fMaxTime) return;
5166  // a crazy wire from the fit?
5167  geo::PlaneID iplID = DecodeCTP(vtx[iv].CTP);
5168  if (vWire < 0 || vWire > geom->Nwires(iplID)) return;
5169  vtx[iv].ChiDOF = chiDOF;
5170  vtx[iv].Wire = vWire;
5171  vtx[iv].Time = vTime;
5172  vtx[iv].WireErr = vWire * sqrt(sumerr);
5173  vtx[iv].TimeErr = vTime * fabs(sumerr);
5174  // set minimum wire error
5175  if (vtx[iv].WireErr < 1) vtx[iv].WireErr = 2;
5176  // set minimum time error
5177  if (vtx[iv].TimeErr < minTimeErr) vtx[iv].TimeErr = minTimeErr;
5178 
5179  } // FitVtx
5180 
5182  void ClusterCrawlerAlg::Vtx3ClusterMatch(detinfo::DetectorPropertiesData const& det_prop,
5183  geo::TPCID const& tpcid)
5184  {
5185  // Look for clusters that end/begin near the expected wire/time
5186  // for incomplete 3D vertices
5187  if (empty(vtx3)) return;
5188 
5189  const unsigned int cstat = tpcid.Cryostat;
5190  const unsigned int tpc = tpcid.TPC;
5191 
5192  unsigned int thePlane, theWire;
5193  float theTime;
5194  int dwb, dwe;
5195 
5196  for (unsigned short ivx = 0; ivx < vtx3.size(); ++ivx) {
5197  // A complete 3D vertex with matching 2D vertices in all planes?
5198  if (vtx3[ivx].Wire < 0) continue;
5199  if (vtx3[ivx].CStat != cstat || vtx3[ivx].TPC != tpc) continue;
5200  // Find the plane that is missing a 2D vertex
5201  thePlane = 3;
5202  theWire = vtx3[ivx].Wire;
5203  for (plane = 0; plane < 3; ++plane) {
5204  if (vtx3[ivx].Ptr2D[plane] >= 0) continue;
5205  thePlane = plane;
5206  break;
5207  } // plane
5208  if (thePlane > 2) continue;
5209  theTime = det_prop.ConvertXToTicks(vtx3[ivx].X, thePlane, tpc, cstat);
5210  clCTP = EncodeCTP(cstat, tpc, thePlane);
5211  // Create a new 2D vertex and see how many clusters we can attach to it
5212  VtxStore vnew;
5213  vnew.Wire = theWire;
5214  vnew.Time = theTime;
5215  vnew.CTP = clCTP;
5216  vnew.Topo = 7;
5217  vnew.Fixed = false;
5218  vtx.push_back(vnew);
5219  unsigned short ivnew = vtx.size() - 1;
5220  std::vector<short> vclIndex;
5221  for (unsigned short icl = 0; icl < tcl.size(); ++icl) {
5222  if (tcl[icl].ID < 0) continue;
5223  if (tcl[icl].CTP != clCTP) continue;
5224  dwb = lar::util::absDiff(theWire, tcl[icl].BeginWir);
5225  dwe = lar::util::absDiff(theWire, tcl[icl].EndWir);
5226  // rough cut to start
5227  if (dwb > 10 && dwe > 10) continue;
5228  if (dwb < dwe && dwb < 10 && tcl[icl].BeginVtx < 0) {
5229  // cluster begin is closer
5230  if (theWire < tcl[icl].BeginWir + 5) continue;
5231  if (ClusterVertexChi(icl, 0, ivnew) > fVertex3DCut) continue;
5232  tcl[icl].BeginVtx = ivnew;
5233  vclIndex.push_back(icl);
5234  }
5235  else if (dwe < 10 && tcl[icl].EndVtx < 0) {
5236  // cluster end is closer
5237  if (theWire > tcl[icl].EndWir - 5) continue;
5238  if (ClusterVertexChi(icl, 1, ivnew) > fVertex3DCut) continue;
5239  tcl[icl].EndVtx = ivnew;
5240  vclIndex.push_back(icl);
5241  } // dwb/dwe check
5242  } // icl
5243  bool goodVtx = false;
5244  if (vclIndex.size() > 0) {
5245  FitVtx(ivnew);
5246  goodVtx = (vtx[ivnew].ChiDOF < fVertex3DCut);
5247  vtx3[ivx].Ptr2D[thePlane] = ivnew;
5248  }
5249  if (goodVtx) {
5250  vtx3[ivx].Ptr2D[thePlane] = ivnew;
5251  vtx3[ivx].Wire = -1;
5252  }
5253  else {
5254  // clobber the vertex
5255  vtx.pop_back();
5256  for (unsigned short ii = 0; ii < vclIndex.size(); ++ii) {
5257  unsigned short icl = vclIndex[ii];
5258  if (tcl[icl].BeginVtx == ivnew) tcl[icl].BeginVtx = -99;
5259  if (tcl[icl].EndVtx == ivnew) tcl[icl].EndVtx = -99;
5260  } // ii
5261  }
5262  } // ivx
5263  } // Vtx3ClusterMatch
5264 
5266  void ClusterCrawlerAlg::Vtx3ClusterSplit(detinfo::DetectorPropertiesData const& det_prop,
5267  geo::TPCID const& tpcid)
5268  {
5269  // Try to split clusters in a view in which there is no 2D vertex
5270  // assigned to a 3D vertex
5271  if (empty(vtx3)) return;
5272 
5273  const unsigned int cstat = tpcid.Cryostat;
5274  const unsigned int tpc = tpcid.TPC;
5275 
5276  vtxprt = (fDebugPlane >= 0) && (fDebugHit == 6666);
5277 
5278  unsigned int lastplane = 5, kcl, kclID;
5279  float theTime;
5280  unsigned int thePlane, theWire, plane;
5281  unsigned int loWire, hiWire;
5282 
5283  for (unsigned short ivx = 0; ivx < vtx3.size(); ++ivx) {
5284  if (vtx3[ivx].CStat != cstat || vtx3[ivx].TPC != tpc) continue;
5285  // Complete 3D vertex with matching 2D vertices in all planes?
5286  if (vtxprt)
5287  mf::LogVerbatim("CC") << "Vtx3ClusterSplit ivx " << ivx << " Ptr2D " << vtx3[ivx].Ptr2D[0]
5288  << " " << vtx3[ivx].Ptr2D[1] << " " << vtx3[ivx].Ptr2D[2] << " wire "
5289  << vtx3[ivx].Wire;
5290  if (vtx3[ivx].Wire < 0) continue;
5291  // find the plane that needs to be studied
5292  thePlane = 3;
5293  theWire = vtx3[ivx].Wire;
5294  for (plane = 0; plane < 3; ++plane) {
5295  if (vtx3[ivx].Ptr2D[plane] >= 0) continue;
5296  thePlane = plane;
5297  break;
5298  } // plane
5299  if (thePlane > 2) continue;
5300  theTime = det_prop.ConvertXToTicks(
5301  (double)vtx3[ivx].X, (int)thePlane, (int)tpcid.TPC, (int)tpcid.Cryostat);
5302  // get the hit range if necessary
5303  if (thePlane != lastplane) {
5304  clCTP = EncodeCTP(tpcid.Cryostat, tpcid.TPC, thePlane);
5305  GetHitRange(clCTP);
5306  lastplane = thePlane;
5307  }
5308  // make a list of clusters that have hits near this point on nearby wires
5309  std::vector<short> clIDs;
5310  if (theWire > fFirstWire + 5) { loWire = theWire - 5; }
5311  else {
5312  loWire = fFirstWire;
5313  }
5314  if (theWire < fLastWire - 5) { hiWire = theWire + 5; }
5315  else {
5316  hiWire = fLastWire;
5317  }
5318  if (vtxprt)
5319  mf::LogVerbatim("CC") << "3DVtx " << ivx << " look for cluster hits near P:W:T " << thePlane
5320  << ":" << theWire << ":" << (int)theTime << " Wire range " << loWire
5321  << " to " << hiWire;
5322  for (unsigned int wire = loWire; wire < hiWire; ++wire) {
5323  // ignore dead wires or wires with no hits
5324  if (WireHitRange[wire].first < 0) continue;
5325  unsigned int firsthit = WireHitRange[wire].first;
5326  unsigned int lasthit = WireHitRange[wire].second;
5327  for (unsigned int khit = firsthit; khit < lasthit; ++khit) {
5328  // ignore obsolete and un-assigned hits
5329  if (inClus[khit] <= 0) continue;
5330  if ((unsigned int)inClus[khit] > tcl.size() + 1) {
5331  mf::LogError("CC") << "Invalid hit InClus. " << khit << " " << inClus[khit];
5332  continue;
5333  }
5334  // check an expanded time range
5335  if (theTime < fHits[khit].StartTick() - 20) continue;
5336  if (theTime > fHits[khit].EndTick() + 20) continue;
5337  kclID = inClus[khit];
5338  kcl = kclID - 1;
5339  // ignore obsolete clusters
5340  if (tcl[kcl].ID < 0) continue;
5341  // ignore short clusters
5342  if (tcl[kcl].tclhits.size() < 6) continue;
5343  // ignore long straight clusters
5344  if (tcl[kcl].tclhits.size() > 100 && std::abs(tcl[kcl].BeginAng - tcl[kcl].EndAng) < 0.1)
5345  continue;
5346  // put the cluster in the list if it's not there already
5347  if (vtxprt)
5348  mf::LogVerbatim("CC") << "Bingo " << ivx << " plane " << thePlane << " wire " << wire
5349  << " hit " << fHits[khit].WireID().Wire << ":"
5350  << (int)fHits[khit].PeakTime() << " inClus " << inClus[khit]
5351  << " P:W:T " << thePlane << ":" << tcl[kcl].BeginWir << ":"
5352  << (int)tcl[kcl].BeginTim;
5353  if (std::find(clIDs.begin(), clIDs.end(), kclID) == clIDs.end()) {
5354  clIDs.push_back(kclID);
5355  } // std::find
5356  } // khit
5357  } // wire
5358  if (clIDs.size() == 0) continue;
5359  if (vtxprt)
5360  for (unsigned int ii = 0; ii < clIDs.size(); ++ii)
5361  mf::LogVerbatim("CC") << " clIDs " << clIDs[ii];
5362 
5363  unsigned short ii, icl, jj;
5364  unsigned int iht;
5365  short nhitfit;
5366  bool didit;
5367  // find a reasonable time error using the 2D vertices that comprise this
5368  // incomplete 3D vertex
5369  float tErr = 1;
5370  unsigned short i2Dvx = 0;
5371  for (ii = 0; ii < 3; ++ii) {
5372  if (ii == thePlane) continue;
5373  i2Dvx = vtx3[ivx].Ptr2D[ii];
5374  if (i2Dvx > vtx.size() - 1) {
5375  mf::LogError("CC") << "Vtx3ClusterSplit: Coding error";
5376  return;
5377  }
5378  if (vtx[i2Dvx].TimeErr > tErr) tErr = vtx[i2Dvx].TimeErr;
5379  } // ii -> plane
5380 
5381  // do a local fit near the crossing point and make a tighter cut
5382  for (ii = 0; ii < clIDs.size(); ++ii) {
5383  icl = clIDs[ii] - 1;
5384  didit = false;
5385  for (jj = 0; jj < tcl[icl].tclhits.size(); ++jj) {
5386  iht = tcl[icl].tclhits[jj];
5387  if (fHits[iht].WireID().Wire < theWire) {
5388  nhitfit = 3;
5389  if (jj > 3) nhitfit = -3;
5390  FitClusterMid(icl, iht, nhitfit);
5391  float doca = DoCA(-1, 1, theWire, theTime);
5392  if (vtxprt)
5393  mf::LogVerbatim("CC") << " cls " << icl << " DoCA " << doca << " tErr " << tErr;
5394  if ((doca / tErr) > 2) clIDs[ii] = -1;
5395  didit = true;
5396  break;
5397  } // fHits[iht].WireID().Wire < theWire
5398  if (didit) break;
5399  } // jj
5400  if (didit) break;
5401  } // ii
5402  if (vtxprt) {
5403  mf::LogVerbatim("CC") << "clIDs after fit " << clIDs.size();
5404  for (ii = 0; ii < clIDs.size(); ++ii)
5405  mf::LogVerbatim("CC") << " clIDs " << clIDs[ii];
5406  }
5407 
5408  // see if any candidates remain
5409  unsigned short nok = 0;
5410  for (ii = 0; ii < clIDs.size(); ++ii)
5411  if (clIDs[ii] >= 0) ++nok;
5412  if (nok == 0) continue;
5413 
5414  // make a new 2D vertex
5415  VtxStore vnew;
5416  vnew.Wire = theWire;
5417  vnew.WireErr = 1;
5418  vnew.Time = theTime;
5419  vnew.TimeErr = 1;
5420  vnew.Topo = 8;
5421  vnew.CTP = clCTP;
5422  vnew.Fixed = false;
5423  vtx.push_back(vnew);
5424  // update the 2D -> 3D vertex pointer
5425  unsigned short ivnew = vtx.size() - 1;
5426  if (vtxprt)
5427  mf::LogVerbatim("CC") << "Make new 2D vtx " << ivnew << " in plane " << thePlane
5428  << " from 3D vtx " << ivx;
5429  vtx3[ivx].Ptr2D[thePlane] = ivnew;
5430  // either split or attach clusters to this vertex
5431  for (ii = 0; ii < clIDs.size(); ++ii) {
5432  if (clIDs[ii] < 0) continue;
5433  icl = clIDs[ii] - 1;
5434  // find the split position
5435  // split the cluster. Find the split position
5436  unsigned short pos = 0;
5437  for (unsigned short jj = 0; jj < tcl[icl].tclhits.size(); ++jj) {
5438  iht = tcl[icl].tclhits[jj];
5439  if (fHits[iht].WireID().Wire < theWire) {
5440  pos = jj;
5441  break;
5442  }
5443  } // jj
5444  if (pos == 0) {
5445  // vertex is DS of the cluster Begin
5446  tcl[icl].BeginVtx = ivnew;
5447  if (vtxprt) mf::LogVerbatim("CC") << "Attach to Begin " << icl;
5448  }
5449  else if (pos > tcl[icl].tclhits.size()) {
5450  // vertex is US of the cluster Eend
5451  tcl[icl].EndVtx = ivnew;
5452  if (vtxprt) mf::LogVerbatim("CC") << "Attach to End " << icl;
5453  }
5454  else {
5455  // vertex is in the middle of the cluster
5456  if (vtxprt) mf::LogVerbatim("CC") << "Split cluster " << clIDs[ii] << " at pos " << pos;
5457  if (!SplitCluster(icl, pos, ivnew)) {
5458  if (vtxprt) mf::LogVerbatim("CC") << "SplitCluster failed";
5459  continue;
5460  }
5461  tcl[icl].ProcCode += 10000;
5462  tcl[tcl.size() - 1].ProcCode += 10000;
5463  } // pos check
5464  } // ii
5465  // Fit the vertex position
5466  FitVtx(ivnew);
5467  if (vtx[ivnew].ChiDOF < 5 && vtx[ivnew].WireErr < fVertex2DWireErrCut) {
5468  // mark the 3D vertex as complete
5469  vtx3[ivx].Wire = -1;
5470  }
5471  else {
5472  if (vtxprt)
5473  mf::LogVerbatim("CC") << "Bad vtx fit " << ivnew << " ChiDOF " << vtx[ivnew].ChiDOF
5474  << " WireErr " << vtx[ivnew].WireErr;
5475  // Recover (partially) from a bad fit. Leave the ProcCode as-is to trace this problem
5476  vtx.pop_back();
5477  vtx3[ivx].Ptr2D[thePlane] = -1;
5478  // find the cluster - vertex associations
5479  for (jj = 0; jj < tcl.size(); ++jj) {
5480  if (tcl[jj].BeginVtx == ivnew) tcl[jj].BeginVtx = -99;
5481  if (tcl[jj].EndVtx == ivnew) tcl[jj].EndVtx = -99;
5482  } // jj
5483  }
5484  } // ivx
5485 
5486  } // Vtx3ClusterSplit()
5487 
5489  void ClusterCrawlerAlg::FindHammerClusters(detinfo::DetectorPropertiesData const& det_prop)
5490  {
5491  // look for a long cluster that stops at a short cluster in two views. This can occur in a CCmu
5492  // interaction where two protons are emitted back-to-back and are therefore reconstructed as one cluster
5493  // This routine looks for this signature, and if found, splits the short clusters and creates a new 3D vertex.
5494  // This routine only considers the case where the long cluster intersects the short cluster at the US (End) end.
5495 
5496  unsigned int nPln = geom->TPC(geo::TPCID(cstat, tpc)).Nplanes();
5497  if (nPln != 3) return;
5498 
5499  float ew1, ew2, bw2, fvw;
5500 
5501  struct Hammer {
5502  bool Used;
5503  unsigned int Wire; // intersection point of the long cluster and the short cluster
5504  float Tick; // intersection point of the long cluster and the short cluster
5505  float X;
5506  unsigned short longClIndex;
5507  unsigned short shortClIndex;
5508  unsigned short splitPos;
5509  };
5510  std::array<std::vector<Hammer>, 3> hamrVec;
5511 
5512  unsigned int ipl;
5513  bool useit = false;
5514  for (ipl = 0; ipl < 3; ++ipl) {
5515  clCTP = EncodeCTP(cstat, tpc, ipl);
5516  for (unsigned short ic1 = 0; ic1 < tcl.size(); ++ic1) {
5517  if (tcl[ic1].ID < 0) continue;
5518  // require a long cluster
5519  if (tcl[ic1].tclhits.size() < 20) continue;
5520  if (tcl[ic1].CTP != clCTP) continue;
5521  // ignore long clusters with an End vertex assignment
5522  if (tcl[ic1].EndVtx >= 0) continue;
5523  ew1 = tcl[ic1].EndWir;
5524  for (unsigned short ic2 = 0; ic2 < tcl.size(); ++ic2) {
5525  if (tcl[ic2].ID < 0) continue;
5526  // require a short cluster
5527  if (tcl[ic2].tclhits.size() > 20) continue;
5528  // but not too short cluster
5529  if (tcl[ic2].tclhits.size() < 6) continue;
5530  if (tcl[ic2].CTP != clCTP) continue;
5531  ew2 = tcl[ic2].EndWir;
5532  bw2 = tcl[ic2].BeginWir;
5533  // check the US end. The End of the long cluster must lie between the Begin and End wire of the
5534  // short cluster
5535  if (ew1 < ew2 || ew1 > bw2) continue;
5536  // look for intersection of the two clusters
5537  float best = 10;
5538  short ibst = -1;
5539  unsigned short spos = 0;
5540  for (unsigned short ii = 0; ii < tcl[ic2].tclhits.size(); ++ii) {
5541  unsigned int iht = tcl[ic2].tclhits[ii];
5542  float dw = fHits[iht].WireID().Wire - tcl[ic1].EndWir;
5543  float dt = fabs(fHits[iht].PeakTime() - tcl[ic1].EndTim - tcl[ic1].EndSlp * dw);
5544  if (dt < best) {
5545  best = dt;
5546  ibst = iht;
5547  spos = ii;
5548  }
5549  } // ii
5550  if (ibst < 0) continue;
5551  fvw = 0.5 + fHits[ibst].WireID().Wire;
5552  Hammer aHam;
5553  aHam.Used = false;
5554  aHam.Wire = (0.5 + fvw);
5555  aHam.Tick = fHits[ibst].PeakTime();
5556  aHam.X = det_prop.ConvertTicksToX((double)aHam.Tick, (int)ipl, (int)tpc, (int)cstat);
5557  aHam.longClIndex = ic1;
5558  aHam.shortClIndex = ic2;
5559  aHam.splitPos = spos;
5560  unsigned short indx = hamrVec[ipl].size();
5561  hamrVec[ipl].resize(indx + 1);
5562  hamrVec[ipl][indx] = aHam;
5563  useit = true;
5564  } // ic2
5565  if (useit) break;
5566  } // ic1
5567  } // ipl
5568 
5569  unsigned short noham = 0;
5570  for (ipl = 0; ipl < 3; ++ipl)
5571  if (hamrVec[ipl].size() == 0) ++noham;
5572  if (noham > 1) return;
5573 
5574  // Y,Z limits of the detector
5575 
5576  geo::TPCID const tpcid(cstat, tpc);
5577  const geo::TPCGeo& thetpc = geom->TPC(tpcid);
5578  auto const world = thetpc.GetCenter();
5579  float YLo = world.Y() - thetpc.HalfHeight() + 1;
5580  float YHi = world.Y() + thetpc.HalfHeight() - 1;
5581  float ZLo = world.Z() - thetpc.Length() / 2 + 1;
5582  float ZHi = world.Z() + thetpc.Length() / 2 - 1;
5583 
5584  // Match in 3D
5585  float dX;
5586  double y, z;
5587  unsigned short icl, jpl, jcl, kpl, splitPos;
5588  for (ipl = 0; ipl < 3; ++ipl) {
5589  if (hamrVec[ipl].size() == 0) continue;
5590  jpl = (ipl + 1) % nPln;
5591  kpl = (jpl + 1) % nPln;
5592  for (unsigned short ii = 0; ii < hamrVec[ipl].size(); ++ii) {
5593  if (hamrVec[ipl][ii].Used) continue;
5594  for (unsigned short jj = 0; jj < hamrVec[jpl].size(); ++jj) {
5595  if (hamrVec[jpl][jj].Used) continue;
5596  dX = hamrVec[ipl][ii].X - hamrVec[jpl][jj].X;
5597  if (fabs(dX) > fVertex3DCut) continue;
5598  geo::PlaneID const plane_i{tpcid, ipl};
5599  geo::PlaneID const plane_j{tpcid, jpl};
5600  geom->IntersectionPoint(geo::WireID{plane_i, hamrVec[ipl][ii].Wire},
5601  geo::WireID{plane_j, hamrVec[jpl][jj].Wire},
5602  y,
5603  z);
5604  if (y < YLo || y > YHi || z < ZLo || z > ZHi) continue;
5605  // mark them used
5606  hamrVec[ipl][ii].Used = true;
5607  hamrVec[jpl][jj].Used = true;
5608  // make a new 3D vertex
5609  Vtx3Store newVtx3;
5610  newVtx3.ProcCode = 7;
5611  newVtx3.X = 0.5 * (hamrVec[ipl][ii].X + hamrVec[jpl][jj].X);
5612  // TODO: do this correctly;
5613  newVtx3.XErr = fabs(hamrVec[ipl][ii].X - hamrVec[jpl][jj].X);
5614  newVtx3.Y = y;
5615  newVtx3.YErr = 1; // TODO
5616  newVtx3.Z = z;
5617  newVtx3.ZErr = 1; // TODO
5618  newVtx3.CStat = cstat;
5619  newVtx3.TPC = tpc;
5620 
5621  // make 2D vertex in ipl
5622  VtxStore newVtx2;
5623  newVtx2.Wire = hamrVec[ipl][ii].Wire;
5624  newVtx2.WireErr = 2;
5625  newVtx2.Time = hamrVec[ipl][ii].Tick;
5626  newVtx2.TimeErr = 5;
5627  newVtx2.Topo = 6;
5628  newVtx2.Fixed = false;
5629  icl = hamrVec[ipl][ii].longClIndex;
5630  newVtx2.CTP = tcl[icl].CTP;
5631  vtx.push_back(newVtx2);
5632  unsigned short ivnew = vtx.size() - 1;
5633  // associate the new vertex with the long cluster
5634  tcl[icl].EndVtx = ivnew;
5635  FitVtx(ivnew);
5636  // stash the index in newVtx3
5637  newVtx3.Ptr2D[ipl] = (short)ivnew;
5638  // split the short cluster and associate the new clusters with the new vtx
5639  icl = hamrVec[ipl][ii].shortClIndex;
5640  splitPos = hamrVec[ipl][ii].splitPos;
5641  if (!SplitCluster(icl, splitPos, ivnew)) return;
5642 
5643  // make 2D vertex in jpl
5644  newVtx2.Wire = hamrVec[jpl][jj].Wire;
5645  newVtx2.Time = hamrVec[jpl][jj].Tick;
5646  newVtx2.Topo = 6;
5647  jcl = hamrVec[jpl][jj].longClIndex;
5648  newVtx2.CTP = tcl[jcl].CTP;
5649  vtx.push_back(newVtx2);
5650  ivnew = vtx.size() - 1;
5651  // associate the new vertex with the long cluster
5652  tcl[jcl].EndVtx = ivnew;
5653  // stash the index in newVtx3
5654  newVtx3.Ptr2D[jpl] = (short)(vtx.size() - 1);
5655  // split the short cluster and associate the new clusters with the new
5656  // vtx
5657  jcl = hamrVec[jpl][jj].shortClIndex;
5658  splitPos = hamrVec[jpl][jj].splitPos;
5659  if (!SplitCluster(jcl, splitPos, vtx.size() - 1)) return;
5660  FitVtx(ivnew);
5661  // set the kpl 2D vertex index < 0. Let follow-on code find the 3rd
5662  // plane vertex
5663  newVtx3.Ptr2D[kpl] = -1;
5664  geo::Point_t const WPos{0, y, z};
5665  try {
5666  newVtx3.Wire = geom->NearestWireID(WPos, geo::PlaneID{cstat, tpc, kpl}).Wire;
5667  }
5668  catch (geo::InvalidWireError const& e) {
5669  newVtx3.Wire = e.suggestedWireID().Wire; // pick the closest valid wire
5670  }
5671  vtx3.push_back(newVtx3);
5672  } // jj
5673  } // ii
5674  }
5675 
5676  } // FindHammerClusters
5677 
5679  void ClusterCrawlerAlg::VtxMatch(detinfo::DetectorPropertiesData const& det_prop,
5680  geo::TPCID const& tpcid)
5681  {
5682  // Create 3D vertices from 2D vertices. 3D vertices that are matched
5683  // in all three planes have Ptr2D >= 0 for all planes
5684 
5685  geo::TPCGeo const& TPC = geom->TPC(tpcid);
5686 
5687  // Y,Z limits of the detector
5688  auto const world = TPC.GetCenter();
5689 
5690  // reduce the active area of the TPC by 1 cm to prevent wire boundary issues
5691  float YLo = world.Y() - TPC.HalfHeight() + 1;
5692  float YHi = world.Y() + TPC.HalfHeight() - 1;
5693  float ZLo = world.Z() - TPC.Length() / 2 + 1;
5694  float ZHi = world.Z() + TPC.Length() / 2 - 1;
5695 
5696  vtxprt = (fDebugPlane >= 0) && (fDebugHit == 6666);
5697 
5698  if (vtxprt) {
5699  mf::LogVerbatim("CC") << "Inside VtxMatch";
5700  PrintVertices();
5701  }
5702 
5703  // wire spacing in cm
5704  float wirePitch = geom->WirePitch(geo::PlaneID{tpcid, 0});
5705 
5706  // fill temp vectors of 2D vertex X and X errors
5707  std::vector<float> vX(vtx.size());
5708  std::vector<float> vXsigma(vtx.size());
5709  float vXp;
5710  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
5711  if (vtx[ivx].NClusters == 0) continue;
5712  geo::PlaneID iplID = DecodeCTP(vtx[ivx].CTP);
5713  if (iplID.TPC != tpc || iplID.Cryostat != cstat) continue;
5714  // Convert 2D vertex time error to X error
5715  vX[ivx] =
5716  det_prop.ConvertTicksToX((double)vtx[ivx].Time, (int)iplID.Plane, (int)tpc, (int)cstat);
5717  vXp = det_prop.ConvertTicksToX(
5718  (double)(vtx[ivx].Time + vtx[ivx].TimeErr), (int)iplID.Plane, (int)tpc, (int)cstat);
5719  vXsigma[ivx] = fabs(vXp - vX[ivx]);
5720  } // ivx
5721 
5722  // create a array/vector of 2D vertex indices in each plane
5723  std::array<std::vector<unsigned short>, 3> vIndex;
5724  unsigned short indx, ipl;
5725  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
5726  if (vtx[ivx].NClusters == 0) continue;
5727  geo::PlaneID iplID = DecodeCTP(vtx[ivx].CTP);
5728  if (iplID.TPC != tpc || iplID.Cryostat != cstat) continue;
5729  ipl = iplID.Plane;
5730  if (ipl > 2) continue;
5731  indx = vIndex[ipl].size();
5732  vIndex[ipl].resize(indx + 1);
5733  vIndex[ipl][indx] = ivx;
5734  }
5735 
5736  // vector of 2D vertices -> 3D vertices.
5737  std::vector<short> vPtr;
5738  for (unsigned short ii = 0; ii < vtx.size(); ++ii)
5739  vPtr.push_back(-1);
5740 
5741  // temp vector of all 2D vertex matches
5742  std::vector<Vtx3Store> v3temp;
5743 
5744  double y = 0, z = 0;
5745  geo::Point_t WPos{0, 0, 0};
5746  // i, j, k indicates 3 different wire planes
5747  unsigned short ii, jpl, jj, kpl, kk, ivx, jvx, kvx;
5748  unsigned int iWire, jWire;
5749  unsigned short v3dBest = 0;
5750  float xbest = 0, ybest = 0, zbest = 0;
5751  float kX, kWire;
5752  // compare vertices in each view
5753  bool gotit = false;
5754  for (ipl = 0; ipl < 2; ++ipl) {
5755  geo::PlaneID const plane_i{tpcid, ipl};
5756  for (ii = 0; ii < vIndex[ipl].size(); ++ii) {
5757  ivx = vIndex[ipl][ii];
5758  if (ivx > vtx.size() - 1) {
5759  mf::LogError("CC") << "VtxMatch: bad ivx " << ivx;
5760  return;
5761  }
5762  // vertex has been matched already
5763  if (vPtr[ivx] >= 0) continue;
5764  iWire = vtx[ivx].Wire;
5765  float best = fVertex3DCut;
5766  // temp array of 2D vertex indices in each plane
5767  // BUG the double brace syntax is required to work around clang bug 21629
5768  // (https://bugs.llvm.org/show_bug.cgi?id=21629)
5769  std::array<short, 3> t2dIndex = {{-1, -1, -1}};
5770  std::array<short, 3> tmpIndex = {{-1, -1, -1}};
5771  for (jpl = ipl + 1; jpl < 3; ++jpl) {
5772  geo::PlaneID const plane_j{tpcid, jpl};
5773  for (jj = 0; jj < vIndex[jpl].size(); ++jj) {
5774  jvx = vIndex[jpl][jj];
5775  if (jvx > vtx.size() - 1) {
5776  mf::LogError("CC") << "VtxMatch: bad jvx " << jvx;
5777  return;
5778  }
5779  // vertex has been matched already
5780  if (vPtr[jvx] >= 0) continue;
5781  jWire = vtx[jvx].Wire;
5782  // new stuff
5783  float dX = fabs(vX[ivx] - vX[jvx]);
5784  float dXSigma = sqrt(vXsigma[ivx] * vXsigma[ivx] + vXsigma[jvx] * vXsigma[jvx]);
5785  float dXChi = dX / dXSigma;
5786 
5787  if (vtxprt)
5788  mf::LogVerbatim("CC")
5789  << "VtxMatch: ipl " << ipl << " ivx " << ivx << " ivX " << vX[ivx] << " jpl " << jpl
5790  << " jvx " << jvx << " jvX " << vX[jvx] << " W:T " << (int)vtx[jvx].Wire << ":"
5791  << (int)vtx[jvx].Time << " dXChi " << dXChi << " fVertex3DCut " << fVertex3DCut;
5792 
5793  if (dXChi > fVertex3DCut) continue;
5794  geom->IntersectionPoint(geo::WireID{plane_i, iWire}, geo::WireID{plane_j, jWire}, y, z);
5795  if (y < YLo || y > YHi || z < ZLo || z > ZHi) continue;
5796  WPos.SetY(y);
5797  WPos.SetZ(z);
5798  kpl = 3 - ipl - jpl;
5799  kX = 0.5 * (vX[ivx] + vX[jvx]);
5800  kWire = -1;
5801  if (TPC.Nplanes() > 2) {
5802  try {
5803  kWire = geom->NearestWireID(WPos, geo::PlaneID{cstat, tpc, kpl}).Wire;
5804  }
5805  catch (geo::InvalidWireError const& e) {
5806  kWire = e.suggestedWireID().Wire; // pick the closest valid wire
5807  }
5808  }
5809  kpl = 3 - ipl - jpl;
5810  // save this incomplete 3D vertex
5811  Vtx3Store v3d;
5812  v3d.ProcCode = 1;
5813  tmpIndex[ipl] = ivx;
5814  tmpIndex[jpl] = jvx;
5815  tmpIndex[kpl] = -1;
5816  v3d.Ptr2D = tmpIndex;
5817  v3d.X = kX;
5818  v3d.XErr = dXSigma;
5819  v3d.Y = y;
5820  float yzSigma = wirePitch * sqrt(vtx[ivx].WireErr * vtx[ivx].WireErr +
5821  vtx[jvx].WireErr * vtx[jvx].WireErr);
5822  v3d.YErr = yzSigma;
5823  v3d.Z = z;
5824  v3d.ZErr = yzSigma;
5825  v3d.Wire = kWire;
5826  v3d.CStat = cstat;
5827  v3d.TPC = tpc;
5828  v3temp.push_back(v3d);
5829 
5830  if (vtxprt)
5831  mf::LogVerbatim("CC")
5832  << "VtxMatch: 2 Plane match ivx " << ivx << " P:W:T " << ipl << ":"
5833  << (int)vtx[ivx].Wire << ":" << (int)vtx[ivx].Time << " jvx " << jvx << " P:W:T "
5834  << jpl << ":" << (int)vtx[jvx].Wire << ":" << (int)vtx[jvx].Time << " dXChi "
5835  << dXChi << " yzSigma " << yzSigma;
5836 
5837  if (TPC.Nplanes() == 2) continue;
5838  // look for a 3 plane match
5839  best = fVertex3DCut;
5840  for (kk = 0; kk < vIndex[kpl].size(); ++kk) {
5841  kvx = vIndex[kpl][kk];
5842  if (vPtr[kvx] >= 0) continue;
5843  // Wire difference error
5844  float dW = wirePitch * (vtx[kvx].Wire - kWire) / yzSigma;
5845  // X difference error
5846  float dX = (vX[kvx] - kX) / dXSigma;
5847  float kChi = 0.5 * sqrt(dW * dW + dX * dX);
5848  if (kChi < best) {
5849  best = kChi;
5850  xbest = (vX[kvx] + 2 * kX) / 3;
5851  ybest = y;
5852  zbest = z;
5853  t2dIndex[ipl] = ivx;
5854  t2dIndex[jpl] = jvx;
5855  t2dIndex[kpl] = kvx;
5856  v3dBest = v3temp.size() - 1;
5857  }
5858 
5859  if (vtxprt)
5860  mf::LogVerbatim("CC")
5861  << " kvx " << kvx << " kpl " << kpl << " wire " << (int)vtx[kvx].Wire << " kTime "
5862  << (int)vtx[kvx].Time << " kChi " << kChi << " best " << best << " dW "
5863  << vtx[kvx].Wire - kWire;
5864 
5865  } // kk
5866  if (vtxprt)
5867  mf::LogVerbatim("CC") << " done best = " << best << " fVertex3DCut " << fVertex3DCut;
5868  if (TPC.Nplanes() > 2 && best < fVertex3DCut) {
5869  // create a real 3D vertex using the previously entered incomplete 3D vertex as a template
5870  if (v3dBest > v3temp.size() - 1) {
5871  mf::LogError("CC") << "VtxMatch: bad v3dBest " << v3dBest;
5872  return;
5873  }
5874  Vtx3Store v3d = v3temp[v3dBest];
5875  v3d.Ptr2D = t2dIndex;
5876  v3d.Wire = -1;
5877  // TODO need to average ybest and zbest here with error weighting
5878  v3d.X = xbest;
5879  v3d.Y = ybest;
5880  v3d.Z = zbest;
5881  vtx3.push_back(v3d);
5882  gotit = true;
5883  // mark the 2D vertices as used
5884  for (unsigned short jj = 0; jj < 3; ++jj)
5885  if (t2dIndex[jj] >= 0) vPtr[t2dIndex[jj]] = vtx3.size() - 1;
5886 
5887  if (vtxprt)
5888  mf::LogVerbatim("CC")
5889  << "New 3D vtx " << vtx3.size() << " X " << v3d.X << " Y " << v3d.Y << " Z "
5890  << v3d.Z << " t2dIndex " << t2dIndex[0] << " " << t2dIndex[1] << " "
5891  << t2dIndex[2] << " best Chi " << best;
5892 
5893  } // best < dRCut
5894  if (gotit) break;
5895  } // jj
5896  if (gotit) break;
5897  } // jpl
5898  if (gotit) break;
5899  } // ii
5900  } // ipl
5901 
5902  // Store incomplete 3D vertices but ignore those that are part of a complete 3D vertex
5903  unsigned short vsize = vtx3.size();
5904  for (unsigned short it = 0; it < v3temp.size(); ++it) {
5905  bool keepit = true;
5906  for (unsigned short i3d = 0; i3d < vsize; ++i3d) {
5907  for (unsigned short plane = 0; plane < 3; ++plane) {
5908  if (v3temp[it].Ptr2D[plane] == vtx3[i3d].Ptr2D[plane]) {
5909  keepit = false;
5910  break;
5911  }
5912  } // plane
5913  if (!keepit) break;
5914  } // i3d
5915 
5916  if (keepit) vtx3.push_back(v3temp[it]);
5917  } // it
5918 
5919  // Modify Ptr2D for 2-plane detector
5920  if (TPC.Nplanes() == 2) {
5921  for (unsigned short iv3 = 0; iv3 < vtx3.size(); ++iv3) {
5922  vtx3[iv3].Ptr2D[2] = 666;
5923  } //iv3
5924  } // 2 planes
5925 
5926  if (vtxprt) {
5927  for (unsigned short it = 0; it < vtx3.size(); ++it) {
5928  mf::LogVerbatim("CC") << "vtx3 " << it << " Ptr2D " << vtx3[it].Ptr2D[0] << " "
5929  << vtx3[it].Ptr2D[1] << " " << vtx3[it].Ptr2D[2] << " wire "
5930  << vtx3[it].Wire;
5931  }
5932  }
5933 
5934  } // VtxMatch
5935 
5937  void ClusterCrawlerAlg::GetHitRange(CTP_t CTP)
5938  {
5939  // fills the WireHitRange vector for the supplied Cryostat/TPC/Plane code
5940  // Hits must have been sorted by increasing wire number
5941  fFirstHit = 0;
5942  geo::PlaneID planeID = DecodeCTP(CTP);
5943  unsigned int nwires = geom->Nwires(planeID);
5944  WireHitRange.resize(nwires + 1);
5945 
5946  // These will be re-defined later
5947  fFirstWire = 0;
5948  fLastWire = 0;
5949 
5950  unsigned int wire, iht;
5951  unsigned int nHitInPlane;
5952  std::pair<int, int> flag;
5953 
5954  // Define the "no hits on wire" condition
5955  flag.first = -2;
5956  flag.second = -2;
5957  for (auto& apair : WireHitRange)
5958  apair = flag;
5959 
5960  nHitInPlane = 0;
5961 
5962  std::vector<bool> firsthit;
5963  firsthit.resize(nwires + 1, true);
5964  bool firstwire = true;
5965  for (iht = 0; iht < fHits.size(); ++iht) {
5966  if (fHits[iht].WireID().asPlaneID() != planeID) continue;
5967  wire = fHits[iht].WireID().Wire;
5968  // define the first hit start index in this TPC, Plane
5969  if (firsthit[wire]) {
5970  WireHitRange[wire].first = iht;
5971  firsthit[wire] = false;
5972  }
5973  if (firstwire) {
5974  fFirstWire = wire;
5975  firstwire = false;
5976  }
5977  WireHitRange[wire].second = iht + 1;
5978  fLastWire = wire + 1;
5979  ++nHitInPlane;
5980  }
5981  // overwrite with the "dead wires" condition
5982  lariov::ChannelStatusProvider const& channelStatus =
5984 
5985  flag.first = -1;
5986  flag.second = -1;
5987  unsigned int nbad = 0;
5988  for (wire = 0; wire < nwires; ++wire) {
5989  raw::ChannelID_t chan = geom->PlaneWireToChannel(geo::WireID(planeID, wire));
5990  if (!channelStatus.IsGood(chan)) {
5991  WireHitRange[wire] = flag;
5992  ++nbad;
5993  }
5994  } // wire
5995  // define the MergeAvailable vector and check for errors
5996  if (mergeAvailable.size() < fHits.size())
5998  << "GetHitRange: Invalid mergeAvailable vector size " << mergeAvailable.size()
5999  << fHits.size();
6000  unsigned int firstHit, lastHit;
6001  unsigned int cnt;
6002  cnt = 0;
6003  float maxRMS, chiSep, peakCut;
6004  for (wire = 0; wire < nwires; ++wire) {
6005  // ignore dead wires and wires with no hits
6006  if (WireHitRange[wire].first < 0) continue;
6007  firstHit = WireHitRange[wire].first;
6008  lastHit = WireHitRange[wire].second;
6009  for (iht = firstHit; iht < lastHit; ++iht) {
6010  if (fHits[iht].WireID().Wire != wire)
6012  << "Bad WireHitRange on wire " << wire << "\n";
6013  ++cnt;
6014  if (fHits[iht].Multiplicity() > 1) {
6015  peakCut = 0.6 * fHits[iht].PeakAmplitude();
6016  std::pair<size_t, size_t> MultipletRange = FindHitMultiplet(iht);
6017  for (size_t jht = MultipletRange.first; jht < MultipletRange.second; ++jht) {
6018  if (jht == iht) continue;
6019  // require that the j hit be similar in magnitude to the i hit
6020  if (fHits[jht].PeakAmplitude() < peakCut) continue;
6021  maxRMS = std::max(fHits[iht].RMS(), fHits[jht].RMS());
6022  chiSep = std::abs(fHits[iht].PeakTime() - fHits[jht].PeakTime()) / maxRMS;
6023  if (chiSep < fHitMergeChiCut) {
6024  mergeAvailable[iht] = true;
6025  break;
6026  }
6027  } // jht
6028  } // fHits[iht].Multiplicity() > 1
6029  } // iht
6030  } // wire
6031  if (cnt != nHitInPlane)
6032  mf::LogWarning("CC") << "Bad WireHitRange count " << cnt << " " << nHitInPlane << "\n";
6033 
6034  if (!fMergeAllHits) return;
6035 
6036  // Merge all of the hits
6037  bool didMerge;
6038  for (wire = 0; wire < nwires; ++wire) {
6039  if (WireHitRange[wire].first < 0) continue;
6040  firstHit = WireHitRange[wire].first;
6041  lastHit = WireHitRange[wire].second;
6042  for (iht = firstHit; iht < lastHit; ++iht) {
6043  if (!mergeAvailable[iht]) continue;
6044  // already merged?
6045  if (fHits[iht].GoodnessOfFit() == 6666) continue;
6046  MergeHits(iht, didMerge);
6047  mergeAvailable[iht] = false;
6048  } // iht
6049  } // wire
6050 
6051  } // GetHitRange()
6052 
6054  unsigned int ClusterCrawlerAlg::DeadWireCount(unsigned int inWire1, unsigned int inWire2)
6055  {
6056  if (inWire1 > inWire2) {
6057  // put in increasing order
6058  unsigned int tmp = inWire1;
6059  inWire1 = inWire2;
6060  inWire2 = tmp;
6061  } // inWire1 > inWire2
6062  ++inWire2;
6063  unsigned int wire, ndead = 0;
6064  for (wire = inWire1; wire < inWire2; ++wire)
6065  if (WireHitRange[wire].first == -1) ++ndead;
6066  return ndead;
6067  } // DeadWireCount
6068 
6071  {
6072  // Counts the number of dead wires in the range spanned by fcl2hits
6073  if (fcl2hits.size() < 2) return 0;
6074  unsigned int wire, ndead = 0;
6075  unsigned int iht = fcl2hits[fcl2hits.size() - 1];
6076  unsigned int eWire = fHits[iht].WireID().Wire;
6077  iht = fcl2hits[0];
6078  unsigned int bWire = fHits[iht].WireID().Wire + 1;
6079  for (wire = eWire; wire < bWire; ++wire)
6080  if (WireHitRange[wire].first == -1) ++ndead;
6081  return ndead;
6082  } // DeadWireCount
6083 
6085  bool ClusterCrawlerAlg::areInSameMultiplet(recob::Hit const& first_hit,
6086  recob::Hit const& second_hit)
6087  {
6088  return (first_hit.StartTick() == second_hit.StartTick()) &&
6089  (first_hit.WireID() == second_hit.WireID());
6090  } // ClusterCrawlerAlg::areInSameMultiplet()
6091 
6093  std::pair<size_t, size_t> ClusterCrawlerAlg::FindHitMultiplet(size_t iHit) const
6094  {
6095  std::pair<size_t, size_t> range{iHit, iHit};
6096 
6097  range.first = iHit - fHits[iHit].LocalIndex();
6098  range.second = range.first + fHits[iHit].Multiplicity();
6099 
6100  return range;
6101  } // ClusterCrawlerAlg::FindHitMultiplet()
6102 
6104  bool ClusterCrawlerAlg::CheckHitDuplicates(std::string location,
6105  std::string marker /* = "" */) const
6106  {
6107  // currently unused, only for debug
6108  unsigned int nDuplicates = 0;
6109  std::set<unsigned int> hits;
6110  for (unsigned int hit : fcl2hits) {
6111  if (hits.count(hit)) {
6112  ++nDuplicates;
6113  mf::LogProblem log("CC");
6114  log << "Hit #" << hit
6115  << " being included twice in the future cluster (ID=" << (tcl.size() + 1)
6116  << "?) at location: " << location;
6117  if (!marker.empty()) log << " (marker: '" << marker << "')";
6118  }
6119  hits.insert(hit);
6120  } // for
6121  return nDuplicates > 0;
6122  } // ClusterCrawlerAlg::CheckHitDuplicates()
6123 
6125  float ClusterCrawlerAlg::DoCA(short icl, unsigned short end, float vwire, float vtick)
6126  {
6127  // Find the Distance of Closest Approach betweeen a cluster and a point (vwire, vtick). The
6128  // DoCA is returned in Tick units.
6129 
6130  if (icl > (short)tcl.size()) return 9999;
6131 
6132  float cwire, cslp, ctick;
6133  // figure out which cluster to use
6134  if (icl < 0) {
6135  if (fcl2hits.size() == 0) return 9999;
6136  // cluster under construction
6137  if (end == 0) {
6138  cwire = clBeginWir;
6139  cslp = clBeginSlp;
6140  ctick = clBeginTim;
6141  }
6142  else {
6143  cwire = clpar[2];
6144  cslp = clpar[1];
6145  ctick = clpar[0];
6146  } // end
6147  }
6148  else {
6149  // tcl cluster
6150  if (end == 0) {
6151  cwire = tcl[icl].BeginWir;
6152  cslp = tcl[icl].BeginSlp;
6153  ctick = tcl[icl].BeginTim;
6154  }
6155  else {
6156  cwire = tcl[icl].EndWir;
6157  cslp = tcl[icl].EndSlp;
6158  ctick = tcl[icl].EndTim;
6159  } // end
6160  }
6161 
6162  // Closest approach wire
6163  float docaW = (vwire + cslp * (vtick - ctick) + cwire * cslp * cslp) / (1 + cslp * cslp);
6164  float dW = docaW - vwire;
6165  float dT = ctick + (vwire - cwire) * cslp - vtick;
6166  return sqrt(dW * dW + dT * dT);
6167 
6168  } // DoCA
6169 
6171  float ClusterCrawlerAlg::ClusterVertexChi(short icl, unsigned short end, unsigned short ivx)
6172  {
6173  // Returns the chisq/DOF between a cluster and a vertex
6174 
6175  if (icl > (short)tcl.size()) return 9999;
6176  if (ivx > vtx.size()) return 9999;
6177 
6178  float cwire, cslp, cslpErr, ctick;
6179  // figure out which cluster to use
6180  if (icl < 0) {
6181  if (fcl2hits.size() == 0) return 9999;
6182  // cluster under construction
6183  if (end == 0) {
6184  cwire = clBeginWir;
6185  cslp = clBeginSlp;
6186  cslpErr = clBeginSlpErr;
6187  ctick = clBeginTim;
6188  }
6189  else {
6190  cwire = clpar[2];
6191  cslp = clpar[1];
6192  cslpErr = clparerr[1];
6193  ctick = clpar[0];
6194  } // end
6195  }
6196  else {
6197  // tcl cluster
6198  if (end == 0) {
6199  cwire = tcl[icl].BeginWir;
6200  cslp = tcl[icl].BeginSlp;
6201  cslpErr = tcl[icl].BeginSlpErr;
6202  ctick = tcl[icl].BeginTim;
6203  }
6204  else {
6205  cwire = tcl[icl].EndWir;
6206  cslp = tcl[icl].EndSlp;
6207  cslpErr = tcl[icl].EndSlpErr;
6208  ctick = tcl[icl].EndTim;
6209  } // end
6210  }
6211 
6212  // Closest approach wire
6213  float docaW =
6214  (vtx[ivx].Wire + cslp * (vtx[ivx].Time - ctick) + cwire * cslp * cslp) / (1 + cslp * cslp);
6215  float dW = docaW - vtx[ivx].Wire;
6216  float chi = dW / vtx[ivx].WireErr;
6217  float totChi = chi * chi;
6218  dW = vtx[ivx].Wire - cwire;
6219  float dT = ctick + dW * cslp - vtx[ivx].Time;
6220  if (cslpErr < 1E-3) cslpErr = 1E-3;
6221  // increase slope error for large angle clusters
6222  cslpErr *= AngleFactor(cslp);
6223  // cluster slope projection error
6224  float dTErr = dW * cslpErr;
6225  // squared
6226  dTErr *= dTErr;
6227  // add the vertex time error^2 to the cluster projection error^2
6228  dTErr += vtx[ivx].TimeErr * vtx[ivx].TimeErr;
6229  if (dTErr < 1E-3) dTErr = 1E-3;
6230  totChi += dT * dT / dTErr;
6231  totChi /= 2;
6232 
6233  return totChi;
6234 
6235  } // ClusterVertexChi
6236 
6238  float ClusterCrawlerAlg::PointVertexChi(float wire, float tick, unsigned short ivx)
6239  {
6240  // Returns the Chisq/DOF between a (Wire, Tick) point and a vertex
6241 
6242  if (ivx > vtx.size()) return 9999;
6243 
6244  float dW = wire - vtx[ivx].Wire;
6245  float chi = dW / vtx[ivx].WireErr;
6246  float totChi = chi * chi;
6247  float dT = tick - vtx[ivx].Time;
6248  chi = dT / vtx[ivx].TimeErr;
6249  totChi += chi * chi;
6250 
6251  return totChi;
6252 
6253  } // PointVertexChi
6254 
6256  std::string ClusterCrawlerAlg::PrintHit(unsigned int iht)
6257  {
6258 
6259  if (iht > fHits.size() - 1) return "Bad Hit";
6260  return std::to_string(fHits[iht].WireID().Plane) + ":" +
6261  std::to_string(fHits[iht].WireID().Wire) + ":" +
6262  std::to_string((int)fHits[iht].PeakTime());
6263 
6264  } // PrintHit
6265 
6266 } // 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:256
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:272
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:228
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:236
float SigmaIntegral() const
Initial tdc tick for hit.
Definition: Hit.h:248
constexpr auto abs(T v)
Returns the absolute value of the argument.
int DegreesOfFreedom() const
Initial tdc tick for hit.
Definition: Hit.h:264
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:244
geo::View_t View() const
View for the plane of the hit.
Definition: Hit.h:276
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:280
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:260
short int Multiplicity() const
How many hits could this one be shared with.
Definition: Hit.h:252
struct of temporary 3D vertices
float PeakAmplitude() const
The estimated amplitude of the hit at its peak, in ADC units.
Definition: Hit.h:232
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:212
float PeakTimeMinusRMS(float sigmas=+1.) const
Returns a time sigmas RMS away from the peak time.
Definition: Hit.h:290
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:216
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
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:220
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
float SummedADC() const
The sum of calibrated ADC counts of the hit (0. by default)
Definition: Hit.h:240
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:224
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:285
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:268
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.