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