All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
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
19 #include "canvas/Utilities/Exception.h"
20 #include "fhiclcpp/ParameterSet.h"
21 #include "messagefacility/MessageLogger/MessageLogger.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)
123  throw art::Exception(art::errors::Configuration)
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
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
150  {
151  fHits.clear();
152  tcl.clear();
153  vtx.clear();
154  vtx3.clear();
155  inClus.clear();
156  } // ClusterCrawlerAlg::ClearResults()
157 
158  //------------------------------------------------------------------------------
159  void
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
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
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
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
291 
292  } // RunCrawler
293 
294  ////////////////////////////////////////////////
295  void
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)
351  throw art::Exception(art::errors::LogicError)
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)
356  throw art::Exception(art::errors::LogicError)
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
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
486  // Check the DS end of clusters
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
496 
497  if (fDebugPlane == (int)plane) {
498  mf::LogVerbatim("CC") << "Clustering done in plane " << plane;
499  PrintClusters();
500  }
501 
503 
504  } // ClusterLoop()
505 
506  //////////////////////////////////////////
507  void
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
820  continue;
821  }
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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()) {
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) {
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);
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
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;
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;
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
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
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
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 
3718 
3719  clProcCode += 300;
3720  if (prt) mf::LogVerbatim("CC") << "LACrawlUS done. Nhits = " << fcl2hits.size();
3721  prt = false;
3722  } // LACrawlUS
3723 
3724  /////////////////////////////////////////
3725  void
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  }
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
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
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
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  }
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
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
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
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
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
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
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
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
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
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
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 =
6047  art::ServiceHandle<lariov::ChannelStatusService const>()->GetProvider();
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())
6062  throw art::Exception(art::errors::LogicError)
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)
6076  throw art::Exception(art::errors::LogicError)
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>
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
float fScaleF
scale factor from Tick/Wire to dx/du
std::vector< short > hitNear
bool ClusterHitsOK(short nHitChk)
float fAveChg
average charge at leading edge of cluster
process_name opflash particleana ie ie ie z
std::vector< ClusterStore > tcl
the clusters we are creating
std::vector< std::pair< int, int > > WireHitRange
void FitVtx(unsigned short iv)
Functions to help with numbers.
geo::SigType_t SignalType() const
Signal type for the plane of the hit.
Definition: Hit.h:231
void RemoveObsoleteHits()
Removes obsolete hits from hits, updating the indices.
void FitClusterMid(unsigned short it1, unsigned int iht, short nhit)
bool ChkSignal(unsigned int iht, unsigned int jht)
Utilities related to art service access.
BEGIN_PROLOG true icarus_rawdigitfilter FilterTools FilterPlane1 Plane
std::vector< float > fTimeDelta
max time difference for matching
static unsigned int kWire
Encapsulate the construction of a single cyostat.
process_name opflash particleana ie x
process_name cluster
Definition: cheaterreco.fcl:51
struct of temporary 2D vertices (end points)
float clparerr[2]
cluster parameter errors
then echo unknown compiler flag
static bool areInSameMultiplet(recob::Hit const &first_hit, recob::Hit const &second_hit)
Returns whether the two hits belong to the same multiplet.
std::vector< float > fChgCut
charge difference cut for adding a hit to a cluster
std::vector< Vtx3Store > vtx3
the 3D vertices we are reconstructing
float clBeginChg
begin average charge
std::vector< unsigned short > fMinWirAfterSkip
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
Declaration of signal hit object.
int fDebugWire
set to the Begin Wire and Hit of a cluster to print
EResult err(const char *call)
walls no right
Definition: selectors.fcl:105
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
float clEndSlp
slope at the end (= US end = low wire number)
BEGIN_PROLOG StartTick
static bool SortByMultiplet(recob::Hit const &a, recob::Hit const &b)
Comparison for sorting hits by wire and hit multiplet.
std::vector< unsigned short > fMaxWirSkip
max number of wires that can be skipped while crawling
int DegreesOfFreedom() const
Definition: Hit.h:229
CryostatID_t Cryostat
Index of cryostat.
Definition: geo_types.h:212
std::size_t size(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:561
void MergeHits(const unsigned int theHit, bool &didMerge)
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
process_name E
void FixMultipletLocalIndices(size_t begin, size_t end, short int multiplicity=-1)
Resets the local index and multiplicity of all the hits in [begin;end[.
std::vector< float > fMinAmp
expected minimum signal in each wire plane
process_name hit
Definition: cheaterreco.fcl:51
CTP_t clCTP
Cryostat/TPC/Plane code.
then echo echo For and will not be changed by echo further linking echo echo B echo The symbol is in the uninitialized data multiple common symbols may appear with the echo same name If the symbol is defined the common echo symbols are treated as undefined references For more echo details on common see the discussion of warn common echo in *Note Linker see the discussion of warn common echo in *Note Linker such as a global int variable echo as opposed to a large global array echo echo I echo The symbol is an indirect reference to another symbol This echo is a GNU extension to the a out object file format which is echo rarely used echo echo N echo The symbol is a debugging symbol echo echo R echo The symbol is in a read only data section echo echo S echo The symbol is in an uninitialized data section for small echo objects echo echo T echo The symbol is in the the normal defined echo symbol is used with no error When a weak undefined symbol echo is linked and the symbol is not the value of the echo weak symbol becomes zero with no error echo echo W echo The symbol is a weak symbol that has not been specifically echo tagged as a weak object symbol When a weak defined symbol echo is linked with a normal defined the normal defined echo symbol is used with no error When a weak undefined symbol echo is linked and the symbol is not the value of the echo weak symbol becomes zero with no error echo echo echo The symbol is a stabs symbol in an a out object file In echo this the next values printed are the stabs other echo the stabs desc and the stab type Stabs symbols are echo used to hold debugging information For more echo see *Note or object file format specific echo echo For Mac OS X
std::vector< float > fMergeChgCut
max charge ratio for matching
float DoCA(short icl, unsigned short end, float vwire, float vtick)
float fHitErrFac
hit time error = fHitErrFac * hit RMS used for cluster fit
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
art::ServiceHandle< geo::Geometry const > geom
void AddLAHit(unsigned int kwire, bool &ChkCharge, bool &HitOK, bool &SigOK)
struct of temporary 3D vertices
BEGIN_PROLOG TPC
float fAveHitWidth
average width (EndTick - StartTick) of hits
void FclTrimUS(unsigned short nTrim)
bool CrawlVtxChk(unsigned int kwire)
static geo::PlaneID DecodeCTP(CTP_t CTP)
float PeakAmplitude() const
The estimated amplitude of the hit at its peak, in ADC units.
Definition: Hit.h:221
constexpr details::BinObj< T > bin(T value)
Returns a wrapper to print the specified data in binary format.
constexpr int cmp(WireID const &other) const
Returns &lt; 0 if this is smaller than tpcid, 0 if equal, &gt; 0 if larger.
Definition: geo_types.h:627
double Efield(unsigned int planegap=0) const
kV/cm
void DoMerge(unsigned short it1, unsigned short it2, short ProcCode)
float fHitMinAmp
&lt; ignore hits with Amp &lt; this value
int TDCtick_t
Type representing a TDC tick.
Definition: RawTypes.h:25
int fDebugHit
out detailed information while crawling
std::vector< bool > fLACrawl
Crawl Large Angle clusters on pass?
process_name gaushit a
std::vector< short > inClus
Hit used in cluster (-1 = obsolete, 0 = free)
unsigned int fLastWire
the last wire with a hit
then local
void VtxMatch(detinfo::DetectorClocksData const &clock_data, detinfo::DetectorPropertiesData const &det_prop, geo::TPCID const &tpcid)
std::vector< float > fChiCut
stop adding hits to clusters if chisq too high
float ClusterVertexChi(short icl, unsigned short end, unsigned short ivx)
float fChgNearWindow
window (ticks) for finding nearby charge
Access the description of detector geometry.
void LinFit(std::vector< float > &x, std::vector< float > &y, std::vector< float > &ey2, float &Intercept, float &Slope, float &InterceptError, float &SlopeError, float &ChiDOF) const
Definition: LinFitAlg.cxx:17
T abs(T value)
unsigned short fNumPass
number of passes over the hit collection
Collection of exceptions for Geometry system.
float fMergeOverlapAngCut
angle cut for merging overlapping clusters
process_name opflash particleana ie ie y
IDparameter< geo::WireID > WireID
Member type of validated geo::WireID parameter.
unsigned int fFirstWire
the first wire with a hit
unsigned int clEndWir
begin wire
float fVertex2DCut
2D vtx -&gt; cluster matching cut (chisq/dof)
void FindHammerClusters(detinfo::DetectorClocksData const &clock_data, detinfo::DetectorPropertiesData const &det_prop)
std::vector< float > fKinkAngCut
kink angle cut made after fKinkChiRat
float fVertex3DCut
2D vtx -&gt; 3D vtx matching cut (chisq/dof)
auto end(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:585
void VtxConstraint(unsigned int iwire, unsigned int ihit, unsigned int jwire, unsigned int &useHit, bool &doConstrain)
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
virtual bool IsGood(raw::ChannelID_t channel) const
Returns whether the specified channel is physical and good.
unsigned short fLAClusMaxHitsFit
max hits fitted on a Large Angle cluster
bool SortByLowHit(unsigned int i, unsigned int j)
void MakeClusterObsolete(unsigned short icl)
Marks the cluster as obsolete and frees hits still associated with it.
Class providing information about the quality of channels.
std::vector< float > fKinkChiRat
void RestoreObsoleteCluster(unsigned short icl)
Restores an obsolete cluster.
void TmpGet(unsigned short it1)
double DriftVelocity(double efield=0., double temperature=0.) const
cm/us
std::vector< bool > fFindVertices
run vertexing code after clustering?
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 clBeginSlp
begin slope (= DS end = high wire number)
std::pair< size_t, size_t > FindHitMultiplet(size_t iHit) const
std::vector< unsigned short > fMaxHitsFit
Max number of hits fitted.
float PeakTimeMinusRMS(float sigmas=+1.) const
Definition: Hit.h:239
std::string PrintHit(unsigned int iht)
constexpr auto absDiff(A const &a, B const &b)
Returns the absolute value of the difference between two values.
Definition: NumericUtils.h:43
static CTP_t EncodeCTP(unsigned int cryo, unsigned int tpc, unsigned int plane)
float clChisq
chisq of the current fit
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
auto begin(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:573
bool greaterThan(CluLen c1, CluLen c2)
bool clLA
using Large Angle crawling code
std::vector< recob::Hit > fHits
our version of the hits
float PeakTime() const
Time of the signal peak, in tick units.
Definition: Hit.h:218
std::vector< unsigned short > fMinHits
Min number of hits to make a cluster.
float clEndChg
end average charge
std::vector< float > chifits
fit chisq for monitoring kinks, etc
Contains all timing reference information for the detector.
unsigned int clBeginWir
begin wire
void ClusterVertex(unsigned short it2)
std::string to_string(WindowPattern const &pattern)
float fLAClusAngleCut
call Large Angle Clustering code if &gt; 0
std::vector< bool > mergeAvailable
set true if hit is with HitMergeChiCut of a neighbor hit
bool SplitCluster(unsigned short icl, unsigned short pos, unsigned short ivx)
void RunCrawler(detinfo::DetectorClocksData const &clock_data, detinfo::DetectorPropertiesData const &det_prop, std::vector< recob::Hit > const &srchits)
float fClProjErrFac
cluster projection error factor
void Vtx3ClusterSplit(detinfo::DetectorClocksData const &clock_data, detinfo::DetectorPropertiesData const &det_prop, geo::TPCID const &tpcid)
void AddHit(unsigned int kwire, bool &HitOK, bool &SigOK)
Interface for experiment-specific channel quality info provider.
do i e
unsigned int fFirstHit
first hit used
static double tdiff(const art::Timestamp &ts1, const art::Timestamp &ts2)
float clBeginChgNear
nearby charge
Exception thrown on invalid wire number.
Definition: Exceptions.h:42
void ChkMerge12(unsigned short it1, unsigned short it2, bool &didit)
float SummedADC() const
The sum of calibrated ADC counts of the hit (0. by default)
Definition: Hit.h:223
std::vector< float > chgNear
charge near a cluster on each wire
std::vector< VtxStore > vtx
the endpoints we are reconstructing
bool fFindHammerClusters
look for hammer type clusters
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
bool ChkMergedClusterHitFrac(unsigned short it1, unsigned short it2)
float PeakTimePlusRMS(float sigmas=+1.) const
Returns a time sigmas RMS away from the peak time.
Definition: Hit.h:236
float clEndChgNear
nearby charge
std::size_t count(Cont const &cont)
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.
void VertexCluster(unsigned short ivx)
std::vector< unsigned int > fcl2hits
vector of hits used in the cluster
float fChgSlp
slope of the charge vs wire
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.
void RefineVertexClusters(unsigned short ivx)
bool empty(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:555
raw::ChannelID_t Channel() const
ID of the readout channel the hit was extracted from.
Definition: Hit.h:230
void ChkVertex(float fvw, float fvt, unsigned short it1, unsigned short it2, short topo)
float PointVertexChi(float wire, float tick, unsigned short ivx)
void LocalToWorld(const double *tpc, double *world) const
Transform point from local TPC frame to world frame.
Definition: TPCGeo.h:563
art framework interface to geometry description
std::vector< unsigned short > fNHitsAve
Encapsulate the construction of a single detector plane.
std::vector< bool > fDoMerge
try to merge clusters?
void Vtx3ClusterMatch(detinfo::DetectorClocksData const &clock_data, detinfo::DetectorPropertiesData const &det_prop, geo::TPCID const &tpcid)
bool CheckHitDuplicates(std::string location, std::string marker="") const
Returns true if there are no duplicates in the hit list for next cluster.
ClusterCrawlerAlg(fhicl::ParameterSet const &pset)