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