LArSoft  v10_04_05
Liquid Argon Software toolkit - https://larsoft.org/
CCTrackMaker_module.cc
Go to the documentation of this file.
1 //
3 // CCTrackMaker
4 //
5 // Make tracks using ClusterCrawler clusters and vertex info
6 //
7 // baller@fnal.gov, October 2014
8 // May 2015: Major re-write
9 //
11 
12 // C++ includes
13 #include <algorithm>
14 #include <cmath>
15 #include <iomanip>
16 #include <iostream>
17 #include <string>
18 
19 // Framework includes
26 #include "fhiclcpp/ParameterSet.h"
28 
29 // LArSoft includes
43 
44 struct CluLen {
45  int index;
46  int length;
47 };
48 
49 bool greaterThan(CluLen c1, CluLen c2)
50 {
51  return (c1.length > c2.length);
52 }
53 bool lessThan(CluLen c1, CluLen c2)
54 {
55  return (c1.length < c2.length);
56 }
57 
58 namespace trkf {
59 
60  class CCTrackMaker : public art::EDProducer {
61  public:
62  explicit CCTrackMaker(fhicl::ParameterSet const& pset);
63 
64  private:
65  void produce(art::Event& evt) override;
66 
67  std::string fHitModuleLabel;
68  std::string fClusterModuleLabel;
69  std::string fVertexModuleLabel;
70 
71  // services
74 
77 
78  // Track matching parameters
79  unsigned short algIndex;
80  std::vector<short> fMatchAlgs;
81  std::vector<float> fXMatchErr;
82  std::vector<float> fAngleMatchErr;
83  std::vector<float> fChgAsymFactor;
84  std::vector<float> fMatchMinLen;
85  std::vector<bool> fMakeAlgTracks;
86 
87  // Cluster merging parameters
88  float fMaxDAng;
89  float fChainMaxdX;
90  float fChainVtxAng;
94 
95  float fChgWindow{40}; // window (ticks) for identifying shower-like clusters
96  float fWirePitch;
97  // cosmic ray tagging
98  float fFiducialCut;
99  float fDeltaRayCut;
100 
101  bool fMakePFPs;
102 
103  // vertex fitting
104  unsigned short fNVtxTrkHitsFit;
106 
107  // temp
108  bool fuBCode;
109 
110  // debugging inputs
111  short fDebugAlg;
112  short fDebugPlane;
115  bool prt;
116 
117  // hit indexing info
118  std::array<unsigned int, 3> firstWire;
119  std::array<unsigned int, 3> lastWire;
120  std::array<unsigned int, 3> firstHit;
121  std::array<unsigned int, 3> lastHit;
122  std::array<std::vector<std::pair<int, int>>, 3> WireHitRange;
123 
124  std::vector<art::Ptr<recob::Hit>> allhits;
125 
126  // Cluster parameters
127  struct clPar {
128  std::array<float, 2> Wire; // Begin/End Wire
129  std::array<float, 2> X; // Begin/End X
130  std::array<short, 2> Time; // Begin/End Time
131  std::array<float, 2> Slope; // Begin/End slope
132  std::array<float, 2> Charge; // Begin/End charge
133  std::array<float, 2> ChgNear; // Charge near the cluster at each end
134  std::array<float, 2> Angle; // Begin/End angle (radians)
135  std::array<short, 2> Dir; // Product of end * slope
136  std::array<short, 2> VtxIndex; // Vertex index
137  std::array<short, 2> mVtxIndex; // "Maybe" Vertex index
138  std::array<short, 2> BrkIndex; // Broken cluster index
139  std::array<float, 2> MergeError; // Broken cluster merge error (See MakeClusterChains)
140  unsigned short EvtIndex; // index of the cluster in clusterlist
141  short InTrack; // cluster -> chain index
142  unsigned short Length; // cluster length (wires)
143  float TotChg; // total charge of cluster (or series of clusters)
144  };
145  // vector of cluster parameters in each plane
146  std::array<std::vector<clPar>, 3> cls;
147 
148  // cluster chain parameters
149  struct ClsChainPar {
150  std::array<float, 2> Wire; // Begin/End Wire
151  std::array<float, 2> X; // Begin/End X
152  std::array<float, 2> Time; // Begin/End Time
153  std::array<float, 2> Slope; // Begin/End slope
154  std::array<float, 2> Angle; // Begin/End angle (radians)
155  std::array<short, 2> VtxIndex; // Vertex index
156  std::array<short, 2> Dir;
157  std::array<float, 2> ChgNear; // Charge near the cluster at each end
158  std::array<short, 2> mBrkIndex; // a "Maybe" cluster index
159  unsigned short Length;
160  float TotChg;
161  std::vector<unsigned short> ClsIndex;
162  std::vector<unsigned short> Order;
163  short InTrack; // cluster -> track ID (-1 if none, 0 if under construction)
164  };
165  // vector of cluster parameters in each plane
166  std::array<std::vector<ClsChainPar>, 3> clsChain;
167 
168  // 3D Vertex info
169  struct vtxPar {
170  short ID;
171  unsigned short EvtIndex;
172  float X;
173  float Y;
174  float Z;
175  std::array<unsigned short, 3> nClusInPln;
176  bool Neutrino;
177  };
178 
179  std::vector<vtxPar> vtx;
180 
181  // cluster indices assigned to one vertex. Filled in VtxMatch
182  std::array<std::vector<unsigned short>, 3> vxCls;
183 
184  struct TrkPar {
185  short ID;
186  unsigned short Proc; // 1 = VtxMatch, 2 = ...
187  std::array<std::vector<art::Ptr<recob::Hit>>, 3> TrkHits;
188  std::vector<TVector3> TrjPos;
189  std::vector<TVector3> TrjDir;
190  std::array<short, 2> VtxIndex;
191  std::vector<unsigned short> ClsEvtIndices;
192  float Length;
193  short ChgOrder;
194  short MomID;
195  std::array<bool, 2> EndInTPC;
196  std::array<bool, 2> GoodEnd; // set true if there are hits in all planes at the end point
197  std::vector<short> DtrID;
198  short PDGCode;
199  };
200  std::vector<TrkPar> trk;
201 
202  // Array of pointers to hits in each plane for one track
203  std::array<std::vector<art::Ptr<recob::Hit>>, 3> trkHits;
204  // and for one seed
205  std::array<std::vector<art::Ptr<recob::Hit>>, 3> seedHits;
206  // relative charge normalization between planes
207  std::array<float, 3> ChgNorm;
208 
209  // Vector of PFParticle -> track IDs. The first element will be
210  // track ID = 0 indicating that this is a neutrino PFParticle and
211  // there is no associated track
212  std::vector<unsigned short> pfpToTrkID;
213 
214  // characterize the match between clusters in 2 or 3 planes
215  struct MatchPars {
216  std::array<short, 3> Cls;
217  std::array<unsigned short, 3> End;
218  std::array<float, 3> Chg;
219  short Vtx;
220  float dWir; // wire difference at the matching end
221  float dAng; // angle difference at the matching end
222  float dX; // X difference
223  float Err; // Wire,Angle,Time match error
224  short oVtx;
225  float odWir; // wire difference at the other end
226  float odAng; // angle difference at the other end
227  float odX; // time difference at the other end
228  float dSP; // space point difference
229  float oErr; // dAngle dX match error
230  };
231  // vector of many match combinations
232  std::vector<MatchPars> matcomb;
233 
235  geo::TPCID const& tpcid) const;
236 
237  void PrintTracks() const;
238 
239  void MakeClusterChains(detinfo::DetectorPropertiesData const& detProp,
240  art::FindManyP<recob::Hit> const& fmCluHits,
241  geo::TPCID const& tpcid);
242  float dXClTraj(art::FindManyP<recob::Hit> const& fmCluHits,
243  unsigned short ipl,
244  unsigned short icl1,
245  unsigned short end1);
246  void FillChgNear(detinfo::DetectorPropertiesData const& detProp, geo::TPCID const& tpcid);
247  void FillWireHitRange(geo::TPCID const& tpcid);
248 
249  // Find clusters that point to vertices but do not have a
250  // cluster-vertex association made by ClusterCrawler
251  void FindMaybeVertices(geo::TPCID const& tpcid);
252  // match clusters associated with vertices
253  void VtxMatch(detinfo::DetectorPropertiesData const& detProp,
254  art::FindManyP<recob::Hit> const& fmCluHits,
255  geo::TPCID const& tpcid);
256  // match clusters in all planes
257  void PlnMatch(detinfo::DetectorPropertiesData const& detProp, geo::TPCID const& tpcid);
258  // match clusters in all planes
259  void AngMatch(art::FindManyP<recob::Hit> const& fmCluHits);
260 
261  // Make the track/vertex and mother/daughter relationships
262  void MakeFamily(geo::TPCID const& tpcid);
263  void TagCosmics(geo::TPCID const& tpcid);
264 
265  void FitVertices(detinfo::DetectorPropertiesData const& detProp, geo::TPCID const& tpcid);
266 
267  // fill the end matching parameters in the MatchPars struct
268  void FillEndMatch(detinfo::DetectorPropertiesData const& detProp,
269  geo::TPCID const& tpcid,
270  MatchPars& match);
271  // 2D version
272  void FillEndMatch2(MatchPars& match);
273 
274  float ChargeAsym(std::array<float, 3>& mChg);
275 
276  bool FindMissingCluster(unsigned short kpl,
277  short& kcl,
278  unsigned short& kend,
279  float kWir,
280  float kX,
281  float okWir,
282  float okX);
283 
284  bool DupMatch(MatchPars& match, unsigned short nplanes);
285 
286  void SortMatches(detinfo::DetectorPropertiesData const& detProp,
287  art::FindManyP<recob::Hit> const& fmCluHits,
288  unsigned short procCode,
289  geo::TPCID const& tpcid);
290 
291  // fill the trkHits array using information.
292  void FillTrkHits(art::FindManyP<recob::Hit> const& fmCluHits,
293  unsigned short imat,
294  unsigned short nplanes);
295 
296  // store the track in the trk vector
297  void StoreTrack(detinfo::DetectorPropertiesData const& detProp,
298  art::FindManyP<recob::Hit> const& fmCluHits,
299  unsigned short imat,
300  unsigned short procCode,
301  geo::TPCID const& tpcid);
302 
303  // returns the charge along the line between (wire1, time1) and (wire2, time2)
304  float ChargeNear(geo::PlaneID const& planeid,
305  unsigned short wire1,
306  float time1,
307  unsigned short wire2,
308  float time2);
309 
310  // inflate cuts at large angle
311  float AngleFactor(float slope);
312 
313  }; // class CCTrackMaker
314 
315  //-------------------------------------------------
316  CCTrackMaker::CCTrackMaker(fhicl::ParameterSet const& pset) : EDProducer{pset}
317  {
318  fHitModuleLabel = pset.get<std::string>("HitModuleLabel");
319  fClusterModuleLabel = pset.get<std::string>("ClusterModuleLabel");
320  fVertexModuleLabel = pset.get<std::string>("VertexModuleLabel");
321  // track matching
322  fMatchAlgs = pset.get<std::vector<short>>("MatchAlgs");
323  fXMatchErr = pset.get<std::vector<float>>("XMatchErr");
324  fAngleMatchErr = pset.get<std::vector<float>>("AngleMatchErr");
325  fChgAsymFactor = pset.get<std::vector<float>>("ChgAsymFactor");
326  fMatchMinLen = pset.get<std::vector<float>>("MatchMinLen");
327  fMakeAlgTracks = pset.get<std::vector<bool>>("MakeAlgTracks");
328  // Cluster merging
329  fMaxDAng = pset.get<float>("MaxDAng");
330  fChainMaxdX = pset.get<float>("ChainMaxdX");
331  fChainVtxAng = pset.get<float>("ChainVtxAng");
332  fMergeChgAsym = pset.get<float>("MergeChgAsym");
333  // Cosmic ray tagging
334  fFiducialCut = pset.get<float>("FiducialCut");
335  fDeltaRayCut = pset.get<float>("DeltaRayCut");
336  // make PFParticles
337  fMakePFPs = pset.get<bool>("MakePFPs");
338  // vertex fitting
339  fNVtxTrkHitsFit = pset.get<unsigned short>("NVtxTrkHitsFit");
340  fHitFitErrFac = pset.get<float>("HitFitErrFac");
341  // uB code
342  fuBCode = pset.get<bool>("uBCode");
343  // debugging inputs
344  fDebugAlg = pset.get<short>("DebugAlg");
345  fDebugPlane = pset.get<short>("DebugPlane");
346  fDebugCluster = pset.get<short>("DebugCluster");
347  fPrintAllClusters = pset.get<bool>("PrintAllClusters");
348 
349  // Consistency check
350  if (fMatchAlgs.size() > fXMatchErr.size() || fMatchAlgs.size() > fAngleMatchErr.size() ||
351  fMatchAlgs.size() > fChgAsymFactor.size() || fMatchAlgs.size() > fMatchMinLen.size() ||
352  fMatchAlgs.size() > fMakeAlgTracks.size()) {
353  mf::LogError("CCTM") << "Incompatible fcl input vector sizes";
354  return;
355  }
356  // Reality check
357  for (unsigned short ii = 0; ii < fMatchAlgs.size(); ++ii) {
358  if (fAngleMatchErr[ii] <= 0 || fXMatchErr[ii] <= 0) {
359  mf::LogError("CCTM") << "Invalid matching parameters " << fAngleMatchErr[ii] << " "
360  << fXMatchErr[ii];
361  return;
362  }
363  } // ii
364 
365  produces<std::vector<recob::PFParticle>>();
366  produces<art::Assns<recob::PFParticle, recob::Track>>();
367  produces<art::Assns<recob::PFParticle, recob::Cluster>>();
368  produces<art::Assns<recob::PFParticle, recob::Seed>>();
369  produces<art::Assns<recob::PFParticle, recob::Vertex>>();
370  produces<std::vector<recob::Vertex>>();
371  produces<std::vector<recob::Track>>();
372  produces<art::Assns<recob::Track, recob::Hit>>();
373  produces<std::vector<recob::Seed>>();
374  }
375 
376  //------------------------------------------------------------------------------------//
378  {
379  fWirePitch = wireReadoutGeom.Plane({0, 0, 0}).WirePitch();
380 
381  auto tcol = std::make_unique<std::vector<recob::Track>>();
382  auto thassn = std::make_unique<art::Assns<recob::Track, recob::Hit>>();
383  auto vcol = std::make_unique<std::vector<recob::Vertex>>();
384  auto pcol = std::make_unique<std::vector<recob::PFParticle>>();
385  auto ptassn = std::make_unique<art::Assns<recob::PFParticle, recob::Track>>();
386  auto pcassn = std::make_unique<art::Assns<recob::PFParticle, recob::Cluster>>();
387  auto psassn = std::make_unique<art::Assns<recob::PFParticle, recob::Seed>>();
388  auto pvassn = std::make_unique<art::Assns<recob::PFParticle, recob::Vertex>>();
389 
390  // seed collection
391  auto scol = std::make_unique<std::vector<recob::Seed>>();
392  auto shassn = std::make_unique<art::Assns<recob::Seed, recob::Hit>>();
393 
394  // all hits
395  art::Handle<std::vector<recob::Hit>> allhitsListHandle;
396  // cluster list
397  art::Handle<std::vector<recob::Cluster>> clusterListHandle;
398  std::vector<art::Ptr<recob::Cluster>> clusterlist;
399  // ClusterCrawler Vertices
401  std::vector<art::Ptr<recob::Vertex>> vtxlist;
402 
403  // get Hits
404  allhits.clear();
405  if (evt.getByLabel(fHitModuleLabel, allhitsListHandle))
406  art::fill_ptr_vector(allhits, allhitsListHandle);
407 
408  // get Clusters
409  if (evt.getByLabel(fClusterModuleLabel, clusterListHandle))
410  art::fill_ptr_vector(clusterlist, clusterListHandle);
411  if (clusterlist.size() == 0) return;
412  // get cluster - hit associations
413  art::FindManyP<recob::Hit> fmCluHits(clusterListHandle, evt, fClusterModuleLabel);
414 
415  // get Vertices
416  if (evt.getByLabel(fVertexModuleLabel, VtxListHandle))
417  art::fill_ptr_vector(vtxlist, VtxListHandle);
419 
420  std::vector<CluLen> clulens;
421 
422  unsigned short itr, tID, tIndex;
423 
424  // maximum error (see MakeClusterChains) for considering clusters broken
425  fMaxMergeError = 30;
426  // Cut on the error for a definitely broken cluster. Clusters with fMergeErrorCut < MergeError < fMaxMergeError
427  // are possibly broken clusters but we will consider these when matching between planes
428  fMergeErrorCut = 10;
429 
430  std::vector<art::Ptr<recob::Hit>> tmpHits;
431  std::vector<art::Ptr<recob::Cluster>> tmpCls;
432  std::vector<art::Ptr<recob::Vertex>> tmpVtx;
433 
434  // vector for PFParticle constructor
435  std::vector<size_t> dtrIndices;
436 
437  // some vectors for recob::Seed
438  double sPos[3], sDir[3];
439  double sErr[3] = {0, 0, 0};
440 
441  auto const detProp =
443 
444  // check consistency between clusters and associated hits
445  std::vector<art::Ptr<recob::Hit>> clusterhits;
446  for (std::size_t icl = 0; icl < clusterlist.size(); ++icl) {
447  auto const ipl = clusterlist[icl]->Plane().Plane;
448  clusterhits = fmCluHits.at(icl);
449  if (clusterhits[0]->WireID().Wire != std::nearbyint(clusterlist[icl]->EndWire())) {
450  std::cout << "CCTM Cluster-Hit End wire mis-match " << clusterhits[0]->WireID().Wire
451  << " vs " << std::nearbyint(clusterlist[icl]->EndWire()) << " Bail out! \n";
452  return;
453  }
454  for (unsigned short iht = 0; iht < clusterhits.size(); ++iht) {
455  if (clusterhits[iht]->WireID().Plane != ipl) {
456  std::cout << "CCTM Cluster-Hit plane mis-match " << ipl << " vs "
457  << clusterhits[iht]->WireID().Plane << " on hit " << iht << " Bail out! \n";
458  return;
459  } // hit-cluster plane mis-match
460  } // iht
461  } // icl
462  // end check consistency
463 
464  vtx.clear();
465  trk.clear();
466  for (auto const& tpcid : geom->Iterate<geo::TPCID>()) {
467  unsigned int const nplanes = wireReadoutGeom.Nplanes(tpcid);
468  if (nplanes > 3) continue;
469  for (std::size_t ipl = 0; ipl < 3; ++ipl) {
470  cls[ipl].clear();
471  clsChain[ipl].clear();
472  trkHits[ipl].clear();
473  } // ipl
474  // FillWireHitRange also calculates the charge in each plane
475  FillWireHitRange(tpcid);
476  for (auto const& planeid : wireReadoutGeom.Iterate<geo::PlaneID>(tpcid)) {
477  clulens.clear();
478  // sort clusters by increasing End wire number
479  for (std::size_t icl = 0; icl < clusterlist.size(); ++icl) {
480  if (clusterlist[icl]->Plane() != planeid) continue;
481  CluLen clulen;
482  clulen.index = icl;
483  clulen.length = clusterlist[icl]->EndWire();
484  clulens.push_back(clulen);
485  }
486  if (clulens.empty()) continue;
487  // sort clusters
488  std::sort(clulens.begin(), clulens.end(), lessThan);
489  if (clulens.empty()) continue;
490  for (unsigned short ii = 0; ii < clulens.size(); ++ii) {
491  const unsigned short icl = clulens[ii].index;
492  clPar clstr;
493  clstr.EvtIndex = icl;
494  recob::Cluster const& cluster = *(clusterlist[icl]);
495  // Begin info -> end index 1 (DS)
496  clstr.Wire[1] = cluster.StartWire();
497  clstr.Time[1] = cluster.StartTick();
498  clstr.X[1] = (float)detProp.ConvertTicksToX(cluster.StartTick(), planeid);
499  clstr.Angle[1] = cluster.StartAngle();
500  clstr.Slope[1] = std::tan(cluster.StartAngle());
501  clstr.Dir[1] = 0;
502  clstr.Charge[1] = ChgNorm[planeid.Plane] * cluster.StartCharge();
503  // this will be filled later
504  clstr.ChgNear[1] = 0;
505  clstr.VtxIndex[1] = -1;
506  clstr.mVtxIndex[1] = -1;
507  clstr.BrkIndex[1] = -1;
508  clstr.MergeError[1] = fMaxMergeError;
509  // End info -> end index 0 (US)
510  clstr.Wire[0] = cluster.EndWire();
511  clstr.Time[0] = cluster.EndTick();
512  clstr.X[0] = (float)detProp.ConvertTicksToX(cluster.EndTick(), planeid);
513  clstr.Angle[0] = cluster.EndAngle();
514  clstr.Slope[0] = std::tan(cluster.EndAngle());
515  clstr.Dir[0] = 0;
516  if (clstr.Time[1] > clstr.Time[0]) {
517  clstr.Dir[0] = 1;
518  clstr.Dir[1] = -1;
519  }
520  else {
521  clstr.Dir[0] = -1;
522  clstr.Dir[1] = 1;
523  }
524  clstr.Charge[0] = ChgNorm[planeid.Plane] * cluster.EndCharge();
525  // this will be filled later
526  clstr.ChgNear[1] = 0;
527  clstr.VtxIndex[0] = -1;
528  clstr.mVtxIndex[0] = -1;
529  clstr.BrkIndex[0] = -1;
530  clstr.MergeError[0] = fMaxMergeError;
531  // other info
532  clstr.InTrack = -1;
533  clstr.Length = (unsigned short)(0.5 + clstr.Wire[1] - clstr.Wire[0]);
534  clstr.TotChg = ChgNorm[planeid.Plane] * cluster.Integral();
535  if (clstr.TotChg <= 0) clstr.TotChg = 1;
536  clusterhits = fmCluHits.at(icl);
537  if (clusterhits.size() == 0) {
538  mf::LogError("CCTM") << "No associated cluster hits " << icl;
539  continue;
540  }
541  // correct charge for missing cluster hits
542  clstr.TotChg *= clstr.Length / (float)clusterhits.size();
543  cls[planeid.Plane].push_back(clstr);
544  } // ii (icl)
545  } // planeid
546 
547  FillChgNear(detProp, tpcid);
548 
549  // and finally the vertices
550  double xyz[3];
551  for (unsigned short ivx = 0; ivx < vtxlist.size(); ++ivx) {
552  vtxPar aVtx;
553  aVtx.ID = ivx + 1;
554  aVtx.EvtIndex = ivx;
555  vtxlist[ivx]->XYZ(xyz);
556  aVtx.X = xyz[0];
557  aVtx.Y = xyz[1];
558  aVtx.Z = xyz[2];
559  aVtx.nClusInPln[0] = 0;
560  aVtx.nClusInPln[1] = 0;
561  aVtx.nClusInPln[2] = 0;
562  std::vector<art::Ptr<recob::Cluster>> const& vtxCls = fmVtxCls.at(ivx);
563  std::vector<const unsigned short*> const& vtxClsEnd = fmVtxCls.data(ivx);
564  for (unsigned short vcass = 0; vcass < vtxCls.size(); ++vcass) {
565  auto const icl = vtxCls[vcass].key();
566  // the cluster plane
567  auto const ipl = vtxCls[vcass]->Plane().Plane;
568  auto end = *vtxClsEnd[vcass];
569  if (end > 1)
570  throw cet::exception("CCTM")
571  << "Invalid end data from vertex - cluster association" << end;
572  bool gotit = false;
573  // CCTM end is opposite of CC end
574  end = 1 - end;
575  for (unsigned short jcl = 0; jcl < cls[ipl].size(); ++jcl) {
576  if (cls[ipl][jcl].EvtIndex == icl) {
577  cls[ipl][jcl].VtxIndex[end] = ivx;
578  ++aVtx.nClusInPln[ipl];
579  gotit = true;
580  break;
581  } // index check
582  } // jcl
583  if (!gotit)
584  throw cet::exception("CCTM") << "Bad index from vertex - cluster association" << icl;
585  } // icl
586  vtx.push_back(aVtx);
587  } // ivx
588  // Find broken clusters
589  MakeClusterChains(detProp, fmCluHits, tpcid);
590  FindMaybeVertices(tpcid);
591 
592  // call algorithms in the specified order
593  matcomb.clear();
594  for (algIndex = 0; algIndex < fMatchAlgs.size(); ++algIndex) {
595  if (fMatchAlgs[algIndex] == 1) {
596  prt = (fDebugAlg == 1);
597  VtxMatch(detProp, fmCluHits, tpcid);
598  if (fMakeAlgTracks[algIndex]) SortMatches(detProp, fmCluHits, 1, tpcid);
599  }
600  else if (fMatchAlgs[algIndex] == 2) {
601  prt = (fDebugAlg == 2);
602  PlnMatch(detProp, tpcid);
603  if (fMakeAlgTracks[algIndex]) SortMatches(detProp, fmCluHits, 2, tpcid);
604  }
605  if (prt) PrintClusters(detProp, tpcid);
606  } // algIndex
607  prt = false;
608  pfpToTrkID.clear();
609  // Determine the vertex/track hierarchy
610  if (fMakePFPs) {
611  TagCosmics(tpcid);
612  MakeFamily(tpcid);
613  }
614  FitVertices(detProp, tpcid);
615 
616  // Make PFParticles -> tracks
617  for (unsigned short ipf = 0; ipf < pfpToTrkID.size(); ++ipf) {
618  // trackID of this PFP
619  tID = pfpToTrkID[ipf];
620  if (tID > trk.size() + 1) {
621  mf::LogWarning("CCTM") << "Bad track ID";
622  continue;
623  }
624  dtrIndices.clear();
625  // load the daughter PFP indices
626  mf::LogVerbatim("CCTM") << "PFParticle " << ipf << " tID " << tID;
627  for (unsigned short jpf = 0; jpf < pfpToTrkID.size(); ++jpf) {
628  itr = pfpToTrkID[jpf] - 1; // convert from track ID to track index
629  if (trk[itr].MomID == tID) dtrIndices.push_back(jpf);
630  if (trk[itr].MomID == tID)
631  mf::LogVerbatim("CCTM") << " dtr jpf " << jpf << " itr " << itr;
632  } // jpf
633  unsigned short parentIndex = USHRT_MAX;
634  if (tID == 0) {
635  // make neutrino PFP USHRT_MAX == primary PFP
636  recob::PFParticle pfp(14, ipf, parentIndex, dtrIndices);
637  pcol->emplace_back(std::move(pfp));
638  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
639  if (!vtx[ivx].Neutrino) continue;
640  // make the vertex
641  xyz[0] = vtx[ivx].X;
642  xyz[1] = vtx[ivx].Y;
643  xyz[2] = vtx[ivx].Z;
644  size_t vStart = vcol->size();
645  recob::Vertex vertex(xyz, vtx[ivx].ID);
646  vcol->emplace_back(std::move(vertex));
647  size_t vEnd = vcol->size();
648  // PFParticle - vertex association
649  util::CreateAssn(evt, *pcol, *vcol, *pvassn, vStart, vEnd);
650  vtx[ivx].ID = -vtx[ivx].ID;
651  break;
652  } // ivx
653  }
654  else if (tID > 0) {
655  // make non-neutrino PFP. Need to find the parent PFP index
656  // Track index of this PFP
657  tIndex = tID - 1;
658  // trackID of this PFP parent
659  for (unsigned short ii = 0; ii < pfpToTrkID.size(); ++ii) {
660  if (pfpToTrkID[ii] == trk[tIndex].MomID) {
661  parentIndex = ii;
662  break;
663  }
664  } // ii
665  // PFParticle
666  mf::LogVerbatim("CCTM") << "daughters tID " << tID << " pdg " << trk[tIndex].PDGCode
667  << " ipf " << ipf << " parentIndex " << parentIndex
668  << " dtr size " << dtrIndices.size();
669  recob::PFParticle pfp(trk[tIndex].PDGCode, ipf, parentIndex, dtrIndices);
670  pcol->emplace_back(std::move(pfp));
671  // track
672  // make the track
673  size_t tStart = tcol->size();
677  recob::Track::Flags_t(trk[itr].TrjPos.size()),
678  false),
679  0,
680  -1.,
681  0,
684  tID);
685  tcol->emplace_back(std::move(track));
686  size_t tEnd = tcol->size();
687  // PFParticle - track association
688  util::CreateAssn(evt, *pcol, *tcol, *ptassn, tStart, tEnd);
689  // flag this track as already put in the event
690  trk[tIndex].ID = -trk[tIndex].ID;
691  // track -> hits association
692  tmpHits.clear();
693  for (unsigned int ipl = 0; ipl < nplanes; ++ipl)
694  tmpHits.insert(
695  tmpHits.end(), trk[tIndex].TrkHits[ipl].begin(), trk[tIndex].TrkHits[ipl].end());
696  util::CreateAssn(evt, *tcol, tmpHits, *thassn);
697  // Find seed hits and the end of the track that is best
698  unsigned int end = 0;
699  unsigned short itj = 0;
700  if (end > 0) itj = trk[tIndex].TrjPos.size() - 1;
701  for (unsigned short ii = 0; ii < 3; ++ii) {
702  sPos[ii] = trk[tIndex].TrjPos[itj](ii);
703  sDir[ii] = trk[tIndex].TrjDir[itj](ii);
704  } // ii
705  size_t sStart = scol->size();
706  recob::Seed seed(sPos, sDir, sErr, sErr);
707  scol->emplace_back(std::move(seed));
708  size_t sEnd = scol->size();
709  // PFP-seed association
710  util::CreateAssn(evt, *pcol, *scol, *psassn, sStart, sEnd);
711  // Seed-hit association
712  tmpHits.clear();
713  for (unsigned int ipl = 0; ipl < nplanes; ++ipl)
714  tmpHits.insert(tmpHits.end(), seedHits[ipl].begin(), seedHits[ipl].end());
715  util::CreateAssn(evt, *scol, tmpHits, *shassn);
716  // cluster association
717  // PFP-cluster association
718  tmpCls.clear();
719  for (unsigned short ii = 0; ii < trk[tIndex].ClsEvtIndices.size(); ++ii) {
720  auto const icl = trk[tIndex].ClsEvtIndices[ii];
721  tmpCls.push_back(clusterlist[icl]);
722  } // ii
723  util::CreateAssn(evt, *pcol, tmpCls, *pcassn);
724  } // non-neutrino PFP
725  } // ipf
726  // make non-PFP tracks
727  for (unsigned short itr = 0; itr < trk.size(); ++itr) {
728  // ignore already saved tracks
729  if (trk[itr].ID < 0) continue;
733  recob::Track::Flags_t(trk[itr].TrjPos.size()),
734  false),
735  0,
736  -1.,
737  0,
740  trk[itr].ID);
741  tcol->emplace_back(std::move(track));
742  tmpHits.clear();
743  for (unsigned int ipl = 0; ipl < nplanes; ++ipl)
744  tmpHits.insert(tmpHits.end(), trk[itr].TrkHits[ipl].begin(), trk[itr].TrkHits[ipl].end());
745  util::CreateAssn(evt, *tcol, tmpHits, *thassn);
746  } // itr
747  // make remnant vertices
748  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
749  if (vtx[ivx].ID < 0) continue;
750  xyz[0] = vtx[ivx].X;
751  xyz[1] = vtx[ivx].Y;
752  xyz[2] = vtx[ivx].Z;
753  recob::Vertex vertex(xyz, vtx[ivx].ID);
754  vcol->emplace_back(std::move(vertex));
755  }
756  if (fDebugAlg > 0) PrintTracks();
757 
758  double orphanLen = 0;
759  for (unsigned int ipl = 0; ipl < nplanes; ++ipl) {
760  for (std::size_t icl = 0; icl < cls[ipl].size(); ++icl) {
761  if (cls[ipl][icl].Length > 40 && cls[ipl][icl].InTrack < 0) {
762  orphanLen += cls[ipl][icl].Length;
763  // unused cluster
764  mf::LogVerbatim("CCTM")
765  << "Orphan long cluster " << ipl << ":" << icl << ":" << cls[ipl][icl].Wire[0] << ":"
766  << (int)cls[ipl][icl].Time[0] << " length " << cls[ipl][icl].Length;
767  }
768  } // icl
769 
770  cls[ipl].clear();
771  clsChain[ipl].clear();
772  std::cout << "Total orphan length " << orphanLen << "\n";
773  trkHits[ipl].clear();
774  seedHits[ipl].clear();
775  vxCls[ipl].clear();
776  } // ipl
777  } // tpcs
778 
779  evt.put(std::move(pcol));
780  evt.put(std::move(ptassn));
781  evt.put(std::move(pcassn));
782  evt.put(std::move(pvassn));
783  evt.put(std::move(psassn));
784  evt.put(std::move(tcol));
785  evt.put(std::move(thassn));
786  evt.put(std::move(scol));
787  evt.put(std::move(vcol));
788 
789  // final cleanup
790  vtx.clear();
791  trk.clear();
792  allhits.clear();
793  matcomb.clear();
794  pfpToTrkID.clear();
795 
796  } // produce
797 
800  geo::TPCID const& tpcid)
801  {
802  std::vector<std::vector<geo::WireID>> hitWID;
803  std::vector<std::vector<double>> hitX;
804  std::vector<std::vector<double>> hitXErr;
805  TVector3 pos, posErr;
806  std::vector<TVector3> trkDir;
807  std::vector<TVector3> trkDirErr;
808  float ChiDOF;
809 
810  if (fNVtxTrkHitsFit == 0) return;
811 
812  unsigned short indx, indx2, iht, nHitsFit;
813 
814  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
815  if (!vtx[ivx].Neutrino) continue;
816  hitWID.clear();
817  hitX.clear();
818  hitXErr.clear();
819  trkDir.clear();
820  // find the tracks associated with this vertex
821  unsigned int thePln, theTPC, theCst;
822  for (unsigned short itk = 0; itk < trk.size(); ++itk) {
823  for (unsigned short end = 0; end < 2; ++end) {
824  if (trk[itk].VtxIndex[end] != ivx) continue;
825  unsigned short itj = 0;
826  if (end == 1) itj = trk[itk].TrjPos.size() - 1;
827  // increase the size of the hit vectors by 1 for this track
828  indx = hitX.size();
829  hitWID.resize(indx + 1);
830  hitX.resize(indx + 1);
831  hitXErr.resize(indx + 1);
832  trkDir.resize(indx + 1);
833  trkDir[indx] = trk[itk].TrjDir[itj];
834  auto const nplanes = wireReadoutGeom.Nplanes(tpcid);
835  for (unsigned short ipl = 0; ipl < nplanes; ++ipl) {
836  if (trk[itk].TrkHits[ipl].size() < 2) continue;
837  // make slots for the hits on this track in this plane
838  nHitsFit = trk[itk].TrkHits[ipl].size();
839  if (nHitsFit > fNVtxTrkHitsFit) nHitsFit = fNVtxTrkHitsFit;
840  indx2 = hitWID[indx].size();
841  hitWID[indx].resize(indx2 + nHitsFit);
842  hitX[indx].resize(indx2 + nHitsFit);
843  hitXErr[indx].resize(indx2 + nHitsFit);
844  for (unsigned short ii = 0; ii < nHitsFit; ++ii) {
845  if (end == 0) { iht = ii; }
846  else {
847  iht = trk[itk].TrkHits[ipl].size() - ii - 1;
848  }
849  hitWID[indx][indx2 + ii] = trk[itk].TrkHits[ipl][iht]->WireID();
850  thePln = trk[itk].TrkHits[ipl][iht]->WireID().Plane;
851  theTPC = trk[itk].TrkHits[ipl][iht]->WireID().TPC;
852  theCst = trk[itk].TrkHits[ipl][iht]->WireID().Cryostat;
853  hitX[indx][indx2 + ii] = detProp.ConvertTicksToX(
854  trk[itk].TrkHits[ipl][iht]->PeakTime(), thePln, theTPC, theCst);
855  hitXErr[indx][indx2 + ii] = fHitFitErrFac * trk[itk].TrkHits[ipl][iht]->RMS();
856  } // ii
857  } // ipl
858  } // end
859  } // itk
860  if (hitX.size() < 2) {
861  mf::LogVerbatim("CCTM") << "Not enough hits to fit vtx " << ivx;
862  continue;
863  } // hitX.size() < 2
864  pos(0) = vtx[ivx].X;
865  pos(1) = vtx[ivx].Y;
866  pos(2) = vtx[ivx].Z;
867  fVertexFitAlg.VertexFit(hitWID, hitX, hitXErr, pos, posErr, trkDir, trkDirErr, ChiDOF);
868  if (ChiDOF > 3000) continue;
869  // update the vertex position
870  vtx[ivx].X = pos(0);
871  vtx[ivx].Y = pos(1);
872  vtx[ivx].Z = pos(2);
873  // and the track trajectory
874  unsigned short fitTrk = 0;
875  for (unsigned short itk = 0; itk < trk.size(); ++itk) {
876  for (unsigned short end = 0; end < 2; ++end) {
877  if (trk[itk].VtxIndex[end] != ivx) continue;
878  unsigned short itj = 0;
879  if (end == 1) itj = trk[itk].TrjPos.size() - 1;
880  trk[itk].TrjDir[itj] = trkDir[fitTrk];
881  ++fitTrk;
882  } // end
883  } // itk
884  } // ivx
885  } // FitVertices
886 
889  geo::TPCID const& tpcid)
890  {
891  // fills the CHgNear array
892 
893  unsigned short wire, nwires, indx;
894  float dir, ctime, cx, chgWinLo, chgWinHi;
895  float cnear;
896 
897  for (auto const& planeid : wireReadoutGeom.Iterate<geo::PlaneID>(tpcid)) {
898  auto const ipl = planeid.Plane;
899  for (unsigned short icl = 0; icl < cls[ipl].size(); ++icl) {
900  // find the nearby charge at the US and DS ends
901  nwires = cls[ipl][icl].Length / 2;
902  if (nwires < 2) continue;
903  // maximum of 30 wires for long clusters
904  if (nwires > 30) nwires = 30;
905  for (unsigned short end = 0; end < 2; ++end) {
906  cnear = 0;
907  // direction for adding/subtracting wire numbers
908  dir = 1 - 2 * end;
909  for (unsigned short w = 0; w < nwires; ++w) {
910  wire = cls[ipl][icl].Wire[end] + dir * w;
911  cx = cls[ipl][icl].X[end] + dir * w * cls[ipl][icl].Slope[end] * fWirePitch;
912  ctime = detProp.ConvertXToTicks(cx, planeid);
913  chgWinLo = ctime - fChgWindow;
914  chgWinHi = ctime + fChgWindow;
915  indx = wire - firstWire[ipl];
916  if (WireHitRange[ipl][indx].first < 0) continue;
917  unsigned int firhit = WireHitRange[ipl][indx].first;
918  unsigned int lashit = WireHitRange[ipl][indx].second;
919  for (unsigned int hit = firhit; hit < lashit; ++hit) {
920  if (hit > allhits.size() - 1) {
921  mf::LogError("CCTM")
922  << "FillChgNear bad lashit " << lashit << " size " << allhits.size() << "\n";
923  continue;
924  }
925  if (allhits[hit]->PeakTime() < chgWinLo) continue;
926  if (allhits[hit]->PeakTime() > chgWinHi) continue;
927  cnear += allhits[hit]->Integral();
928  } // hit
929  } // w
930  cnear /= (float)(nwires - 1);
931  if (cnear > cls[ipl][icl].Charge[end]) {
932  cls[ipl][icl].ChgNear[end] = ChgNorm[ipl] * cnear / cls[ipl][icl].Charge[end];
933  }
934  else {
935  cls[ipl][icl].ChgNear[end] = 0;
936  }
937  } // end
938  } // icl
939  } // ipl
940 
941  } // FillChgNear
942 
945  {
946  // define the track/vertex and mother/daughter relationships
947 
948  unsigned short ivx, itr, ipl, ii, jtr;
949  unsigned short nus, nds, nuhs, ndhs;
950  float longUSTrk, longDSTrk, qual;
951 
952  // min distance^2 between a neutrino vertex candidate and a through
953  // going muon
954  float tgMuonCut2 = 9;
955 
956  // struct for neutrino vertex candidates
957  struct NuVtx {
958  unsigned short VtxIndex;
959  unsigned short nUSTk;
960  unsigned short nDSTk;
961  unsigned short nUSHit;
962  unsigned short nDSHit;
963  float longUSTrk;
964  float longDSTrk;
965  float Qual;
966  };
967  std::vector<NuVtx> nuVtxCand;
968 
969  NuVtx aNuVtx;
970 
971  // analyze all of the vertices
972  float best = 999, dx, dy, dz, dr;
973  short imbest = -1;
974  bool skipVtx;
975  unsigned short itj;
976  auto const nplanes = wireReadoutGeom.Nplanes(tpcid);
977  for (ivx = 0; ivx < vtx.size(); ++ivx) {
978  vtx[ivx].Neutrino = false;
979  nus = 0;
980  nds = 0;
981  nuhs = 0;
982  ndhs = 0;
983  longUSTrk = 0;
984  longDSTrk = 0;
985  skipVtx = false;
986  // skip vertices that are close to through-going muons
987  for (itr = 0; itr < trk.size(); ++itr) {
988  if (trk[itr].ID < 0) continue;
989  if (trk[itr].PDGCode != 13) continue;
990  for (itj = 0; itj < trk[itr].TrjPos.size(); ++itj) {
991  dx = trk[itr].TrjPos[itj](0) - vtx[ivx].X;
992  dy = trk[itr].TrjPos[itj](1) - vtx[ivx].Y;
993  dz = trk[itr].TrjPos[itj](2) - vtx[ivx].Z;
994  dr = dx * dx + dy * dy + dz * dz;
995  if (dr < tgMuonCut2) {
996  skipVtx = true;
997  break;
998  }
999  if (skipVtx) break;
1000  } // itj
1001  if (skipVtx) break;
1002  } // itr
1003  if (skipVtx) continue;
1004  for (itr = 0; itr < trk.size(); ++itr) {
1005  if (trk[itr].ID < 0) continue;
1006  if (trk[itr].VtxIndex[0] == ivx) {
1007  // DS-going track
1008  ++nds;
1009  if (trk[itr].Length > longDSTrk) longDSTrk = trk[itr].Length;
1010  for (ipl = 0; ipl < nplanes; ++ipl)
1011  ndhs += trk[itr].TrkHits[ipl].size();
1012  } // DS-going track
1013  // Reject this vertex as a neutrino candidate if the track being
1014  // considered has a starting vertex
1015  if (trk[itr].VtxIndex[1] == ivx && trk[itr].VtxIndex[0] >= 0) {
1016  skipVtx = true;
1017  break;
1018  } // trk[itr].VtxIndex[0] > 0
1019  if (trk[itr].VtxIndex[1] == ivx && trk[itr].VtxIndex[0] < 0) {
1020  // US-going track w no US vertex
1021  ++nus;
1022  if (trk[itr].Length > longUSTrk) longUSTrk = trk[itr].Length;
1023  for (ipl = 0; ipl < nplanes; ++ipl)
1024  nuhs += trk[itr].TrkHits[ipl].size();
1025  } // US-going track
1026  } // itr
1027  if (skipVtx) continue;
1028  if (nds == 0) continue;
1029  qual = 1 / (float)nds;
1030  qual /= (float)ndhs;
1031  if (nus > 0) qual *= (float)nuhs / (float)ndhs;
1032  if (qual < best) {
1033  best = qual;
1034  imbest = ivx;
1035  }
1036  if (nds > 0 && longDSTrk > 5) {
1037  // at least one longish track going DS
1038  aNuVtx.VtxIndex = ivx;
1039  aNuVtx.nUSTk = nus;
1040  aNuVtx.nDSTk = nds;
1041  aNuVtx.nUSHit = nuhs;
1042  aNuVtx.nDSHit = ndhs;
1043  aNuVtx.longUSTrk = longUSTrk;
1044  aNuVtx.longDSTrk = longDSTrk;
1045  aNuVtx.Qual = qual;
1046  nuVtxCand.push_back(aNuVtx);
1047  }
1048  } // ivx
1049  if (imbest < 0) return;
1050 
1051  // Found the neutrino interaction vertex
1052  ivx = imbest;
1053  vtx[ivx].Neutrino = true;
1054  // Put a 0 into the PFParticle -> track vector to identify a neutrino
1055  // track with no trajectory or hits
1056  pfpToTrkID.push_back(0);
1057 
1058  // list of DS-going current generation daughters so we can fix next
1059  // generation daughter assignments. This code section assumes that there
1060  // are no decays or 2ndry interactions with US-going primary tracks.
1061  std::vector<unsigned short> dtrGen;
1062  std::vector<unsigned short> dtrNextGen;
1063  for (itr = 0; itr < trk.size(); ++itr) {
1064  if (trk[itr].ID < 0) continue;
1065  if (trk[itr].VtxIndex[0] == ivx) {
1066  // DS-going primary track
1067  trk[itr].MomID = 0;
1068  // call every track coming from a neutrino vertex a proton
1069  trk[itr].PDGCode = 2212;
1070  pfpToTrkID.push_back(trk[itr].ID);
1071  dtrGen.push_back(itr);
1072  } // DS-going primary track
1073  if (trk[itr].VtxIndex[1] == ivx) {
1074  // US-going primary track
1075  trk[itr].MomID = 0;
1076  // call every track coming from a neutrino vertex a proton
1077  trk[itr].PDGCode = 2212;
1078  pfpToTrkID.push_back(trk[itr].ID);
1079  // reverse the track trajectory
1080  std::reverse(trk[itr].TrjPos.begin(), trk[itr].TrjPos.end());
1081  for (ii = 0; ii < trk[itr].TrjDir.size(); ++ii)
1082  trk[itr].TrjDir[ii] = -trk[itr].TrjDir[ii];
1083  } // DS-going primary track
1084  } // itr
1085 
1086  if (dtrGen.size() == 0) return;
1087 
1088  unsigned short tmp, indx;
1089  unsigned short nit = 0;
1090 
1091  // follow daughters through all generations (< 10). Daughter tracks
1092  // may go US or DS
1093  while (nit < 10) {
1094  ++nit;
1095  dtrNextGen.clear();
1096  // Look for next generation daughters
1097  for (ii = 0; ii < dtrGen.size(); ++ii) {
1098  itr = dtrGen[ii];
1099  if (trk[itr].VtxIndex[1] >= 0) {
1100  // found a DS vertex
1101  ivx = trk[itr].VtxIndex[1];
1102  // look for a track associated with this vertex
1103  for (jtr = 0; jtr < trk.size(); ++jtr) {
1104  if (jtr == itr) continue;
1105  if (trk[jtr].VtxIndex[0] == ivx) {
1106  // DS-going track
1107  indx = trk[itr].DtrID.size();
1108  trk[itr].DtrID.resize(indx + 1);
1109  trk[itr].DtrID[indx] = jtr;
1110  trk[jtr].MomID = trk[itr].ID;
1111  // call all daughters pions
1112  trk[jtr].PDGCode = 211;
1113  dtrNextGen.push_back(jtr);
1114  pfpToTrkID.push_back(trk[jtr].ID);
1115  } // DS-going track
1116  if (trk[jtr].VtxIndex[1] == ivx) {
1117  // US-going track
1118  indx = trk[itr].DtrID.size();
1119  trk[itr].DtrID.resize(indx + 1);
1120  trk[itr].DtrID[indx] = jtr;
1121  trk[jtr].MomID = trk[itr].ID;
1122  // call all daughters pions
1123  trk[jtr].PDGCode = 211;
1124  dtrNextGen.push_back(jtr);
1125  pfpToTrkID.push_back(trk[jtr].ID);
1126  // reverse the track trajectory
1127  std::reverse(trk[jtr].TrjPos.begin(), trk[jtr].TrjPos.end());
1128  for (unsigned short jj = 0; jj < trk[jtr].TrjDir.size(); ++jj)
1129  trk[jtr].TrjDir[jj] = -trk[jtr].TrjDir[jj];
1130  // interchange the trk - vtx assignments
1131  tmp = trk[jtr].VtxIndex[0];
1132  trk[jtr].VtxIndex[0] = trk[jtr].VtxIndex[1];
1133  trk[jtr].VtxIndex[1] = tmp;
1134  } // DS-going track
1135  } // jtr
1136  } // trk[itr].VtxIndex[0] >= 0
1137  } // ii (itr)
1138  // break out if no next gen daughters found
1139  if (dtrNextGen.size() == 0) break;
1140  dtrGen = dtrNextGen;
1141  } // nit
1142 
1143  } // MakeFamily
1144 
1147  {
1148  // Make cosmic ray PFParticles
1149  unsigned short ipf, itj;
1150  bool skipit = true;
1151 
1152  // Y,Z limits of the detector
1153  const geo::TPCGeo& thetpc = geom->TPC(tpcid);
1154  auto const world = thetpc.GetCenter();
1155 
1156  float XLo = world.X() - thetpc.HalfWidth() + fFiducialCut;
1157  float XHi = world.X() + thetpc.HalfWidth() - fFiducialCut;
1158  float YLo = world.Y() - thetpc.HalfHeight() + fFiducialCut;
1159  float YHi = world.Y() + thetpc.HalfHeight() - fFiducialCut;
1160  float ZLo = world.Z() - thetpc.Length() / 2 + fFiducialCut;
1161  float ZHi = world.Z() + thetpc.Length() / 2 - fFiducialCut;
1162 
1163  bool startsIn, endsIn;
1164 
1165  for (unsigned short itk = 0; itk < trk.size(); ++itk) {
1166  // ignore already used tracks
1167  if (trk[itk].ID < 0) continue;
1168  // ignore short tracks (< 10 cm)
1169  if (trk[itk].Length < 10) continue;
1170  // check for already identified PFPs
1171  skipit = false;
1172  for (ipf = 0; ipf < pfpToTrkID.size(); ++ipf) {
1173  if (pfpToTrkID[ipf] == trk[itk].ID) {
1174  skipit = true;
1175  break;
1176  }
1177  } // ipf
1178  if (skipit) continue;
1179  startsIn = true;
1180  if (trk[itk].TrjPos[0](0) < XLo || trk[itk].TrjPos[0](0) > XHi) startsIn = false;
1181  if (trk[itk].TrjPos[0](1) < YLo || trk[itk].TrjPos[0](1) > YHi) startsIn = false;
1182  if (trk[itk].TrjPos[0](2) < ZLo || trk[itk].TrjPos[0](2) > ZHi) startsIn = false;
1183  if (startsIn) continue;
1184  endsIn = true;
1185  itj = trk[itk].TrjPos.size() - 1;
1186  if (trk[itk].TrjPos[itj](0) < XLo || trk[itk].TrjPos[itj](0) > XHi) endsIn = false;
1187  if (trk[itk].TrjPos[itj](1) < YLo || trk[itk].TrjPos[itj](1) > YHi) endsIn = false;
1188  if (trk[itk].TrjPos[itj](2) < ZLo || trk[itk].TrjPos[itj](2) > ZHi) endsIn = false;
1189  if (endsIn) continue;
1190  // call it a cosmic muon
1191  trk[itk].PDGCode = 13;
1192  pfpToTrkID.push_back(trk[itk].ID);
1193  } // itk
1194 
1195  if (fDeltaRayCut <= 0) return;
1196 
1197  for (unsigned short itk = 0; itk < trk.size(); ++itk) {
1198  // find a tagged cosmic ray
1199  if (trk[itk].PDGCode != 13) continue;
1200 
1201  } // itk
1202 
1203  } // TagCosmics
1204 
1207  art::FindManyP<recob::Hit> const& fmCluHits,
1208  geo::TPCID const& tpcid)
1209  {
1210  // Use vertex assignments to match clusters
1211  unsigned short ivx, ii, ipl, icl, jj, jpl, jcl, kk, kpl, kcl;
1212  short idir, iend, jdir, jend, kdir, kend, ioend;
1213 
1214  auto const nplanes = wireReadoutGeom.Nplanes(tpcid);
1215  for (ivx = 0; ivx < vtx.size(); ++ivx) {
1216 
1217  // list of cluster chains associated with this vertex in each plane
1218  for (ipl = 0; ipl < nplanes; ++ipl) {
1219  vxCls[ipl].clear();
1220  for (icl = 0; icl < clsChain[ipl].size(); ++icl) {
1221  if (clsChain[ipl][icl].InTrack >= 0) continue;
1222  for (iend = 0; iend < 2; ++iend) {
1223  if (clsChain[ipl][icl].VtxIndex[iend] == vtx[ivx].EvtIndex) vxCls[ipl].push_back(icl);
1224  } // end
1225  } // icl
1226  } // ipl
1227 
1228  if (prt) {
1229  mf::LogVerbatim myprt("CCTM");
1230  myprt << "VtxMatch: Vertex ID " << vtx[ivx].EvtIndex << "\n";
1231  for (ipl = 0; ipl < nplanes; ++ipl) {
1232  myprt << "ipl " << ipl << " cls";
1233  for (unsigned short ii = 0; ii < vxCls[ipl].size(); ++ii)
1234  myprt << " " << vxCls[ipl][ii];
1235  myprt << "\n";
1236  } // ipl
1237  } // prt
1238  // match between planes, requiring clusters matched to this vertex
1239  iend = 0;
1240  jend = 0;
1241  bool gotkcl;
1242  float totErr;
1243  for (ipl = 0; ipl < nplanes; ++ipl) {
1244  if (nplanes == 2 && ipl > 0) continue;
1245  for (ii = 0; ii < vxCls[ipl].size(); ++ii) {
1246  icl = vxCls[ipl][ii];
1247  // ignore used clusters
1248  if (clsChain[ipl][icl].InTrack >= 0) continue;
1249  jpl = (ipl + 1) % nplanes;
1250  kpl = (jpl + 1) % nplanes;
1251  for (jj = 0; jj < vxCls[jpl].size(); ++jj) {
1252  jcl = vxCls[jpl][jj];
1253  if (clsChain[jpl][jcl].InTrack >= 0) continue;
1254  for (iend = 0; iend < 2; ++iend) {
1255  if (clsChain[ipl][icl].VtxIndex[iend] != vtx[ivx].EvtIndex) continue;
1256  ioend = 1 - iend;
1257  idir = clsChain[ipl][icl].Dir[iend];
1258  for (jend = 0; jend < 2; ++jend) {
1259  if (clsChain[jpl][jcl].VtxIndex[jend] != vtx[ivx].EvtIndex) continue;
1260  jdir = clsChain[jpl][jcl].Dir[jend];
1261  if (idir != 0 && jdir != 0 && idir != jdir) continue;
1262  // ignore outrageously bad other end X matches
1263  if (fabs(clsChain[jpl][jcl].X[1 - jend] - clsChain[ipl][icl].X[ioend]) > 50)
1264  continue;
1265  MatchPars match;
1266  match.Cls[ipl] = icl;
1267  match.End[ipl] = iend;
1268  match.Cls[jpl] = jcl;
1269  match.End[jpl] = jend;
1270  match.Vtx = ivx;
1271  match.oVtx = -1;
1272  // set large so that DupMatch doesn't get confused when called before FillEndMatch
1273  match.Err = 1E6;
1274  match.oErr = 1E6;
1275  if (nplanes == 2) {
1276  FillEndMatch2(match);
1277  if (prt)
1278  mf::LogVerbatim("CCTM")
1279  << "FillEndMatch2: Err " << match.Err << " oErr " << match.oErr;
1280  if (match.Err + match.oErr > 100) continue;
1281  if (DupMatch(match, nplanes)) continue;
1282  matcomb.push_back(match);
1283  continue;
1284  }
1285  match.Cls[kpl] = -1;
1286  match.End[kpl] = 0;
1287  if (prt)
1288  mf::LogVerbatim("CCTM")
1289  << "VtxMatch: check " << ipl << ":" << icl << ":" << iend << " and " << jpl
1290  << ":" << jcl << ":" << jend << " for cluster in kpl " << kpl;
1291  gotkcl = false;
1292  for (kk = 0; kk < vxCls[kpl].size(); ++kk) {
1293  kcl = vxCls[kpl][kk];
1294  if (clsChain[kpl][kcl].InTrack >= 0) continue;
1295  for (kend = 0; kend < 2; ++kend) {
1296  kdir = clsChain[kpl][kcl].Dir[kend];
1297  if (idir != 0 && kdir != 0 && idir != kdir) continue;
1298  if (clsChain[kpl][kcl].VtxIndex[kend] != vtx[ivx].EvtIndex) continue;
1299  // rough check of other end match
1300  // TODO: SHOWER-LIKE CLUSTER CHECK
1301  match.Cls[kpl] = kcl;
1302  match.End[kpl] = kend;
1303  // first call to ignore redundant matches
1304  if (DupMatch(match, nplanes)) continue;
1305  FillEndMatch(detProp, tpcid, match);
1306  // ignore if no signal at the other end
1307  if (match.Chg[kpl] <= 0) continue;
1308  if (match.Err + match.oErr > 100) continue;
1309  // second call to keep matches with better error
1310  if (DupMatch(match, nplanes)) continue;
1311  matcomb.push_back(match);
1312  gotkcl = true;
1313  // break;
1314  } // kend
1315  } // kk -> kcl
1316  if (gotkcl) continue;
1317  // look for a cluster that missed the vertex assignment
1318  float best = 10;
1319  short kbst = -1;
1320  unsigned short kbend = 0;
1321  if (prt)
1322  mf::LogVerbatim("CCTM") << "VtxMatch: look for missed cluster chain in kpl";
1323  for (kcl = 0; kcl < clsChain[kpl].size(); ++kcl) {
1324  if (clsChain[kpl][kcl].InTrack >= 0) continue;
1325  for (kend = 0; kend < 2; ++kend) {
1326  kdir = clsChain[kpl][kcl].Dir[kend];
1327  if (idir != 0 && kdir != 0 && idir != kdir) continue;
1328  if (clsChain[kpl][kcl].VtxIndex[kend] >= 0) continue;
1329  // make a rough dX cut at the match end
1330  if (fabs(clsChain[kpl][kcl].X[kend] - vtx[ivx].X) > 5) continue;
1331  // and at the other end
1332  if (fabs(clsChain[kpl][kcl].X[1 - kend] - clsChain[ipl][icl].X[ioend]) > 50)
1333  continue;
1334  // check the error
1335  match.Cls[kpl] = kcl;
1336  match.End[kpl] = kend;
1337  if (DupMatch(match, nplanes)) continue;
1338  FillEndMatch(detProp, tpcid, match);
1339  totErr = match.Err + match.oErr;
1340  if (prt) {
1341  mf::LogVerbatim myprt("CCTM");
1342  myprt << "VtxMatch: Chk missing cluster match ";
1343  for (unsigned short ii = 0; ii < nplanes; ++ii)
1344  myprt << " " << ii << ":" << match.Cls[ii] << ":" << match.End[ii];
1345  myprt << " Err " << match.Err << "\n";
1346  }
1347  if (totErr > 100) continue;
1348  if (totErr < best) {
1349  best = totErr;
1350  kbst = kcl;
1351  kbend = kend;
1352  }
1353  } // kend
1354  } // kcl
1355  if (kbst >= 0) {
1356  // found a decent match
1357  match.Cls[kpl] = kbst;
1358  match.End[kpl] = kbend;
1359  FillEndMatch(detProp, tpcid, match);
1360  matcomb.push_back(match);
1361  // assign the vertex to this cluster
1362  clsChain[kpl][kbst].VtxIndex[kbend] = ivx;
1363  // and update vxCls
1364  vxCls[kpl].push_back(kbst);
1365  }
1366  else {
1367  // Try a 2 plane match if a 3 plane match didn't work
1368  match.Cls[kpl] = -1;
1369  match.End[kpl] = 0;
1370  if (DupMatch(match, nplanes)) continue;
1371  FillEndMatch(detProp, tpcid, match);
1372  if (match.Err + match.oErr < 100) matcomb.push_back(match);
1373  }
1374  } // jend
1375  } // iend
1376  } // jj
1377  } // ii -> icl
1378  } // ipl
1379 
1380  if (matcomb.size() == 0) continue;
1381  SortMatches(detProp, fmCluHits, 1, tpcid);
1382 
1383  } // ivx
1384 
1385  for (ipl = 0; ipl < 3; ++ipl)
1386  vxCls[ipl].clear();
1387 
1388  } // VtxMatch
1389 
1392  {
1393  // Project clusters to vertices and fill mVtxIndex. No requirement is
1394  // made that charge exists on the line between the Begin (End) of the
1395  // cluster and the vertex
1396 
1397  if (vtx.empty()) return;
1398 
1399  for (auto const& plane : wireReadoutGeom.Iterate<geo::PlaneGeo>(tpcid)) {
1400  auto const& planeID = plane.ID();
1401  unsigned int const ipl = planeID.Cryostat;
1402  for (unsigned int icl = 0; icl < cls[ipl].size(); ++icl) {
1403  for (unsigned int end = 0; end < 2u; ++end) {
1404  // ignore already attached clusters
1405  if (cls[ipl][icl].VtxIndex[end] >= 0) continue;
1406  short ibstvx = -1;
1407  float best = 1.;
1408  // index of the other end
1409  unsigned int oend = 1 - end;
1410  for (std::size_t ivx = 0; ivx < vtx.size(); ++ivx) {
1411  // ignore if the other end is attached to this vertex (which can happen with short clusters)
1412  if (cls[ipl][icl].VtxIndex[oend] == static_cast<short>(ivx)) continue;
1413  float const dWire = plane.WireCoordinate(geo::Point_t{0, vtx[ivx].Y, vtx[ivx].Z}) -
1414  cls[ipl][icl].Wire[end];
1415  if (end == 0) {
1416  if (dWire > 30 || dWire < -2) continue;
1417  }
1418  else {
1419  if (dWire < -30 || dWire > 2) continue;
1420  }
1421  // project the cluster to the vertex wire
1422  float const dX = fabs(cls[ipl][icl].X[end] +
1423  cls[ipl][icl].Slope[end] * fWirePitch * dWire - vtx[ivx].X);
1424  if (dX < best) {
1425  best = dX;
1426  ibstvx = ivx;
1427  }
1428  } // ivx
1429  if (ibstvx >= 0) {
1430  // attach
1431  cls[ipl][icl].VtxIndex[end] = ibstvx;
1432  cls[ipl][icl].mVtxIndex[end] = ibstvx;
1433  }
1434  } // end
1435  } // icl
1436  } // ipl
1437 
1438  } // FindMaybeVertices
1439 
1442  art::FindManyP<recob::Hit> const& fmCluHits,
1443  geo::TPCID const& tpcid)
1444  {
1445  unsigned short icl, icl1, icl2;
1446  float dw, dx, dWCut, dw1Max, dw2Max;
1447  float dA, dA2, dACut = fMaxDAng, chgAsymCut;
1448  float dXCut, chgasym, mrgErr;
1449  // long straight clusters
1450  bool ls1, ls2;
1451  bool gotprt = false;
1452  auto const nplanes = wireReadoutGeom.Nplanes(tpcid);
1453  for (unsigned short ipl = 0; ipl < nplanes; ++ipl) {
1454  if (cls[ipl].size() > 1) {
1455  for (icl1 = 0; icl1 < cls[ipl].size() - 1; ++icl1) {
1456  prt = (fDebugAlg == 666 && ipl == fDebugPlane && icl1 == fDebugCluster);
1457  if (prt) gotprt = true;
1458  // maximum delta Wire overlap is 10% of the total length
1459  dw1Max = 0.6 * cls[ipl][icl1].Length;
1460  ls1 = (cls[ipl][icl1].Length > 100 &&
1461  fabs(cls[ipl][icl1].Angle[0] - cls[ipl][icl1].Angle[1]) < 0.04);
1462  for (icl2 = icl1 + 1; icl2 < cls[ipl].size(); ++icl2) {
1463  ls2 = (cls[ipl][icl2].Length > 100 &&
1464  fabs(cls[ipl][icl2].Angle[0] - cls[ipl][icl2].Angle[1]) < 0.04);
1465  dw2Max = 0.6 * cls[ipl][icl2].Length;
1466  // set overlap cut to be the shorter of the two
1467  dWCut = dw1Max;
1468  if (dw2Max < dWCut) dWCut = dw2Max;
1469  // but not exceeding 20 for very long clusters
1470  if (dWCut > 100) dWCut = 100;
1471  if (dWCut < 2) dWCut = 2;
1472  chgAsymCut = fMergeChgAsym;
1473  // Compare end 1 of icl1 with end 0 of icl2
1474 
1475  if (prt)
1476  mf::LogVerbatim("CCTM")
1477  << "MCC P:C:W icl1 " << ipl << ":" << icl1 << ":" << cls[ipl][icl1].Wire[1]
1478  << " vtx " << cls[ipl][icl1].VtxIndex[1] << " ls1 " << ls1 << " icl2 " << ipl << ":"
1479  << icl2 << ":" << cls[ipl][icl2].Wire[0] << " vtx " << cls[ipl][icl2].VtxIndex[0]
1480  << " ls2 " << ls2 << " dWCut " << dWCut;
1481  if (std::abs(cls[ipl][icl1].Wire[1] - cls[ipl][icl2].Wire[0]) > dWCut) continue;
1482  // ignore if the clusters begin/end on the same wire
1483  // if(cls[ipl][icl1].Wire[1] == cls[ipl][icl2].Wire[0]) continue;
1484  // or if the angle exceeds the cut
1485  float af = AngleFactor(cls[ipl][icl1].Slope[1]);
1486  dACut = fMaxDAng * af;
1487  dXCut = fChainMaxdX * 5 * af;
1488  dA = fabs(cls[ipl][icl1].Angle[1] - cls[ipl][icl2].Angle[0]);
1489  // compare the match angle at the opposite ends of the clusters.
1490  // May have a bad end/begin angle if there is a delta-ray in the middle
1491  dA2 = fabs(cls[ipl][icl1].Angle[0] - cls[ipl][icl2].Angle[1]);
1492 
1493  if (prt)
1494  mf::LogVerbatim("CCTM")
1495  << " dA " << dA << " dA2 " << dA2 << " DACut " << dACut << " dXCut " << dXCut;
1496 
1497  if (dA2 < dA) dA = dA2;
1498  // ignore vertices that have only two associated clusters and
1499  // the angle is small
1500  if (dA < fChainVtxAng && cls[ipl][icl1].VtxIndex[1] >= 0) {
1501  dw = fWirePitch * (cls[ipl][icl2].Wire[0] - cls[ipl][icl1].Wire[1]);
1502  dx = cls[ipl][icl1].X[1] + cls[ipl][icl1].Slope[1] * dw * fWirePitch -
1503  cls[ipl][icl2].X[0];
1504  unsigned short ivx = cls[ipl][icl1].VtxIndex[1];
1505  if (vtx[ivx].nClusInPln[ipl] == 2 && fabs(dx) < 1) {
1506  cls[ipl][icl1].VtxIndex[1] = -2;
1507  cls[ipl][icl2].VtxIndex[0] = -2;
1508  vtx[ivx].nClusInPln[ipl] = 0;
1509  if (prt) mf::LogVerbatim("CCTM") << " clobbered vertex " << ivx;
1510  } // vertices match
1511  } // dA < 0.1 && ...
1512 
1513  // don't merge if a vertex exists at these ends
1514  if (cls[ipl][icl1].VtxIndex[1] >= 0) continue;
1515  if (cls[ipl][icl2].VtxIndex[0] >= 0) continue;
1516 
1517  // expand the angle match cut for clusters that appear to be stopping
1518  if (cls[ipl][icl2].Wire[0] - cls[ipl][icl1].Wire[1] < 3 &&
1519  (cls[ipl][icl1].Length < 3 || cls[ipl][icl2].Length < 3)) {
1520  if (prt) mf::LogVerbatim("CCTM") << "Stopping cluster";
1521  dACut *= 1.5;
1522  chgAsymCut *= 1.5;
1523  dXCut *= 3;
1524  } // stopping US cluster
1525 
1526  // find the angle made by the endpoints of the clusters
1527  dw = fWirePitch * (cls[ipl][icl2].Wire[0] - cls[ipl][icl1].Wire[1]);
1528  if (dw != 0) {
1529  dx = cls[ipl][icl2].X[0] - cls[ipl][icl1].X[1];
1530  float dA3 = std::abs(atan(dx / dw) - cls[ipl][icl1].Angle[1]);
1531  if (prt) mf::LogVerbatim("CCTM") << " dA3 " << dA3;
1532  if (dA3 > dA) dA = dA3;
1533  }
1534 
1535  // angle matching
1536  if (dA > dACut) continue;
1537 
1538  if (prt)
1539  mf::LogVerbatim("CCTM")
1540  << " rough dX " << fabs(cls[ipl][icl1].X[1] - cls[ipl][icl2].X[0]) << " cut = 20";
1541 
1542  // make a rough dX cut
1543  if (fabs(cls[ipl][icl1].X[1] - cls[ipl][icl2].X[0]) > 20) continue;
1544 
1545  // handle cosmic ray clusters that are broken at delta rays
1546  if (ls1 || ls2) {
1547  // tighter angle cuts but no charge cut
1548  if (dA > fChainVtxAng) continue;
1549  }
1550  else {
1551  chgasym = fabs(cls[ipl][icl1].Charge[1] - cls[ipl][icl2].Charge[0]);
1552  chgasym /= cls[ipl][icl1].Charge[1] + cls[ipl][icl2].Charge[0];
1553  if (prt) mf::LogVerbatim("CCTM") << " chgasym " << chgasym << " cut " << chgAsymCut;
1554  if (chgasym > chgAsymCut) continue;
1555  } // ls1 || ls2
1556  // project the longer cluster to the end of the shorter one
1557  if (cls[ipl][icl1].Length > cls[ipl][icl2].Length) {
1558  dw = fWirePitch * (cls[ipl][icl2].Wire[0] - cls[ipl][icl1].Wire[1]);
1559  dx = cls[ipl][icl1].X[1] + cls[ipl][icl1].Slope[1] * dw * fWirePitch -
1560  cls[ipl][icl2].X[0];
1561  }
1562  else {
1563  dw = fWirePitch * (cls[ipl][icl1].Wire[1] - cls[ipl][icl2].Wire[0]);
1564  dx = cls[ipl][icl2].X[0] + cls[ipl][icl2].Slope[0] * dw * fWirePitch -
1565  cls[ipl][icl1].X[1];
1566  }
1567 
1568  // handle overlapping clusters
1569  if (dA2 < 0.01 && abs(dx) > dXCut && dx < -1) {
1570  dx = dXClTraj(fmCluHits, ipl, icl1, 1);
1571  if (prt) mf::LogVerbatim("CCTM") << " new dx from dXClTraj " << dx;
1572  }
1573 
1574  if (prt)
1575  mf::LogVerbatim("CCTM")
1576  << " X0 " << cls[ipl][icl1].X[1] << " slp " << cls[ipl][icl1].Slope[1] << " dw "
1577  << dw << " oX " << cls[ipl][icl2].X[0] << " dx " << dx << " cut " << dXCut;
1578 
1579  if (fabs(dx) > dXCut) continue;
1580 
1581  // calculate a merge error that will be used to adjudicate between multiple merge attempts AngleFactor
1582  float xerr = dx / dXCut;
1583  float aerr = dA / dACut;
1584  mrgErr = xerr * xerr + aerr * aerr;
1585 
1586  if (prt)
1587  mf::LogVerbatim("CCTM")
1588  << "icl1 mrgErr " << mrgErr << " MergeError " << cls[ipl][icl1].MergeError[1]
1589  << " icl2 MergeError " << cls[ipl][icl2].MergeError[0];
1590 
1591  // this merge better than a previous one?
1592  if (mrgErr > cls[ipl][icl1].MergeError[1]) continue;
1593  if (mrgErr > cls[ipl][icl2].MergeError[0]) continue;
1594 
1595  // un-merge icl1 - this should always be true but check anyway
1596  if (cls[ipl][icl1].BrkIndex[1] >= 0) {
1597  unsigned short ocl = cls[ipl][icl1].BrkIndex[1];
1598  if (prt) mf::LogVerbatim("CCTM") << "clobber old icl1 BrkIndex " << ocl;
1599  if (cls[ipl][ocl].BrkIndex[0] == icl1) {
1600  cls[ipl][ocl].BrkIndex[0] = -1;
1601  cls[ipl][ocl].MergeError[0] = fMaxMergeError;
1602  }
1603  if (cls[ipl][ocl].BrkIndex[1] == icl1) {
1604  cls[ipl][ocl].BrkIndex[1] = -1;
1605  cls[ipl][ocl].MergeError[1] = fMaxMergeError;
1606  }
1607  } // cls[ipl][icl1].BrkIndex[1] >= 0
1608  cls[ipl][icl1].BrkIndex[1] = icl2;
1609  cls[ipl][icl1].MergeError[1] = mrgErr;
1610 
1611  // un-merge icl2
1612  if (cls[ipl][icl2].BrkIndex[0] >= 0) {
1613  unsigned short ocl = cls[ipl][icl2].BrkIndex[0];
1614  if (prt) mf::LogVerbatim("CCTM") << "clobber old icl2 BrkIndex " << ocl;
1615  if (cls[ipl][ocl].BrkIndex[0] == icl1) {
1616  cls[ipl][ocl].BrkIndex[0] = -1;
1617  cls[ipl][ocl].MergeError[0] = fMaxMergeError;
1618  }
1619  if (cls[ipl][ocl].BrkIndex[1] == icl1) {
1620  cls[ipl][ocl].BrkIndex[1] = -1;
1621  cls[ipl][ocl].MergeError[1] = fMaxMergeError;
1622  }
1623  } // cls[ipl][icl2].BrkIndex[0] >= 0
1624  cls[ipl][icl2].BrkIndex[0] = icl1;
1625  cls[ipl][icl2].MergeError[0] = mrgErr;
1626  if (prt) mf::LogVerbatim("CCTM") << " merge " << icl1 << " and " << icl2;
1627 
1628  } // icl2
1629  } // icl1
1630 
1631  // look for broken clusters in which have a C shape similar to a Begin-Begin vertex. The clusters
1632  // will have large and opposite sign angles
1633  bool gotone;
1634  for (icl1 = 0; icl1 < cls[ipl].size() - 1; ++icl1) {
1635  gotone = false;
1636  for (icl2 = icl1 + 1; icl2 < cls[ipl].size(); ++icl2) {
1637  // check both ends
1638  for (unsigned short end = 0; end < 2; ++end) {
1639  // Ignore already identified broken clusters
1640  if (cls[ipl][icl1].BrkIndex[end] >= 0) continue;
1641  if (cls[ipl][icl2].BrkIndex[end] >= 0) continue;
1642  // require a large angle cluster
1643  if (fabs(cls[ipl][icl1].Angle[end]) < 1) continue;
1644  // and a second large angle cluster
1645  if (fabs(cls[ipl][icl2].Angle[end]) < 1) continue;
1646  if (prt)
1647  mf::LogVerbatim("CCTM")
1648  << "BrokenC: clusters " << cls[ipl][icl1].Wire[end] << ":"
1649  << (int)cls[ipl][icl1].Time[end] << " " << cls[ipl][icl2].Wire[end] << ":"
1650  << (int)cls[ipl][icl2].Time[end] << " angles " << cls[ipl][icl1].Angle[end] << " "
1651  << cls[ipl][icl2].Angle[end] << " dWire "
1652  << fabs(cls[ipl][icl1].Wire[end] - cls[ipl][icl2].Wire[end]);
1653  if (fabs(cls[ipl][icl1].Wire[end] - cls[ipl][icl2].Wire[end]) > 5) continue;
1654  // This is really crude but maybe OK
1655  // project 1 -> 2
1656  float dsl = cls[ipl][icl2].Slope[end] - cls[ipl][icl1].Slope[end];
1657  float fvw =
1658  (cls[ipl][icl1].X[end] - cls[ipl][icl1].Wire[end] * cls[ipl][icl1].Slope[end] -
1659  cls[ipl][icl2].X[end] + cls[ipl][icl2].Wire[end] * cls[ipl][icl2].Slope[end]) /
1660  dsl;
1661  if (prt) mf::LogVerbatim("CCTM") << " fvw " << fvw;
1662  if (fabs(cls[ipl][icl1].Wire[end] - fvw) > 4) continue;
1663  if (fabs(cls[ipl][icl2].Wire[end] - fvw) > 4) continue;
1664  cls[ipl][icl1].BrkIndex[end] = icl2;
1665  // TODO This could use some improvement if necessary
1666  cls[ipl][icl1].MergeError[end] = 1;
1667  cls[ipl][icl2].BrkIndex[end] = icl1;
1668  cls[ipl][icl2].MergeError[end] = 1;
1669  gotone = true;
1670  dx = fabs(cls[ipl][icl1].X[end] - cls[ipl][icl2].X[end]);
1671  if (prt)
1672  mf::LogVerbatim("CCTM")
1673  << "BrokenC: icl1:W " << icl1 << ":" << cls[ipl][icl1].Wire[end] << " icl2:W "
1674  << icl2 << ":" << cls[ipl][icl2].Wire[end] << " end " << end << " dx " << dx;
1675  } // end
1676  if (gotone) break;
1677  } // icl2
1678  } // icl1
1679 
1680  } // cls[ipl].size() > 1
1681 
1682  // follow mother-daughter broken clusters and put them in the cluster chain array
1683  unsigned short mom, momBrkEnd, dtrBrkEnd, nit;
1684  short dtr;
1685 
1686  std::vector<bool> gotcl(cls[ipl].size());
1687  for (icl = 0; icl < cls[ipl].size(); ++icl)
1688  gotcl[icl] = false;
1689  if (prt)
1690  mf::LogVerbatim("CCTM") << "ipl " << ipl << " cls.size() " << cls[ipl].size() << "\n";
1691 
1692  std::vector<unsigned short> sCluster;
1693  std::vector<unsigned short> sOrder;
1694  for (icl = 0; icl < cls[ipl].size(); ++icl) {
1695  sCluster.clear();
1696  sOrder.clear();
1697  if (gotcl[icl]) continue;
1698  // don't start with a cluster broken at both ends
1699  if (cls[ipl][icl].BrkIndex[0] >= 0 && cls[ipl][icl].BrkIndex[1] >= 0) continue;
1700  for (unsigned int end = 0; end < 2; ++end) {
1701  if (cls[ipl][icl].BrkIndex[end] < 0) continue;
1702  if (cls[ipl][icl].MergeError[end] > fMergeErrorCut) continue;
1703  gotcl[icl] = true;
1704  mom = icl;
1705  // end where the mom is broken
1706  momBrkEnd = end;
1707  sCluster.push_back(mom);
1708  if (momBrkEnd == 1) {
1709  // typical case - broken at the DS end
1710  sOrder.push_back(0);
1711  }
1712  else {
1713  // broken at the US end
1714  sOrder.push_back(1);
1715  }
1716  dtr = cls[ipl][icl].BrkIndex[end];
1717  nit = 0;
1718  while (dtr >= 0 && dtr < (short)cls[ipl].size() && nit < cls[ipl].size()) {
1719  // determine which end of the dtr should be attached to mom
1720  for (dtrBrkEnd = 0; dtrBrkEnd < 2; ++dtrBrkEnd)
1721  if (cls[ipl][dtr].BrkIndex[dtrBrkEnd] == mom) break;
1722  if (dtrBrkEnd == 2) {
1723  gotcl[icl] = false;
1724  break;
1725  }
1726  // check for reasonable merge error
1727  if (cls[ipl][dtr].MergeError[dtrBrkEnd] < fMergeErrorCut) {
1728  sCluster.push_back(dtr);
1729  sOrder.push_back(dtrBrkEnd);
1730  gotcl[dtr] = true;
1731  }
1732  ++nit;
1733  // set up to check the new mom
1734  mom = dtr;
1735  // at the other end
1736  momBrkEnd = 1 - dtrBrkEnd;
1737  // with the new dtr
1738  dtr = cls[ipl][mom].BrkIndex[momBrkEnd];
1739  } // dtr >= 0 ...
1740  if (dtrBrkEnd == 2) continue;
1741  } // end
1742 
1743  if (!gotcl[icl]) {
1744  // a single unbroken cluster
1745  sCluster.push_back(icl);
1746  sOrder.push_back(0);
1747  }
1748 
1749  if (sCluster.size() == 0) {
1750  mf::LogError("CCTM") << "MakeClusterChains error in plane " << ipl << " cluster " << icl;
1751  return;
1752  }
1753 
1754  ClsChainPar ccp;
1755  // fill the struct parameters assuming this is a cluster chain containing only one cluster
1756  unsigned short jcl = sCluster[0];
1757  if (jcl > cls[ipl].size()) std::cout << "oops MCC\n";
1758  unsigned short oend;
1759  for (unsigned int end = 0; end < 2; ++end) {
1760  oend = end;
1761  if (sOrder[0] > 0) oend = 1 - end;
1762  ccp.Wire[end] = cls[ipl][jcl].Wire[oend];
1763  ccp.Time[end] = cls[ipl][jcl].Time[oend];
1764  ccp.X[end] = cls[ipl][jcl].X[oend];
1765  ccp.Slope[end] = cls[ipl][jcl].Slope[oend];
1766  ccp.Angle[end] = cls[ipl][jcl].Angle[oend];
1767  ccp.Dir[end] = cls[ipl][icl].Dir[oend];
1768  ccp.VtxIndex[end] = cls[ipl][jcl].VtxIndex[oend];
1769  ccp.ChgNear[end] = cls[ipl][jcl].ChgNear[oend];
1770  ccp.mBrkIndex[end] = cls[ipl][jcl].BrkIndex[oend];
1771  } // end
1772  ccp.Length = cls[ipl][icl].Length;
1773  ccp.TotChg = cls[ipl][icl].TotChg;
1774  ccp.InTrack = -1;
1775 
1776  for (unsigned short ii = 1; ii < sCluster.size(); ++ii) {
1777  jcl = sCluster[ii];
1778  if (jcl > cls[ipl].size()) std::cout << "oops MCC\n";
1779  // end is the end where the break is being mended
1780  unsigned int end = sOrder[ii];
1781  if (end > 1) std::cout << "oops2 MCC\n";
1782  oend = 1 - end;
1783  // update the parameters at the other end of the chain
1784  ccp.Wire[1] = cls[ipl][jcl].Wire[oend];
1785  ccp.Time[1] = cls[ipl][jcl].Time[oend];
1786  ccp.X[1] = cls[ipl][jcl].X[oend];
1787  ccp.Slope[1] = cls[ipl][jcl].Slope[oend];
1788  ccp.Angle[1] = cls[ipl][jcl].Angle[oend];
1789  ccp.Dir[1] = cls[ipl][jcl].Dir[oend];
1790  ccp.VtxIndex[1] = cls[ipl][jcl].VtxIndex[oend];
1791  ccp.ChgNear[1] = cls[ipl][jcl].ChgNear[oend];
1792  ccp.mBrkIndex[1] = cls[ipl][jcl].BrkIndex[oend];
1793  ccp.Length += cls[ipl][jcl].Length;
1794  ccp.TotChg += cls[ipl][jcl].TotChg;
1795  } // ii
1796  ccp.ClsIndex = sCluster;
1797  ccp.Order = sOrder;
1798  // redo the direction
1799  if (ccp.Time[1] > ccp.Time[0]) {
1800  ccp.Dir[0] = 1;
1801  ccp.Dir[1] = -1;
1802  }
1803  else {
1804  ccp.Dir[0] = -1;
1805  ccp.Dir[1] = 1;
1806  }
1807  clsChain[ipl].push_back(ccp);
1808 
1809  } // icl
1810 
1811  // re-index mBrkIndex to point to a cluster chain, not a cluster
1812  unsigned short brkCls;
1813  bool gotit;
1814  for (unsigned short ccl = 0; ccl < clsChain[ipl].size(); ++ccl) {
1815  for (unsigned short end = 0; end < 2; ++end) {
1816  if (clsChain[ipl][ccl].mBrkIndex[end] < 0) continue;
1817  brkCls = clsChain[ipl][ccl].mBrkIndex[end];
1818  gotit = false;
1819  // find this cluster index in a cluster chain
1820  for (unsigned short ccl2 = 0; ccl2 < clsChain[ipl].size(); ++ccl2) {
1821  if (ccl2 == ccl) continue;
1822  if (std::find(clsChain[ipl][ccl2].ClsIndex.begin(),
1823  clsChain[ipl][ccl2].ClsIndex.end(),
1824  brkCls) == clsChain[ipl][ccl2].ClsIndex.end())
1825  continue;
1826  // found it
1827  clsChain[ipl][ccl].mBrkIndex[end] = ccl2;
1828  gotit = true;
1829  break;
1830  } // ccl2
1831  if (!gotit)
1832  mf::LogError("CCTM") << "MCC: Cluster chain " << ccl << " end " << end
1833  << " Failed to find brkCls " << brkCls << " in plane " << ipl;
1834  } // end
1835  } // ccl
1836 
1837  } // ipl
1838 
1839  if (gotprt) PrintClusters(detProp, tpcid);
1840  prt = false;
1841 
1842  } // MakeClusterChains
1843 
1846  unsigned short ipl,
1847  unsigned short icl1,
1848  unsigned short end1)
1849  {
1850  // project cluster icl1 at end1 to find the best intersection with icl2
1851  float dw, dx, best = 999;
1852  std::vector<art::Ptr<recob::Hit>> clusterhits = fmCluHits.at(cls[ipl][icl1].EvtIndex);
1853  for (unsigned short hit = 0; hit < clusterhits.size(); ++hit) {
1854  dw = clusterhits[hit]->WireID().Wire - cls[ipl][icl1].Wire[end1];
1855  dx = fabs(cls[ipl][icl1].Time[end1] + dw * fWirePitch * cls[ipl][icl1].Slope[end1] -
1856  clusterhits[hit]->PeakTime());
1857  if (dx < best) best = dx;
1858  if (dx < 0.01) break;
1859  } // hit
1860  return best;
1861  } // dXClTraj
1862 
1865  art::FindManyP<recob::Hit> const& fmCluHits,
1866  unsigned short imat,
1867  unsigned short procCode,
1868  geo::TPCID const& tpcid)
1869  {
1870  // store the current "under construction" track in the trk vector
1871 
1872  TrkPar newtrk;
1873 
1874  if (imat > matcomb.size() - 1) {
1875  mf::LogError("CCTM") << "Bad imat in StoreTrack";
1876  return;
1877  }
1878 
1879  // ensure there are at least 2 hits in at least 2 planes
1880  unsigned short nhitinpl = 0;
1881  auto const nplanes = wireReadoutGeom.Nplanes(tpcid);
1882  for (unsigned short ipl = 0; ipl < nplanes; ++ipl)
1883  if (trkHits[ipl].size() > 1) ++nhitinpl;
1884  if (nhitinpl < 2) {
1885  mf::LogError("CCTM") << "StoreTrack: Not enough hits in each plane\n";
1886  return;
1887  }
1888  if (prt)
1889  mf::LogVerbatim("CCTM") << "In StoreTrack: matcomb " << imat << " cluster chains "
1890  << matcomb[imat].Cls[0] << " " << matcomb[imat].Cls[1] << " "
1891  << matcomb[imat].Cls[2];
1892 
1893  // Track hit vectors for fitting the trajectory
1894  std::array<std::vector<geo::WireID>, 3> trkWID;
1895  std::array<std::vector<double>, 3> trkX;
1896  std::array<std::vector<double>, 3> trkXErr;
1897 
1898  // track trajectory for a track
1899  std::vector<TVector3> trkPos;
1900  std::vector<TVector3> trkDir;
1901 
1902  newtrk.ID = trk.size() + 1;
1903  newtrk.Proc = procCode;
1904  newtrk.TrkHits = trkHits;
1905  // BUG the double brace syntax is required to work around clang bug 21629
1906  // (https://bugs.llvm.org/show_bug.cgi?id=21629)
1907  newtrk.VtxIndex = {{-1, -1}};
1908  newtrk.ChgOrder = 0;
1909  newtrk.MomID = -1;
1910  // BUG the double brace syntax is required to work around clang bug 21629
1911  // (https://bugs.llvm.org/show_bug.cgi?id=21629)
1912  newtrk.EndInTPC = {{false, false}};
1913  newtrk.GoodEnd = {{false, false}};
1914  newtrk.DtrID = {0};
1915  newtrk.PDGCode = -1;
1916 
1917  unsigned short icl, iht;
1918 
1919  if (prt)
1920  mf::LogVerbatim("CCTM") << "CCTM: Make traj for track " << newtrk.ID << " procCode "
1921  << procCode << " nhits in planes " << trkHits[0].size() << " "
1922  << trkHits[1].size() << " " << trkHits[2].size();
1923  // make the track trajectory
1924  if (nplanes == 2) {
1925  trkWID[2].resize(0);
1926  trkX[2].resize(0);
1927  trkXErr[2].resize(0);
1928  }
1929  for (auto const& planeid : wireReadoutGeom.Iterate<geo::PlaneID>(tpcid)) {
1930  auto const ipl = planeid.Plane;
1931  trkWID[ipl].resize(trkHits[ipl].size());
1932  trkX[ipl].resize(trkHits[ipl].size());
1933  trkXErr[ipl].resize(trkHits[ipl].size());
1934  for (iht = 0; iht < trkHits[ipl].size(); ++iht) {
1935  trkWID[ipl][iht] = trkHits[ipl][iht]->WireID();
1936  trkX[ipl][iht] = detProp.ConvertTicksToX(trkHits[ipl][iht]->PeakTime(), planeid);
1937  trkXErr[ipl][iht] =
1938  fHitFitErrFac * trkHits[ipl][iht]->RMS() * trkHits[ipl][iht]->Multiplicity();
1939  } // iht
1940  } // ipl
1941  fTrackTrajectoryAlg.TrackTrajectory(trkWID, trkX, trkXErr, trkPos, trkDir);
1942  if (trkPos.size() < 2) {
1943  mf::LogError("CCTM") << "StoreTrack: No trajectory points on failed track " << newtrk.ID
1944  << " in StoreTrack: matcomb " << imat << " cluster chains "
1945  << matcomb[imat].Cls[0] << " " << matcomb[imat].Cls[1] << " "
1946  << matcomb[imat].Cls[2];
1947  // make a garbage trajectory
1948  trkPos.resize(2);
1949  trkPos[1](2) = 1;
1950  trkDir.resize(2);
1951  trkDir[1](2) = 1;
1952  }
1953  newtrk.TrjPos = trkPos;
1954  newtrk.TrjDir = trkDir;
1955 
1956  if (prt) mf::LogVerbatim("CCTM") << " number of traj points " << trkPos.size();
1957 
1958  // determine if each end is good in the sense that there are hits in each plane
1959  // that are consistent in time and are presumed to form a good 3D space point
1960  unsigned short nClose, indx, jndx;
1961  float xErr;
1962  for (unsigned int end = 0; end < 2; ++end) {
1963  nClose = 0;
1964  for (unsigned short ipl = 0; ipl < nplanes - 1; ++ipl) {
1965  if (trkX[ipl].size() == 0) continue;
1966  for (unsigned short jpl = ipl + 1; jpl < nplanes; ++jpl) {
1967  if (trkX[jpl].size() == 0) continue;
1968  if (end == 0) {
1969  indx = 0;
1970  jndx = 0;
1971  }
1972  else {
1973  indx = trkXErr[ipl].size() - 1;
1974  jndx = trkXErr[jpl].size() - 1;
1975  }
1976  xErr = 3 * (trkXErr[ipl][indx] + trkXErr[jpl][jndx]);
1977  if (std::abs(trkX[ipl][indx] - trkX[jpl][jndx]) < xErr) ++nClose;
1978  } // jpl
1979  } // ipl
1980  if (nClose == nplanes) newtrk.GoodEnd[end] = true;
1981  } // end
1982 
1983  // set trajectory end points to a vertex if one exists
1984  unsigned short ivx, itj, ccl;
1985  float dx, dy, dz, dr0, dr1;
1986  unsigned short attachEnd;
1987  for (unsigned int end = 0; end < 2; ++end) {
1988  ivx = USHRT_MAX;
1989  if (end == 0 && matcomb[imat].Vtx >= 0) ivx = matcomb[imat].Vtx;
1990  if (end == 1 && matcomb[imat].oVtx >= 0) ivx = matcomb[imat].oVtx;
1991  if (ivx == USHRT_MAX) continue;
1992  // determine the proper end using the TrjPos order and brute force
1993  itj = 0;
1994  dx = vtx[ivx].X - newtrk.TrjPos[itj](0);
1995  dy = vtx[ivx].Y - newtrk.TrjPos[itj](1);
1996  dz = vtx[ivx].Z - newtrk.TrjPos[itj](2);
1997  dr0 = dx * dx + dy * dy + dz * dz;
1998  itj = newtrk.TrjPos.size() - 1;
1999  dx = vtx[ivx].X - newtrk.TrjPos[itj](0);
2000  dy = vtx[ivx].Y - newtrk.TrjPos[itj](1);
2001  dz = vtx[ivx].Z - newtrk.TrjPos[itj](2);
2002  dr1 = dx * dx + dy * dy + dz * dz;
2003  attachEnd = 1;
2004  if (dr0 < dr1) {
2005  itj = 0;
2006  attachEnd = 0;
2007  // a really bad match to the vertex
2008  if (dr0 > 5) return;
2009  }
2010  else {
2011  // a really bad match to the vertex
2012  if (dr1 > 5) return;
2013  }
2014  newtrk.TrjPos[itj](0) = vtx[ivx].X;
2015  newtrk.TrjPos[itj](1) = vtx[ivx].Y;
2016  newtrk.TrjPos[itj](2) = vtx[ivx].Z;
2017  newtrk.VtxIndex[attachEnd] = ivx;
2018  // correct the trajectory direction
2019  TVector3 dir;
2020  if (itj == 0) {
2021  dir = newtrk.TrjPos[1] - newtrk.TrjPos[0];
2022  newtrk.TrjDir[0] = dir.Unit();
2023  }
2024  else {
2025  dir = newtrk.TrjPos[itj - 1] - newtrk.TrjPos[itj];
2026  newtrk.TrjDir[itj] = dir.Unit();
2027  }
2028  } // end
2029 
2030  if (newtrk.VtxIndex[0] >= 0 && newtrk.VtxIndex[0] == newtrk.VtxIndex[1]) {
2031  mf::LogError("CCTM")
2032  << "StoreTrack: Trying to attach a vertex to both ends of a track. imat = " << imat;
2033  return;
2034  }
2035 
2036  // calculate the length
2037  newtrk.Length = 0;
2038  float norm;
2039  double X, Y, Z;
2040  for (unsigned short itj = 1; itj < newtrk.TrjPos.size(); ++itj) {
2041  X = newtrk.TrjPos[itj](0) - newtrk.TrjPos[itj - 1](0);
2042  Y = newtrk.TrjPos[itj](1) - newtrk.TrjPos[itj - 1](1);
2043  Z = newtrk.TrjPos[itj](2) - newtrk.TrjPos[itj - 1](2);
2044  norm = sqrt(X * X + Y * Y + Z * Z);
2045  newtrk.Length += norm;
2046  }
2047 
2048  // store the cluster -> track assignment
2049  newtrk.ClsEvtIndices.clear();
2050  for (unsigned short ipl = 0; ipl < nplanes; ++ipl) {
2051  if (matcomb[imat].Cls[ipl] < 0) continue;
2052  ccl = matcomb[imat].Cls[ipl];
2053  if (ccl > clsChain[ipl].size()) std::cout << "oops StoreTrack\n";
2054  clsChain[ipl][ccl].InTrack = newtrk.ID;
2055  for (unsigned short icc = 0; icc < clsChain[ipl][ccl].ClsIndex.size(); ++icc) {
2056  icl = clsChain[ipl][ccl].ClsIndex[icc];
2057  if (icl > cls[ipl].size()) std::cout << "oops StoreTrack\n";
2058  cls[ipl][icl].InTrack = newtrk.ID;
2059  if (cls[ipl][icl].EvtIndex > fmCluHits.size() - 1) {
2060  std::cout << "ooops2 store track EvtIndex " << cls[ipl][icl].EvtIndex << " size "
2061  << fmCluHits.size() << " icl " << icl << "\n";
2062  continue;
2063  }
2064  newtrk.ClsEvtIndices.push_back(cls[ipl][icl].EvtIndex);
2065  } // icc
2066  } // ipl
2067 
2068  if (prt) mf::LogVerbatim("CCTM") << " track ID " << newtrk.ID << " stored in StoreTrack";
2069 
2070  trk.push_back(newtrk);
2071  } // StoreTrack
2072 
2075  geo::TPCID const& tpcid)
2076  {
2077  // Match clusters in all planes
2078  bool ignoreSign;
2079  float kSlp, kAng, kX, kWir, okWir;
2080  short idir, ioend, jdir, joend, kdir;
2081 
2082  double yp, zp;
2083  auto const& first_tpc = geom->TPC({0, 0});
2084  float tpcSizeY = first_tpc.HalfWidth();
2085  float tpcSizeZ = first_tpc.Length();
2086 
2087  float dxcut = 2;
2088  float dxkcut;
2089  float dwcut = 6;
2090  if (fuBCode) {
2091  dxcut = 20;
2092  dwcut = 60;
2093  }
2094 
2095  // temp array for making a rough charge asymmetry cut
2096  std::array<float, 3> mchg;
2097  auto const nplanes = wireReadoutGeom.Nplanes(tpcid);
2098  for (unsigned short ipl = 0; ipl < nplanes; ++ipl) {
2099  geo::PlaneID const iplane_id{tpcid, ipl};
2100  for (unsigned short icl = 0; icl < clsChain[ipl].size(); ++icl) {
2101  if (clsChain[ipl][icl].InTrack >= 0) continue;
2102  // skip short clusters
2103  if (clsChain[ipl][icl].Length < fMatchMinLen[algIndex]) continue;
2104  unsigned short jpl = (ipl + 1) % nplanes;
2105  unsigned short kpl = (jpl + 1) % nplanes;
2106  geo::PlaneID const jplane_id{tpcid, jpl};
2107  for (unsigned short jcl = 0; jcl < clsChain[jpl].size(); ++jcl) {
2108  if (clsChain[jpl][jcl].InTrack >= 0) continue;
2109  // skip short clusters
2110  if (clsChain[jpl][jcl].Length < fMatchMinLen[algIndex]) continue;
2111  // make first charge asymmetry cut
2112  mchg[0] = clsChain[ipl][icl].TotChg;
2113  mchg[1] = clsChain[jpl][jcl].TotChg;
2114  mchg[2] = mchg[1];
2115  if (fChgAsymFactor[algIndex] > 0 && ChargeAsym(mchg) > 0.5) continue;
2116  for (unsigned short iend = 0; iend < 2; ++iend) {
2117  idir = clsChain[ipl][icl].Dir[iend];
2118  for (unsigned short jend = 0; jend < 2; ++jend) {
2119  jdir = clsChain[jpl][jcl].Dir[jend];
2120  if (idir != 0 && jdir != 0 && idir != jdir) continue;
2121  // make an X cut
2122  if (fabs(clsChain[ipl][icl].X[iend] - clsChain[jpl][jcl].X[jend]) > dxcut) continue;
2123  ioend = 1 - iend;
2124  joend = 1 - jend;
2125  // Find the expected third (k) plane parameters
2127  ipl, clsChain[ipl][icl].Slope[iend], jpl, clsChain[jpl][jcl].Slope[jend], tpcid);
2128  kAng = atan(kSlp);
2129 
2130  // Ensure the match end is within the TPC
2131  auto const intersection =
2134  geo::WireID{iplane_id, (unsigned int)(0.5 + clsChain[ipl][icl].Wire[iend])},
2135  geo::WireID{jplane_id, (unsigned int)(0.5 + clsChain[jpl][jcl].Wire[jend])})
2136  .value_or(geo::WireIDIntersection::invalid());
2137  yp = intersection.y;
2138  zp = intersection.z;
2139  if (yp > tpcSizeY || yp < -tpcSizeY) continue;
2140  if (zp < 0 || zp > tpcSizeZ) continue;
2141 
2142  kX = 0.5 * (clsChain[ipl][icl].X[iend] + clsChain[jpl][jcl].X[jend]);
2143  auto const& kplane = wireReadoutGeom.Plane({tpcid, kpl});
2144  kWir = kplane.WireCoordinate(geo::Point_t{0, yp, zp});
2145 
2146  // now look at the other end
2147  auto const ointersection =
2150  geo::WireID{iplane_id, (unsigned int)(0.5 + clsChain[ipl][icl].Wire[ioend])},
2151  geo::WireID{jplane_id, (unsigned int)(0.5 + clsChain[jpl][jcl].Wire[joend])})
2152  .value_or(geo::WireIDIntersection::invalid());
2153  yp = ointersection.y;
2154  zp = ointersection.z;
2155  if (yp > tpcSizeY || yp < -tpcSizeY) continue;
2156  if (zp < 0 || zp > tpcSizeZ) continue;
2157  okWir = kplane.WireCoordinate(geo::Point_t{0, yp, zp});
2158 
2159  if (prt)
2160  mf::LogVerbatim("CCTM")
2161  << "PlnMatch: chk i " << ipl << ":" << icl << ":" << iend << " idir " << idir
2162  << " X " << clsChain[ipl][icl].X[iend] << " j " << jpl << ":" << jcl << ":"
2163  << jend << " jdir " << jdir << " X " << clsChain[jpl][jcl].X[jend];
2164 
2165  if (prt)
2166  mf::LogVerbatim("CCTM")
2167  << "PlnMatch: chk j " << ipl << ":" << icl << ":" << iend << " " << jpl << ":"
2168  << jcl << ":" << jend << " iSlp " << std::setprecision(2)
2169  << clsChain[ipl][icl].Slope[iend] << " jSlp " << std::setprecision(2)
2170  << clsChain[jpl][jcl].Slope[jend] << " kWir " << (int)kWir << " okWir "
2171  << (int)okWir << " kSlp " << std::setprecision(2) << kSlp << " kAng "
2172  << std::setprecision(2) << kAng << " kX " << std::setprecision(1) << kX;
2173 
2174  // handle the case near pi/2, where the errors on large slopes
2175  // could result in a wrong-sign kAng
2176  ignoreSign = (fabs(kSlp) > 1.5);
2177  if (ignoreSign) kAng = fabs(kAng);
2178  dxkcut = dxcut * AngleFactor(kSlp);
2179  bool gotkcl = false;
2180  for (unsigned short kcl = 0; kcl < clsChain[kpl].size(); ++kcl) {
2181  if (clsChain[kpl][kcl].InTrack >= 0) continue;
2182  // make second charge asymmetry cut
2183  mchg[0] = clsChain[ipl][icl].TotChg;
2184  mchg[1] = clsChain[jpl][jcl].TotChg;
2185  mchg[2] = clsChain[kpl][kcl].TotChg;
2186  if (fChgAsymFactor[algIndex] > 0 && ChargeAsym(mchg) > 0.5) continue;
2187  for (unsigned short kend = 0; kend < 2; ++kend) {
2188  kdir = clsChain[kpl][kcl].Dir[kend];
2189  if (idir != 0 && kdir != 0 && idir != kdir) continue;
2190  if (prt)
2191  mf::LogVerbatim("CCTM")
2192  << " kcl " << kcl << " kend " << kend << " dx "
2193  << std::abs(clsChain[kpl][kcl].X[kend] - kX) << " dxkcut " << dxkcut;
2194  if (std::abs(clsChain[kpl][kcl].X[kend] - kX) > dxkcut) continue;
2195  // rough dWire cut
2196  if (prt)
2197  mf::LogVerbatim("CCTM")
2198  << " kcl " << kcl << " kend " << kend << " dw "
2199  << (clsChain[kpl][kcl].Wire[kend] - kWir) << " ignoreSign " << ignoreSign;
2200  if (fabs(clsChain[kpl][kcl].Wire[kend] - kWir) > dwcut) continue;
2201  if (prt) mf::LogVerbatim("CCTM") << " chk k " << kpl << ":" << kcl << ":" << kend;
2202  MatchPars match;
2203  match.Cls[ipl] = icl;
2204  match.End[ipl] = iend;
2205  match.Cls[jpl] = jcl;
2206  match.End[jpl] = jend;
2207  match.Cls[kpl] = kcl;
2208  match.End[kpl] = kend;
2209  match.Err = 100;
2210  if (DupMatch(match, nplanes)) continue;
2211  match.Chg[ipl] = 0;
2212  match.Chg[jpl] = 0;
2213  match.Chg[kpl] = 0;
2214  match.Vtx = clsChain[ipl][icl].VtxIndex[iend];
2215  match.oVtx = -1;
2216  FillEndMatch(detProp, tpcid, match);
2217  if (prt)
2218  mf::LogVerbatim("CCTM") << " PlnMatch: match k " << kpl << ":" << match.Cls[kpl]
2219  << ":" << match.End[kpl] << " oChg " << match.Chg[kpl]
2220  << " mErr " << match.Err << " oErr " << match.oErr;
2221  if (match.Chg[kpl] == 0) continue;
2222  if (match.Err > 10 || match.oErr > 10) continue;
2223  if (prt) mf::LogVerbatim("CCTM") << " dup? ";
2224  if (DupMatch(match, nplanes)) continue;
2225  matcomb.push_back(match);
2226  gotkcl = true;
2227  } // kend
2228  } // kcl
2229  if (prt) mf::LogVerbatim("CCTM") << " PlnMatch: gotkcl " << gotkcl;
2230  if (!gotkcl) {
2231  // make a 2-plane match and try again
2232  MatchPars match;
2233  match.Cls[ipl] = icl;
2234  match.End[ipl] = iend;
2235  match.Cls[jpl] = jcl;
2236  match.End[jpl] = jend;
2237  match.Cls[kpl] = -1;
2238  match.End[kpl] = 0;
2239  match.Err = 100;
2240  if (DupMatch(match, nplanes)) continue;
2241  match.Chg[ipl] = 0;
2242  match.Chg[jpl] = 0;
2243  match.Chg[kpl] = 0;
2244  match.Vtx = clsChain[ipl][icl].VtxIndex[iend];
2245  match.oVtx = -1;
2246  FillEndMatch(detProp, tpcid, match);
2247  if (prt)
2248  mf::LogVerbatim("CCTM")
2249  << " Tried 2-plane match"
2250  << " k " << kpl << ":" << match.Cls[kpl] << ":" << match.End[kpl] << " Chg "
2251  << match.Chg[kpl] << " Err " << match.Err << " match.oErr " << match.oErr;
2252  if (match.Chg[kpl] <= 0) continue;
2253  if (match.Err > 10 || match.oErr > 10) continue;
2254  matcomb.push_back(match);
2255  } // !gotkcl
2256  } // jend
2257  } // iend
2258  } // jcl
2259  } // icl
2260  } // ipl
2261  } // PlnMatch
2262 
2264  bool CCTrackMaker::DupMatch(MatchPars& match, unsigned short const nplanes)
2265  {
2266  unsigned short nMatCl, nMiss;
2267  float toterr = match.Err + match.oErr;
2268  for (unsigned int imat = 0; imat < matcomb.size(); ++imat) {
2269  // check for exact matches
2270  if (match.Cls[0] == matcomb[imat].Cls[0] && match.Cls[1] == matcomb[imat].Cls[1] &&
2271  match.Cls[2] == matcomb[imat].Cls[2]) {
2272 
2273  // compare the error
2274  if (toterr < matcomb[imat].Err + matcomb[imat].oErr) {
2275  // keep the better one
2276  matcomb[imat].End[0] = match.End[0];
2277  matcomb[imat].End[1] = match.End[1];
2278  matcomb[imat].End[2] = match.End[2];
2279  matcomb[imat].Vtx = match.Vtx;
2280  matcomb[imat].dWir = match.dWir;
2281  matcomb[imat].dAng = match.dAng;
2282  matcomb[imat].dX = match.dX;
2283  matcomb[imat].Err = match.Err;
2284  matcomb[imat].oVtx = match.oVtx;
2285  matcomb[imat].odWir = match.odWir;
2286  matcomb[imat].odAng = match.odAng;
2287  matcomb[imat].odX = match.odX;
2288  matcomb[imat].oErr = match.oErr;
2289  }
2290  return true;
2291  } // test
2292  // check for a 3-plane match vs 2-plane match
2293  nMatCl = 0;
2294  nMiss = 0;
2295  for (unsigned short ipl = 0; ipl < nplanes; ++ipl) {
2296  if (match.Cls[ipl] >= 0) {
2297  if (match.Cls[ipl] == matcomb[imat].Cls[ipl] &&
2298  (match.End[0] == matcomb[imat].End[0] || match.End[1] == matcomb[imat].End[1]))
2299  ++nMatCl;
2300  }
2301  else {
2302  ++nMiss;
2303  }
2304  } // ipl
2305  if (nMatCl == 2 && nMiss == 1) return true;
2306  } // imat
2307  return false;
2308  } // DupMatch
2309 
2312  art::FindManyP<recob::Hit> const& fmCluHits,
2313  unsigned short const procCode,
2314  geo::TPCID const& tpcid)
2315  {
2316  // sort cluster matches by increasing total match error. Find the minimum total error of all
2317  // cluster match combinations and make tracks from them
2318  CluLen merr;
2319  std::vector<CluLen> materr;
2320 
2321  if (matcomb.size() == 0) return;
2322 
2323  // sort by decreasing error
2324  for (std::size_t ii = 0; ii < matcomb.size(); ++ii) {
2325  merr.index = ii;
2326  merr.length = matcomb[ii].Err + matcomb[ii].oErr;
2327  materr.push_back(merr);
2328  } // ii
2329  std::sort(materr.begin(), materr.end(), lessThan);
2330 
2331  auto const nplanes = wireReadoutGeom.Nplanes(tpcid);
2332  if (prt) {
2333  mf::LogVerbatim myprt("CCTM");
2334  myprt << "SortMatches\n";
2335  myprt << " ii im Vx Err dW dA dX oVx oErr odW odA odX Asym "
2336  " icl jcl kcl \n";
2337  for (std::size_t ii = 0; ii < materr.size(); ++ii) {
2338  unsigned int im = materr[ii].index;
2339  float asym =
2340  fabs(matcomb[im].Chg[0] - matcomb[im].Chg[1]) / (matcomb[im].Chg[0] + matcomb[im].Chg[1]);
2341  asym *=
2342  fabs(matcomb[im].Chg[1] - matcomb[im].Chg[2]) / (matcomb[im].Chg[1] + matcomb[im].Chg[2]);
2343  myprt << std::fixed << std::right << std::setw(5) << ii << std::setw(5) << im
2344  << std::setw(4) << matcomb[im].Vtx << std::setw(7) << std::setprecision(2)
2345  << matcomb[im].Err << std::setw(7) << std::setprecision(1) << matcomb[im].dWir
2346  << std::setw(7) << std::setprecision(2) << matcomb[im].dAng << std::setw(7)
2347  << std::setprecision(2) << matcomb[im].dX << std::setw(4) << matcomb[im].oVtx
2348  << std::setw(7) << std::setprecision(2) << matcomb[im].oErr << std::setw(7)
2349  << std::setprecision(1) << matcomb[im].odWir << std::setw(7) << std::setprecision(2)
2350  << matcomb[im].odAng << std::setw(7) << std::setprecision(2) << matcomb[im].odX
2351  << std::setw(7) << std::setprecision(3) << asym << " 0:" << matcomb[im].Cls[0] << ":"
2352  << matcomb[im].End[0] << " 1:" << matcomb[im].Cls[1] << ":" << matcomb[im].End[1];
2353  if (nplanes > 2) myprt << " 2:" << matcomb[im].Cls[2] << ":" << matcomb[im].End[2];
2354  myprt << "\n";
2355  } // ii
2356  } // prt
2357 
2358  // define an array to ensure clusters are only used once
2359  std::array<std::vector<bool>, 3> pclUsed;
2360  for (unsigned int ipl = 0; ipl < nplanes; ++ipl) {
2361  pclUsed[ipl].resize(clsChain[ipl].size());
2362  }
2363 
2364  // count the total number of clusters and length used in matcomb
2365  unsigned short matcombTotCl = 0;
2366  float matcombTotLen = 0;
2367  unsigned short icl;
2368  for (std::size_t ii = 0; ii < matcomb.size(); ++ii) {
2369  for (unsigned int ipl = 0; ipl < nplanes; ++ipl) {
2370  if (matcomb[ii].Cls[ipl] < 0) continue;
2371  icl = matcomb[ii].Cls[ipl];
2372  ++matcombTotCl;
2373  matcombTotLen += clsChain[ipl][icl].Length;
2374  }
2375  }
2376 
2377  if (prt)
2378  mf::LogVerbatim("CCTM") << "Number of clusters to match " << matcombTotCl << " total length "
2379  << matcombTotLen;
2380 
2381  if (matcombTotLen <= 0) {
2382  mf::LogError("CCTM") << "SortMatches: bad matcomb total length " << matcombTotLen;
2383  return;
2384  }
2385 
2386  // vector of matcomb indices of unique cluster matches
2387  std::vector<unsigned short> matIndex;
2388  // vector of matcomb indices of unique cluster matches that have the best total error
2389  std::vector<unsigned short> bestMatIndex;
2390  float totLen, totErr, bestTotErr = 9999;
2391  // start with the best match
2392  unsigned short jj, jm, nused, jcl;
2393  // fraction of the length of all clustters in matcomb that are used in a match
2394  float fracLen;
2395 
2396  for (std::size_t ii = 0; ii < materr.size(); ++ii) {
2397  unsigned int im = materr[ii].index;
2398  matIndex.clear();
2399  // skip really bad matches
2400  if (matcomb[im].Err > bestTotErr) continue;
2401  totLen = 0;
2402  // initialize pclUsed and flag the clusters in this match
2403  for (unsigned int ipl = 0; ipl < nplanes; ++ipl) {
2404  // initialize to no clusters used
2405  std::fill(pclUsed[ipl].begin(), pclUsed[ipl].end(), false);
2406  // check for 2 plane match
2407  if (matcomb[im].Cls[ipl] < 0) continue;
2408  icl = matcomb[im].Cls[ipl];
2409  pclUsed[ipl][icl] = true;
2410  totLen += clsChain[ipl][icl].Length;
2411  } // ipl
2412  // Initialize the error sum
2413  totErr = matcomb[im].Err;
2414  // Save the index
2415  matIndex.push_back(im);
2416  // look for matches in the rest of the list that are not already matched.
2417  for (jj = 0; jj < materr.size(); ++jj) {
2418  if (jj == ii) continue;
2419  jm = materr[jj].index;
2420  // skip really bad matches
2421  if (matcomb[jm].Err > bestTotErr) continue;
2422  // check for non-unique cluster indices
2423  nused = 0;
2424  for (unsigned int ipl = 0; ipl < nplanes; ++ipl) {
2425  if (matcomb[jm].Cls[ipl] < 0) continue;
2426  jcl = matcomb[jm].Cls[ipl];
2427  if (pclUsed[ipl][jcl]) ++nused;
2428  // This cluster chain was used in a previous match
2429  if (nused > 0) break;
2430  totLen += clsChain[ipl][jcl].Length;
2431  } // ipl
2432  // at least one of the clusters in this match have been used
2433  if (nused != 0) continue;
2434  // found a match with an unmatched set of clusters. Update the total error and flag them
2435  totErr += matcomb[jm].Err;
2436  matIndex.push_back(jm);
2437  // Flag the clusters used and see if all of them are used
2438  for (unsigned int ipl = 0; ipl < nplanes; ++ipl) {
2439  if (matcomb[jm].Cls[ipl] < 0) continue;
2440  jcl = matcomb[jm].Cls[ipl];
2441  pclUsed[ipl][jcl] = true;
2442  } // ipl
2443  } // jm
2444  if (totLen == 0) continue;
2445  nused = 0;
2446  for (unsigned int ipl = 0; ipl < nplanes; ++ipl) {
2447  for (unsigned short indx = 0; indx < pclUsed[ipl].size(); ++indx)
2448  if (pclUsed[ipl][indx]) ++nused;
2449  } // ipl
2450  if (totLen > matcombTotLen) std::cout << "Oops " << totLen << " " << matcombTotLen << "\n";
2451  // weight the total error by the total length of all clusters
2452  fracLen = totLen / matcombTotLen;
2453  totErr /= fracLen;
2454  if (prt) {
2455  mf::LogVerbatim myprt("CCTM");
2456  myprt << "match " << im << " totErr " << totErr << " nused " << nused << " fracLen "
2457  << fracLen << " totLen " << totLen << " mat: ";
2458  for (unsigned short indx = 0; indx < matIndex.size(); ++indx)
2459  myprt << " " << matIndex[indx];
2460  } // prt
2461  // check for more used clusters and a better total error
2462  if (totErr < bestTotErr) {
2463  bestTotErr = totErr;
2464  bestMatIndex = matIndex;
2465  if (nused == matcombTotCl) break;
2466  if (prt) {
2467  mf::LogVerbatim myprt("CCTM");
2468  myprt << "bestTotErr " << bestTotErr << " nused " << nused << " matcombTotCl "
2469  << matcombTotCl << " mat: ";
2470  for (unsigned short indx = 0; indx < bestMatIndex.size(); ++indx)
2471  myprt << " " << bestMatIndex[indx];
2472  } // prt
2473  // stop looking if we have found everything
2474  if (fracLen > 0.999) break;
2475  } // totErr < bestTotErr
2476  } // im
2477 
2478  if (bestTotErr > 9000) return;
2479 
2480  for (std::size_t ii = 0; ii < bestMatIndex.size(); ++ii) {
2481  unsigned int im = bestMatIndex[ii];
2482  FillTrkHits(fmCluHits, im, nplanes);
2483  // look for missing clusters?
2484  // store this track with processor code 1
2485  StoreTrack(detProp, fmCluHits, im, procCode, tpcid);
2486  } // ii
2487 
2488  } // SortMatches
2489 
2492  {
2493  // 2D version of FillEndMatch
2494 
2495  match.Err = 100;
2496  match.oErr = 100;
2497  match.Chg[2] = 0;
2498  match.dWir = 0;
2499  match.dAng = 0;
2500  match.odWir = 0;
2501  match.odAng = 0;
2502 
2503  unsigned short ipl = 0;
2504  unsigned short jpl = 1;
2505 
2506  if (match.Cls[0] < 0 || match.Cls[1] < 0) return;
2507 
2508  unsigned short icl = match.Cls[ipl];
2509  unsigned short iend = match.End[ipl];
2510  match.Chg[ipl] = clsChain[ipl][icl].TotChg;
2511  // cluster i match end
2512  float miX = clsChain[ipl][icl].X[iend];
2513  // cluster i other end
2514  unsigned short oiend = 1 - iend;
2515  float oiX = clsChain[ipl][icl].X[oiend];
2516 
2517  unsigned short jcl = match.Cls[jpl];
2518  unsigned short jend = match.End[jpl];
2519  match.Chg[jpl] = clsChain[jpl][jcl].TotChg;
2520  // cluster j match end
2521  float mjX = clsChain[jpl][jcl].X[jend];
2522  // cluster j other end
2523  unsigned short ojend = 1 - jend;
2524  float ojX = clsChain[jpl][jcl].X[ojend];
2525 
2526  // look for a match end vertex match
2527  match.Vtx = -1;
2528  if (clsChain[ipl][icl].VtxIndex[iend] >= 0 &&
2529  clsChain[ipl][icl].VtxIndex[iend] == clsChain[jpl][jcl].VtxIndex[jend]) {
2530  match.Vtx = clsChain[ipl][icl].VtxIndex[iend];
2531  miX = vtx[match.Vtx].X;
2532  mjX = vtx[match.Vtx].X;
2533  }
2534 
2535  // look for an other end vertex match
2536  match.oVtx = -1;
2537  if (clsChain[ipl][icl].VtxIndex[oiend] >= 0 &&
2538  clsChain[ipl][icl].VtxIndex[oiend] == clsChain[jpl][jcl].VtxIndex[ojend]) {
2539  match.oVtx = clsChain[ipl][icl].VtxIndex[oiend];
2540  oiX = vtx[match.oVtx].X;
2541  ojX = vtx[match.oVtx].X;
2542  }
2543 
2544  // find the charge asymmetry
2545  float chgAsym = 1;
2546  if (fChgAsymFactor[algIndex] > 0) {
2547  chgAsym = fabs(match.Chg[ipl] - match.Chg[jpl]) / (match.Chg[ipl] + match.Chg[jpl]);
2548  if (chgAsym > 0.5) return;
2549  chgAsym = 1 + fChgAsymFactor[algIndex] * chgAsym;
2550  }
2551 
2552  // find the error at the match end
2553  float maxSlp = fabs(clsChain[ipl][icl].Slope[iend]);
2554  if (fabs(clsChain[jpl][jcl].Slope[jend]) > maxSlp)
2555  maxSlp = fabs(clsChain[jpl][jcl].Slope[jend]);
2556  float sigmaX = fXMatchErr[algIndex] + std::max(maxSlp, (float)20);
2557  match.dX = fabs(miX - mjX);
2558  match.Err = chgAsym * match.dX / sigmaX;
2559 
2560  // find the error at the other end
2561  maxSlp = fabs(clsChain[ipl][icl].Slope[oiend]);
2562  if (fabs(clsChain[jpl][jcl].Slope[ojend]) > maxSlp)
2563  maxSlp = fabs(clsChain[jpl][jcl].Slope[ojend]);
2564  sigmaX = fXMatchErr[algIndex] + std::max(maxSlp, (float)20);
2565  match.odX = fabs(oiX - ojX);
2566  match.oErr = chgAsym * match.odX / sigmaX;
2567 
2568  if (prt)
2569  mf::LogVerbatim("CCTM") << "FEM2: m " << ipl << ":" << icl << ":" << iend << " miX " << miX
2570  << " - " << jpl << ":" << jcl << ":" << jend << " mjX " << mjX
2571  << " match.dX " << match.dX << " match.Err " << match.Err
2572  << " chgAsym " << chgAsym << " o "
2573  << " oiX " << oiX << " ojX " << ojX << " match.odX " << match.odX
2574  << " match.oErr " << match.oErr << "\n";
2575 
2576  } // FillEndMatch2
2577 
2580  geo::TPCID const& tpcid,
2581  MatchPars& match)
2582  {
2583  // fill the matching parameters for this cluster match. The calling routine
2584  // should set the match end vertex ID (if applicable) as well as the
2585  // cluster IDs and matching ends in each plane. Note that the matching variables
2586  // Note that dWir, dAng and dTim are not filled if there is a vertex (match.Vtx >= 0).
2587  // Likewise, odWir, odAng and odX are not filled if there is a vertex match
2588  // at the other end
2589  auto const nplanes = wireReadoutGeom.Nplanes(tpcid);
2590 
2591  if (nplanes == 2) {
2592  FillEndMatch2(match);
2593  return;
2594  }
2595 
2596  std::array<short, 3> mVtx;
2597  std::array<short, 3> oVtx;
2598  std::array<float, 3> oWir;
2599  std::array<float, 3> oSlp;
2600  std::array<float, 3> oAng;
2601  std::array<float, 3> oX;
2602 
2603  std::array<float, 3> mChg;
2604 
2605  unsigned short ii, ipl, iend, jpl, jend, kpl, kend, oend;
2606  short icl, jcl, kcl;
2607 
2608  for (ipl = 0; ipl < 3; ++ipl) {
2609  mVtx[ipl] = -1;
2610  oVtx[ipl] = -1;
2611  oWir[ipl] = -66;
2612  oSlp[ipl] = -66;
2613  oAng[ipl] = -66;
2614  oX[ipl] = -66;
2615  mChg[ipl] = -1;
2616  } // ipl
2617 
2618  // initialize parameters that shouldn't have been set by the calling routine
2619  match.dWir = 0;
2620  match.dAng = 0;
2621  match.dX = 0;
2622  match.Err = 100;
2623  match.odWir = 0;
2624  match.odAng = 0;
2625  match.odX = 0;
2626  match.oErr = 100;
2627  match.oVtx = -1;
2628 
2629  if (prt) {
2630  mf::LogVerbatim myprt("CCTM");
2631  myprt << "FEM ";
2632  for (ipl = 0; ipl < nplanes; ++ipl) {
2633  myprt << " " << ipl << ":" << match.Cls[ipl] << ":" << match.End[ipl];
2634  }
2635  }
2636 
2637  short missingPlane = -1;
2638  unsigned short nClInPln = 0;
2639  // number of vertex matches at each end
2640  short aVtx = -1;
2641  unsigned short novxmat = 0;
2642  short aoVtx = -1;
2643  unsigned short nvxmat = 0;
2644  unsigned short nShortCl = 0;
2645  // fill the other end parameters in each plane
2646  for (ipl = 0; ipl < nplanes; ++ipl) {
2647  if (match.Cls[ipl] < 0) {
2648  missingPlane = ipl;
2649  continue;
2650  }
2651  ++nClInPln;
2652  icl = match.Cls[ipl];
2653  match.Chg[ipl] = clsChain[ipl][icl].TotChg;
2654  mChg[ipl] = clsChain[ipl][icl].TotChg;
2655  iend = match.End[ipl];
2656  mVtx[ipl] = clsChain[ipl][icl].VtxIndex[iend];
2657  if (clsChain[ipl][icl].Length < 6) ++nShortCl;
2658  if (mVtx[ipl] >= 0) {
2659  if (aVtx < 0) aVtx = mVtx[ipl];
2660  if (mVtx[ipl] == aVtx) ++nvxmat;
2661  }
2662  if (prt)
2663  mf::LogVerbatim("CCTM") << "FEM: m " << ipl << ":" << icl << ":" << iend << " Vtx "
2664  << mVtx[ipl] << " Wir " << clsChain[ipl][icl].Wire[iend]
2665  << std::fixed << std::setprecision(3) << " Slp "
2666  << clsChain[ipl][icl].Slope[iend] << std::fixed
2667  << std::setprecision(1) << " X " << clsChain[ipl][icl].X[iend];
2668 
2669  oend = 1 - iend;
2670  oWir[ipl] = clsChain[ipl][icl].Wire[oend];
2671  oAng[ipl] = clsChain[ipl][icl].Angle[oend];
2672  oSlp[ipl] = clsChain[ipl][icl].Slope[oend];
2673  oX[ipl] = clsChain[ipl][icl].X[oend];
2674  oVtx[ipl] = clsChain[ipl][icl].VtxIndex[oend];
2675  if (oVtx[ipl] >= 0) {
2676  if (aoVtx < 0) aoVtx = oVtx[ipl];
2677  if (oVtx[ipl] == aoVtx) ++novxmat;
2678  }
2679 
2680  if (prt)
2681  mf::LogVerbatim("CCTM") << " o " << ipl << ":" << icl << ":" << oend << " oVtx "
2682  << oVtx[ipl] << " oWir " << oWir[ipl] << std::fixed
2683  << std::setprecision(3) << " oSlp " << oSlp[ipl] << std::fixed
2684  << std::setprecision(1) << " oX " << oX[ipl] << " Chg "
2685  << (int)mChg[ipl];
2686 
2687  } // ipl
2688 
2689  bool isShort = (nShortCl > 1);
2690 
2691  if (nClInPln < 2) {
2692  mf::LogWarning("CCTM") << "Not enough matched planes supplied";
2693  return;
2694  }
2695 
2696  if (prt)
2697  mf::LogVerbatim("CCTM") << "FEM: Vtx m " << aVtx << " count " << nvxmat << " o " << aoVtx
2698  << " count " << novxmat << " missingPlane " << missingPlane
2699  << " nClInPln " << nClInPln;
2700 
2701  // perfect match
2702  if (nvxmat == 3 && novxmat == 3) {
2703  match.Vtx = aVtx;
2704  match.Err = 0;
2705  match.oVtx = aoVtx;
2706  match.oErr = 0;
2707  return;
2708  }
2709 
2710  // 2-plane vertex match?
2711  // factors applied to error = 1 (no vtx), 0.5 (2 pln vtx), 0.33 (3 pln vtx)
2712  float vxFactor = 1;
2713  float ovxFactor = 1;
2714  if (nClInPln == 3) {
2715  // a cluster in all 3 planes
2716  if (nvxmat == 3) {
2717  // and all vertex assignments agree at the match end
2718  match.Vtx = aVtx;
2719  vxFactor = 0.33;
2720  }
2721  if (novxmat == 3) {
2722  // and all vertex assignments agree at the other end
2723  match.oVtx = aoVtx;
2724  ovxFactor = 0.33;
2725  }
2726  }
2727  else {
2728  // a cluster in 2 planes
2729  if (nvxmat == 2) {
2730  match.Vtx = aVtx;
2731  vxFactor = 0.5;
2732  }
2733  if (novxmat == 2) {
2734  match.oVtx = aoVtx;
2735  ovxFactor = 0.5;
2736  }
2737  } // nClInPln
2738 
2739  // find wire, X and Time at both ends
2740 
2741  // Find the "other end" matching parameters with
2742  // two cases: a 3-plane match or a 2-plane match
2743  // and with/without an other end vertex
2744 
2745  float kWir, okWir;
2746  float kSlp, okSlp, kAng, okAng, okX, kX, kTim, okTim;
2747 
2748  if (nClInPln == 3) {
2749  ipl = 0;
2750  jpl = 1;
2751  kpl = 2;
2752  }
2753  else {
2754  // 2-plane match
2755  kpl = missingPlane;
2756  if (kpl == 0) {
2757  ipl = 1;
2758  jpl = 2;
2759  }
2760  else if (kpl == 1) {
2761  ipl = 2;
2762  jpl = 0;
2763  }
2764  else {
2765  ipl = 0;
2766  jpl = 1;
2767  } // kpl test
2768  } // missing plane
2769  iend = match.End[ipl];
2770  jend = match.End[jpl];
2771  icl = match.Cls[ipl];
2772  jcl = match.Cls[jpl];
2773  if (nplanes > 2) {
2774  kcl = match.Cls[kpl];
2775  kend = match.End[kpl];
2776  }
2777 
2778  geo::PlaneID const iplaneid{tpcid, ipl};
2779  geo::PlaneID const jplaneid{tpcid, jpl};
2780  geo::PlaneID const kplaneid{tpcid, kpl};
2781  auto const& kplane = wireReadoutGeom.Plane(kplaneid);
2783  okSlp = wireReadoutGeom.ThirdPlaneSlope(ipl, oSlp[ipl], jpl, oSlp[jpl], tpcid);
2784  okAng = atan(okSlp);
2785  // handle the case near pi/2, where the errors on large slopes could result in
2786  // a wrong-sign kAng
2787  bool ignoreSign = (fabs(okSlp) > 10);
2788  if (ignoreSign) okAng = fabs(okAng);
2789  if (match.oVtx >= 0) {
2790  // a vertex exists at the other end
2791  okWir = kplane.WireCoordinate(geo::Point_t{0, vtx[match.oVtx].Y, vtx[match.oVtx].Z});
2792  okX = vtx[match.oVtx].X;
2793  }
2794  else {
2795  // no vertex at the other end
2796  auto const [ypos, zpos, _] =
2798  .WireIDsIntersect(geo::WireID(iplaneid, oWir[ipl]), geo::WireID(jplaneid, oWir[jpl]))
2799  .value_or(geo::WireIDIntersection::invalid());
2800  okWir = 0.5 + kplane.WireCoordinate(geo::Point_t{0, ypos, zpos});
2801  okX = 0.5 * (oX[ipl] + oX[jpl]);
2802  }
2803  okTim = detProp.ConvertXToTicks(okX, kplaneid);
2804  if (prt)
2805  mf::LogVerbatim("CCTM") << "FEM: oEnd"
2806  << " kpl " << kpl << " okSlp " << okSlp << " okAng " << okAng
2807  << " okWir " << (int)okWir << " okX " << okX;
2808 
2810 
2812  ipl, clsChain[ipl][icl].Slope[iend], jpl, clsChain[jpl][jcl].Slope[jend], kplaneid);
2813  kAng = atan(kSlp);
2814  if (ignoreSign) kAng = fabs(kAng);
2815  if (match.Vtx >= 0) {
2816  if (vtx.size() == 0 || (unsigned int)match.Vtx > vtx.size() - 1) {
2817  mf::LogError("CCTM") << "FEM: Bad match.Vtx " << match.Vtx << " vtx size " << vtx.size();
2818  return;
2819  }
2820  // a vertex exists at the match end
2821  kWir = kplane.WireCoordinate(geo::Point_t{0, vtx[match.Vtx].Y, vtx[match.Vtx].Z});
2822  kX = vtx[match.Vtx].X;
2823  }
2824  else {
2825  // no vertex at the match end
2826  auto const [ypos, zpos, _] =
2828  .WireIDsIntersect(geo::WireID(iplaneid, clsChain[ipl][icl].Wire[iend]),
2829  geo::WireID(jplaneid, clsChain[jpl][jcl].Wire[jend]))
2830  .value_or(geo::WireIDIntersection::invalid());
2831  kWir = 0.5 + kplane.WireCoordinate(geo::Point_t{0, ypos, zpos});
2832  kX = 0.5 * (clsChain[ipl][icl].X[iend] + clsChain[jpl][jcl].X[jend]);
2833  }
2834  kTim = detProp.ConvertXToTicks(kX, kplaneid);
2835  if (prt)
2836  mf::LogVerbatim("CCTM") << "FEM: mEnd"
2837  << " kpl " << kpl << " kSlp " << kSlp << " kAng " << kAng << " kX "
2838  << kX;
2839 
2840  // try to find a 3-plane match using this information
2841  if (nClInPln < 3 && FindMissingCluster(kpl, kcl, kend, kWir, kX, okWir, okX)) {
2842  nClInPln = 3;
2843  // update local variables
2844  match.Cls[kpl] = kcl;
2845  match.End[kpl] = kend;
2846  match.Chg[kpl] = clsChain[kpl][kcl].TotChg;
2847  mChg[kpl] = clsChain[kpl][kcl].TotChg;
2848  oend = 1 - kend;
2849  oWir[kpl] = clsChain[kpl][kcl].Wire[oend];
2850  oX[kpl] = clsChain[kpl][kcl].X[oend];
2851  oAng[kpl] = clsChain[kpl][kcl].Angle[oend];
2852  oSlp[kpl] = clsChain[kpl][kcl].Slope[oend];
2853  } // FindMissingCluster
2854 
2855  // decide whether to continue with a 2-plane match. The distance between match and other end should
2856  // be large enough to create a cluster
2857  if (nClInPln == 2 && fabs(okWir - kWir) > 3) return;
2858 
2859  // Calculate the cluster charge asymmetry. This factor will be applied
2860  // to the error of the end matches
2861  float chgAsym = 1;
2862  // Get the charge in the plane without a matching cluster
2863  if (nClInPln < 3 && mChg[missingPlane] <= 0) {
2864  if (missingPlane != kpl)
2865  mf::LogError("CCTM") << "FEM bad missingPlane " << missingPlane << " " << kpl << "\n";
2866  mChg[kpl] = ChargeNear(kplaneid, (unsigned short)kWir, kTim, (unsigned short)okWir, okTim);
2867  match.Chg[kpl] = mChg[kpl];
2868  if (prt)
2869  mf::LogVerbatim("CCTM") << "FEM: Missing cluster in " << kpl << " ChargeNear " << (int)kWir
2870  << ":" << (int)kTim << " " << (int)okWir << ":" << (int)okTim
2871  << " chg " << mChg[kpl];
2872  if (mChg[kpl] <= 0) return;
2873  }
2874 
2875  if (fChgAsymFactor[algIndex] > 0) {
2876  chgAsym = ChargeAsym(mChg);
2877  if (chgAsym > 0.5) return;
2878  chgAsym = 1 + fChgAsymFactor[algIndex] * chgAsym;
2879  }
2880 
2881  if (prt) mf::LogVerbatim("CCTM") << "FEM: charge asymmetry factor " << chgAsym;
2882  float sigmaX, sigmaA;
2883  float da, dx, dw;
2884 
2886  // check for vertex consistency at the match end
2887  aVtx = -1;
2888  bool allPlnVtxMatch = false;
2889  if (nClInPln == 3) {
2890  unsigned short nmvtx = 0;
2891  for (ii = 0; ii < nplanes; ++ii) {
2892  if (mVtx[ii] >= 0) {
2893  if (aVtx < 0) aVtx = mVtx[ii];
2894  ++nmvtx;
2895  }
2896  } // ii
2897  // same vertex in all planes
2898  if (nmvtx) allPlnVtxMatch = true;
2899  } // nClInPln
2900 
2901  // inflate the X match error to allow for missing one wire on the end of a cluster
2902  sigmaX = fXMatchErr[algIndex] + std::max(kSlp, (float)20);
2903  sigmaA = fAngleMatchErr[algIndex] * AngleFactor(kSlp);
2904  if (prt)
2905  mf::LogVerbatim("CCTM") << "bb " << algIndex << " " << fXMatchErr[algIndex] << " "
2906  << fAngleMatchErr[algIndex] << " kslp " << kSlp;
2907 
2908  if (nClInPln == 3) {
2909  kcl = match.Cls[kpl];
2910  kend = match.End[kpl];
2911  dw = kWir - clsChain[kpl][kcl].Wire[kend];
2912  match.dWir = dw;
2913  if (fabs(match.dWir) > 100) return;
2914  if (match.Vtx >= 0) { match.dX = kX - clsChain[kpl][kcl].X[kend]; }
2915  else {
2916  match.dX = std::abs(clsChain[ipl][icl].X[iend] - clsChain[jpl][jcl].X[jend]) +
2917  std::abs(clsChain[ipl][icl].X[iend] - clsChain[kpl][kcl].X[kend]);
2918  }
2919  if (prt) mf::LogVerbatim("CCTM") << " dw " << dw << " dx " << match.dX;
2920  // TODO: Angle matching has problems with short clusters
2921  if (!isShort) {
2922  if (ignoreSign) { match.dAng = kAng - fabs(clsChain[kpl][kcl].Angle[kend]); }
2923  else {
2924  match.dAng = kAng - clsChain[kpl][kcl].Angle[kend];
2925  }
2926  } // !isShort
2927  da = fabs(match.dAng) / sigmaA;
2928  dx = fabs(match.dX) / sigmaX;
2929  if (allPlnVtxMatch) {
2930  // matched vertex. Use angle for match error
2931  match.Err = vxFactor * chgAsym * da / 3;
2932  if (prt) mf::LogVerbatim("CCTM") << " 3-pln w Vtx match.Err " << match.Err;
2933  }
2934  else {
2935  dw /= 2;
2936  // divide by 9
2937  match.Err = vxFactor * chgAsym * sqrt(dx * dx + da * da + dw * dw) / 9;
2938  if (prt) mf::LogVerbatim("CCTM") << " 3-pln match.Err " << match.Err;
2939  }
2940  }
2941  else {
2942  // 2-plane match
2943  match.dWir = -1;
2944  match.dAng = -1;
2945  match.dX = clsChain[ipl][icl].X[iend] - clsChain[jpl][jcl].X[jend];
2946  // degrade error by 3 for 2-plane matches
2947  match.Err = 3 + vxFactor * chgAsym * fabs(match.dX) / sigmaX;
2948  if (prt) mf::LogVerbatim("CCTM") << " 2-pln Err " << match.Err;
2949  } // !(nClInPln == 3)
2950 
2952  if (nClInPln == 3) {
2953  // A cluster in all 3 planes
2954  dw = okWir - oWir[kpl];
2955  match.odWir = dw;
2956  if (match.oVtx >= 0) { match.odX = okX - oX[kpl]; }
2957  else {
2958  match.odX = std::abs(oX[ipl] - oX[jpl]) + std::abs(oX[ipl] - oX[kpl]);
2959  }
2960  if (prt)
2961  mf::LogVerbatim("CCTM") << " odw " << match.odWir << " odx " << match.odX << " sigmaX "
2962  << sigmaX;
2963  // TODO: CHECK FOR SHOWER-LIKE CLUSTER OTHER END MATCH
2964  if (!isShort) {
2965  if (ignoreSign) { match.odAng = okAng - fabs(oAng[kpl]); }
2966  else {
2967  match.odAng = okAng - oAng[kpl];
2968  }
2969  } // !isShort
2970  da = match.odAng / sigmaA;
2971  dx = fabs(match.odX) / sigmaX;
2972  // error for wire number match
2973  dw /= 2;
2974  // divide by number of planes with clusters * 3 for dx, da and dw
2975  match.oErr = ovxFactor * chgAsym * sqrt(dx * dx + da * da + dw * dw) / 9;
2976  if (prt) mf::LogVerbatim("CCTM") << " 3-pln match.oErr " << match.oErr;
2977  }
2978  else {
2979  // Only 2 clusters in 3 planes
2980  match.odX = (oX[ipl] - oX[jpl]) / sigmaX;
2981  match.oErr = 3 + ovxFactor * chgAsym * fabs(match.odX);
2982  if (prt) mf::LogVerbatim("CCTM") << " 2-pln match.oErr " << match.oErr;
2983  }
2984  } // FillEndMatch
2985 
2987  bool CCTrackMaker::FindMissingCluster(unsigned short kpl,
2988  short& kcl,
2989  unsigned short& kend,
2990  float kWir,
2991  float kX,
2992  float okWir,
2993  float okX)
2994  {
2995  // try to attach a missing cluster to the cluster chain kcl. kend is the "match end"
2996 
2997  unsigned short okend;
2998  float dxcut;
2999 
3000  if (kcl >= 0) return false;
3001 
3002  // Look for a missing cluster with loose cuts
3003  float kslp = fabs((okX - kX) / (okWir - kWir));
3004  if (kslp > 20) kslp = 20;
3005  // expand dX cut assuming there is a missing hit on the end of a cluster => 1 wire
3006  dxcut = 3 * fXMatchErr[algIndex] + kslp;
3007  unsigned short nfound = 0;
3008  unsigned short foundCl = 0, foundEnd = 0;
3009  for (unsigned short ccl = 0; ccl < clsChain[kpl].size(); ++ccl) {
3010  if (clsChain[kpl][ccl].InTrack >= 0) continue;
3011  // require a match at both ends
3012  for (unsigned short end = 0; end < 2; ++end) {
3013  okend = 1 - end;
3014  if (fabs(clsChain[kpl][ccl].Wire[end] - kWir) > 4) continue;
3015  if (fabs(clsChain[kpl][ccl].Wire[okend] - okWir) > 4) continue;
3016  // require at least one end to match
3017  if (fabs(clsChain[kpl][ccl].X[end] - kX) > dxcut &&
3018  fabs(clsChain[kpl][ccl].X[okend] - okX) > dxcut)
3019  continue;
3020  ++nfound;
3021  foundCl = ccl;
3022  foundEnd = end;
3023  } // end
3024  } // ccl
3025  if (nfound == 0) return false;
3026  if (nfound > 1) {
3027  mf::LogVerbatim("CCTM") << "FindMissingCluster: Found too many matches. Write some code "
3028  << nfound;
3029  return false;
3030  }
3031  kcl = foundCl;
3032  kend = foundEnd;
3033  return true;
3034 
3035  } // FindMissingCluster
3036 
3038  float CCTrackMaker::ChargeAsym(std::array<float, 3>& mChg)
3039  {
3040  // find charge asymmetry between the cluster with the highest (lowest)
3041  // charge
3042  float big = 0, small = 1.E9;
3043  for (unsigned short ii = 0; ii < 3; ++ii) {
3044  if (mChg[ii] < small) small = mChg[ii];
3045  if (mChg[ii] > big) big = mChg[ii];
3046  }
3047  // chgAsym varies between 0 (perfect charge match) and 1 (dreadfull)
3048  return (big - small) / (big + small);
3049  } // CalculateChargeAsym
3050 
3053  unsigned short imat,
3054  unsigned short nplanes)
3055  {
3056  // Fills the trkHits vector using cluster hits associated with the match combo imat
3057 
3058  unsigned short ipl;
3059 
3060  for (ipl = 0; ipl < 3; ++ipl)
3061  trkHits[ipl].clear();
3062 
3063  if (imat > matcomb.size()) return;
3064 
3065  unsigned short indx;
3066  std::vector<art::Ptr<recob::Hit>> clusterhits;
3067  unsigned short icc, ccl, icl, ecl, iht, ii;
3068  short endOrder, fillOrder;
3069 
3070  if (prt)
3071  mf::LogVerbatim("CCTM") << "In FillTrkHits: matcomb " << imat << " cluster chains "
3072  << matcomb[imat].Cls[0] << " " << matcomb[imat].Cls[1] << " "
3073  << matcomb[imat].Cls[2];
3074 
3075  for (unsigned int ipl = 0; ipl < nplanes; ++ipl) {
3076  if (matcomb[imat].Cls[ipl] < 0) continue;
3077  // ccl is the cluster chain index
3078  ccl = matcomb[imat].Cls[ipl];
3079  // endOrder = 1 for normal order (hits added from US to DS), and -1 for reverse order
3080  endOrder = 1 - 2 * matcomb[imat].End[ipl];
3081  // re-order the sequence of cluster indices for reverse order
3082  if (endOrder < 0) {
3083  std::reverse(clsChain[ipl][ccl].ClsIndex.begin(), clsChain[ipl][ccl].ClsIndex.end());
3084  std::reverse(clsChain[ipl][ccl].Order.begin(), clsChain[ipl][ccl].Order.end());
3085  for (ii = 0; ii < clsChain[ipl][ccl].Order.size(); ++ii)
3086  clsChain[ipl][ccl].Order[ii] = 1 - clsChain[ipl][ccl].Order[ii];
3087  }
3088  if (ccl > clsChain[ipl].size() - 1) {
3089  mf::LogError("CCTM") << "Bad cluster chain index " << ccl << " in plane " << ipl;
3090  continue;
3091  }
3092  // loop over all the clusters in the chain
3093  for (icc = 0; icc < clsChain[ipl][ccl].ClsIndex.size(); ++icc) {
3094  icl = clsChain[ipl][ccl].ClsIndex[icc];
3095  if (icl > fmCluHits.size() - 1) {
3096  std::cout << "oops in FTH " << icl << " clsChain size " << clsChain[ipl].size() << "\n";
3097  exit(1);
3098  }
3099  ecl = cls[ipl][icl].EvtIndex;
3100  if (ecl > fmCluHits.size() - 1) {
3101  std::cout << "FTH bad EvtIndex " << ecl << " fmCluHits size " << fmCluHits.size() << "\n";
3102  continue;
3103  }
3104  clusterhits = fmCluHits.at(ecl);
3105  if (clusterhits.size() == 0) {
3106  std::cout << "FTH no cluster hits for EvtIndex " << cls[ipl][icl].EvtIndex << "\n";
3107  continue;
3108  }
3109  indx = trkHits[ipl].size();
3110  trkHits[ipl].resize(indx + clusterhits.size());
3111  // ensure the hit fill ordering is consistent
3112  fillOrder = 1 - 2 * clsChain[ipl][ccl].Order[icc];
3113  if (fillOrder == 1) {
3114  for (iht = 0; iht < clusterhits.size(); ++iht) {
3115  if (indx + iht > trkHits[ipl].size() - 1) std::cout << "FTH oops3\n";
3116  trkHits[ipl][indx + iht] = clusterhits[iht];
3117  }
3118  }
3119  else {
3120  for (ii = 0; ii < clusterhits.size(); ++ii) {
3121  iht = clusterhits.size() - 1 - ii;
3122  if (indx + ii > trkHits[ipl].size() - 1) std::cout << "FTH oops4\n";
3123  trkHits[ipl][indx + ii] = clusterhits[iht];
3124  } // ii
3125  }
3126  } // icc
3127  ii = trkHits[ipl].size() - 1;
3128  if (prt)
3129  mf::LogVerbatim("CCTM") << "plane " << ipl << " first p " << trkHits[ipl][0]->WireID().Plane
3130  << " w " << trkHits[ipl][0]->WireID().Wire << ":"
3131  << (int)trkHits[ipl][0]->PeakTime() << " last p "
3132  << trkHits[ipl][ii]->WireID().Plane << " w "
3133  << trkHits[ipl][ii]->WireID().Wire << ":"
3134  << (int)trkHits[ipl][ii]->PeakTime();
3135  } // ipl
3136 
3137  // TODO Check the ends of trkHits to see if there are missing hits that should have been included
3138  // in a cluster
3139 
3140  } // FillTrkHits
3141 
3144  {
3145  mf::LogVerbatim myprt("CCTM");
3146  myprt << "********* PrintTracks \n";
3147  myprt << "vtx Index X Y Z\n";
3148  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
3149  myprt << std::right << std::setw(4) << ivx << std::setw(4) << vtx[ivx].EvtIndex;
3150  myprt << std::fixed;
3151  myprt << std::right << std::setw(10) << std::setprecision(1) << vtx[ivx].X;
3152  myprt << std::right << std::setw(7) << std::setprecision(1) << vtx[ivx].Y;
3153  myprt << std::right << std::setw(7) << std::setprecision(1) << vtx[ivx].Z;
3154  if (vtx[ivx].Neutrino) myprt << " Neutrino vertex";
3155  myprt << "\n";
3156  } // ivx
3157 
3158  myprt << ">>>>>>>>>> Tracks \n";
3159  myprt << "trk ID Proc nht nTrj sX sY sZ eX eY eZ sVx eVx sGd eGd "
3160  "ChgOrd dirZ Mom PDG ClsIndices\n";
3161  for (unsigned short itr = 0; itr < trk.size(); ++itr) {
3162  myprt << std::right << std::setw(3) << itr << std::setw(4) << trk[itr].ID;
3163  myprt << std::right << std::setw(5) << std::setw(4) << trk[itr].Proc;
3164  unsigned short nht = 0;
3165  for (unsigned short ii = 0; ii < 3; ++ii)
3166  nht += trk[itr].TrkHits[ii].size();
3167  myprt << std::right << std::setw(5) << nht;
3168  myprt << std::setw(4) << trk[itr].TrjPos.size();
3169  myprt << std::fixed;
3170  myprt << std::right << std::setw(7) << std::setprecision(1) << trk[itr].TrjPos[0](0);
3171  myprt << std::right << std::setw(7) << std::setprecision(1) << trk[itr].TrjPos[0](1);
3172  myprt << std::right << std::setw(7) << std::setprecision(1) << trk[itr].TrjPos[0](2);
3173  unsigned short itj = trk[itr].TrjPos.size() - 1;
3174  myprt << std::right << std::setw(7) << std::setprecision(1) << trk[itr].TrjPos[itj](0);
3175  myprt << std::right << std::setw(7) << std::setprecision(1) << trk[itr].TrjPos[itj](1);
3176  myprt << std::right << std::setw(7) << std::setprecision(1) << trk[itr].TrjPos[itj](2);
3177  myprt << std::setw(4) << trk[itr].VtxIndex[0] << std::setw(4) << trk[itr].VtxIndex[1];
3178  myprt << std::setw(4) << trk[itr].GoodEnd[0];
3179  myprt << std::setw(4) << trk[itr].GoodEnd[1];
3180  myprt << std::setw(4) << trk[itr].ChgOrder;
3181  myprt << std::right << std::setw(10) << std::setprecision(3) << trk[itr].TrjDir[itj](2);
3182  myprt << std::right << std::setw(4) << trk[itr].MomID;
3183  myprt << std::right << std::setw(5) << trk[itr].PDGCode << " ";
3184  for (unsigned short ii = 0; ii < trk[itr].ClsEvtIndices.size(); ++ii)
3185  myprt << " " << trk[itr].ClsEvtIndices[ii];
3186  myprt << "\n";
3187  } // itr
3188 
3189  } // PrintTracks
3190 
3193  geo::TPCID const& tpcid) const
3194  {
3195 
3196  unsigned short iTime;
3197  mf::LogVerbatim myprt("CCTM");
3198  myprt << "******* PrintClusters ********* Num_Clusters_in Wire:Time\n";
3199  myprt << "vtx Index X Y Z Pln0 Pln1 Pln2 Pln0 Pln1 Pln2\n";
3200  for (unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
3201  myprt << std::right << std::setw(3) << ivx << std::setw(7) << ivx;
3202  myprt << std::fixed;
3203  myprt << std::right << std::setw(7) << std::setprecision(1) << vtx[ivx].X;
3204  myprt << std::right << std::setw(7) << std::setprecision(1) << vtx[ivx].Y;
3205  myprt << std::right << std::setw(7) << std::setprecision(1) << vtx[ivx].Z;
3206  myprt << std::right << std::setw(5) << vtx[ivx].nClusInPln[0];
3207  myprt << std::right << std::setw(5) << vtx[ivx].nClusInPln[1];
3208  myprt << std::right << std::setw(5) << vtx[ivx].nClusInPln[2];
3209  myprt << " ";
3210  for (auto const& plane : wireReadoutGeom.Iterate<geo::PlaneGeo>(tpcid)) {
3211  int time = 0.5 + detProp.ConvertXToTicks(vtx[ivx].X, plane.ID());
3212  int wire = plane.WireCoordinate(geo::Point_t{0, vtx[ivx].Y, vtx[ivx].Z});
3213  myprt << std::right << std::setw(7) << wire << ":" << time;
3214  }
3215 
3216  myprt << "\n";
3217  } // ivx
3218 
3219  auto const nplanes = wireReadoutGeom.Nplanes(tpcid);
3220  for (unsigned short ipl = 0; ipl < nplanes; ++ipl) {
3221  myprt << ">>>>>>>>>> Cluster chains in Plane " << ipl << "\n";
3222  myprt << "ipl ccl Len Chg W0:T0 Ang0 Dir0 Vx0 mBk0 W1:T1 Ang1 Dir1 Vx1 "
3223  " mBk1 InTk cls:Order \n";
3224  for (unsigned short ccl = 0; ccl < clsChain[ipl].size(); ++ccl) {
3225  myprt << std::right << std::setw(3) << ipl;
3226  myprt << std::right << std::setw(5) << ccl;
3227  myprt << std::right << std::setw(6) << clsChain[ipl][ccl].Length;
3228  myprt << std::right << std::setw(8) << (int)clsChain[ipl][ccl].TotChg;
3229  for (unsigned short end = 0; end < 2; ++end) {
3230  iTime = clsChain[ipl][ccl].Time[end];
3231  myprt << std::right << std::setw(5) << (int)clsChain[ipl][ccl].Wire[end] << ":"
3232  << std::setprecision(1) << iTime;
3233  if (iTime < 10) { myprt << " "; }
3234  else if (iTime < 100) {
3235  myprt << " ";
3236  }
3237  else if (iTime < 1000)
3238  myprt << " ";
3239  myprt << std::right << std::setw(7) << std::setprecision(2)
3240  << clsChain[ipl][ccl].Angle[end];
3241  myprt << std::right << std::setw(5) << clsChain[ipl][ccl].Dir[end];
3242  myprt << std::right << std::setw(5) << clsChain[ipl][ccl].VtxIndex[end];
3243  myprt << std::fixed << std::right << std::setw(6) << std::setprecision(1)
3244  << clsChain[ipl][ccl].mBrkIndex[end];
3245  }
3246  myprt << std::right << std::setw(7) << clsChain[ipl][ccl].InTrack;
3247  myprt << " ";
3248  for (unsigned short ii = 0; ii < clsChain[ipl][ccl].ClsIndex.size(); ++ii)
3249  myprt << " " << clsChain[ipl][ccl].ClsIndex[ii] << ":" << clsChain[ipl][ccl].Order[ii];
3250  myprt << "\n";
3251  } // ccl
3252  if (fPrintAllClusters) {
3253  myprt << ">>>>>>>>>> Clusters in Plane " << ipl << "\n";
3254  myprt << "ipl icl Evt Len Chg W0:T0 Ang0 Dir0 Vx0 CN0 W1:T1 Ang1 "
3255  "Dir1 Vx1 CN1 InTk Brk0 MrgEr0 Brk1 MrgEr1\n";
3256  for (unsigned short icl = 0; icl < cls[ipl].size(); ++icl) {
3257  myprt << std::right << std::setw(3) << ipl;
3258  myprt << std::right << std::setw(5) << icl;
3259  myprt << std::right << std::setw(5) << cls[ipl][icl].EvtIndex;
3260  myprt << std::right << std::setw(6) << cls[ipl][icl].Length;
3261  myprt << std::right << std::setw(8) << (int)cls[ipl][icl].TotChg;
3262  for (unsigned short end = 0; end < 2; ++end) {
3263  iTime = cls[ipl][icl].Time[end];
3264  myprt << std::right << std::setw(5) << (int)cls[ipl][icl].Wire[end] << ":" << iTime;
3265  if (iTime < 10) { myprt << " "; }
3266  else if (iTime < 100) {
3267  myprt << " ";
3268  }
3269  else if (iTime < 1000)
3270  myprt << " ";
3271  myprt << std::right << std::setw(7) << std::setprecision(2) << cls[ipl][icl].Angle[end];
3272  myprt << std::right << std::setw(5) << cls[ipl][icl].Dir[end];
3273  myprt << std::right << std::setw(5) << cls[ipl][icl].VtxIndex[end];
3274  myprt << std::fixed << std::right << std::setw(5) << std::setprecision(1)
3275  << cls[ipl][icl].ChgNear[end];
3276  }
3277  myprt << std::fixed;
3278  myprt << std::right << std::setw(5) << cls[ipl][icl].InTrack;
3279  myprt << std::right << std::setw(5) << (int)cls[ipl][icl].BrkIndex[0];
3280  myprt << std::right << std::setw(7) << std::setprecision(1)
3281  << cls[ipl][icl].MergeError[0];
3282  myprt << std::right << std::setw(5) << (int)cls[ipl][icl].BrkIndex[1];
3283  myprt << std::right << std::setw(7) << std::setprecision(1)
3284  << cls[ipl][icl].MergeError[1];
3285  myprt << "\n";
3286  } // icl
3287  } // fPrintAllClusters
3288  } // ipl
3289  } // PrintClusters
3290 
3292  float CCTrackMaker::AngleFactor(float slope)
3293  {
3294  float slp = fabs(slope);
3295  if (slp > 10.) slp = 30.;
3296  // return a value between 1 and 46
3297  return 1 + 0.05 * slp * slp;
3298  } // AngleFactor
3299 
3302  unsigned short wire1,
3303  float time1,
3304  unsigned short wire2,
3305  float time2)
3306  {
3307  // returns the hit charge along a line between (wire1, time1) and
3308  // (wire2, time2)
3309 
3310  // put in increasing wire order (wire2 > wire1)
3311  unsigned short w1 = wire1;
3312  unsigned short w2 = wire2;
3313  double t1 = time1;
3314  double t2 = time2;
3315  double slp, prtime;
3316  if (w1 == w2) { slp = 0; }
3317  else {
3318  if (w1 > w2) {
3319  w1 = wire2;
3320  w2 = wire1;
3321  t1 = time2;
3322  t2 = time1;
3323  }
3324  slp = (t2 - t1) / (w2 - w1);
3325  }
3326 
3327  unsigned short wire;
3328 
3329  float chg = 0;
3330  for (unsigned short hit = 0; hit < allhits.size(); ++hit) {
3331  if (allhits[hit]->WireID().asPlaneID() != planeid) continue;
3332  wire = allhits[hit]->WireID().Wire;
3333  if (wire < w1) continue;
3334  if (wire > w2) continue;
3335  prtime = t1 + (wire - w1) * slp;
3336  if (prtime > allhits[hit]->PeakTimePlusRMS(3)) continue;
3337  if (prtime < allhits[hit]->PeakTimeMinusRMS(3)) continue;
3338  chg += ChgNorm[planeid.Plane] * allhits[hit]->Integral();
3339  } // hit
3340  return chg;
3341  } // ChargeNear
3342 
3345  {
3346  // fills the WireHitRange vector. Slightly modified version of the one in ClusterCrawlerAlg
3347 
3348  for (unsigned int ipl = 0; ipl < 3; ++ipl) {
3349  firstWire[ipl] = INT_MAX;
3350  lastWire[ipl] = 0;
3351  firstHit[ipl] = INT_MAX;
3352  lastHit[ipl] = 0;
3353  WireHitRange[ipl].clear();
3354  ChgNorm[ipl] = 0;
3355  }
3356 
3357  // find the first and last wire with a hit
3358  unsigned short oldipl = 0;
3359  for (unsigned int hit = 0; hit < allhits.size(); ++hit) {
3360  if (static_cast<geo::TPCID const&>(allhits[hit]->WireID()) != tpcid) continue;
3361  auto const ipl = allhits[hit]->WireID().Plane;
3362  if (allhits[hit]->WireID().Wire >
3363  wireReadoutGeom.Nwires(allhits[hit]->WireID().asPlaneID())) {
3364  if (lastWire[ipl] < firstWire[ipl]) {
3365  mf::LogError("CCTM") << "Invalid WireID().Wire " << allhits[hit]->WireID().Wire;
3366  return;
3367  }
3368  }
3369  // ChgNorm[ipl] += allhits[hit]->Integral();
3370  if (ipl < oldipl) {
3371  mf::LogError("CCTM") << "Hits are not in increasing-plane order\n";
3372  return;
3373  }
3374  oldipl = ipl;
3375  if (firstHit[ipl] == INT_MAX) {
3376  firstHit[ipl] = hit;
3377  firstWire[ipl] = allhits[hit]->WireID().Wire;
3378  }
3379  lastHit[ipl] = hit;
3380  lastWire[ipl] = allhits[hit]->WireID().Wire;
3381  } // hit
3382 
3383  // xxx
3384  auto const nplanes = wireReadoutGeom.Nplanes(tpcid);
3385  for (unsigned int ipl = 0; ipl < nplanes; ++ipl) {
3386  if (lastWire[ipl] < firstWire[ipl]) {
3387  mf::LogError("CCTM") << "Invalid first/last wire in plane " << ipl;
3388  return;
3389  }
3390  } // ipl
3391 
3392  // normalize charge in induction planes to the collection plane
3393  // for(ipl = 0; ipl < nplanes; ++ipl) ChgNorm[ipl] = ChgNorm[nplanes - 1] / ChgNorm[ipl];
3394  for (unsigned int ipl = 0; ipl < nplanes; ++ipl)
3395  ChgNorm[ipl] = 1;
3396 
3397  // now we can define the WireHitRange vector.
3398  int sflag, nwires, wire;
3399  unsigned int indx, thisWire, thisHit, lastFirstHit;
3400  //uint32_t chan;
3401  for (unsigned int ipl = 0; ipl < nplanes; ++ipl) {
3402  nwires = lastWire[ipl] - firstWire[ipl] + 1;
3403  WireHitRange[ipl].resize(nwires);
3404  // start by defining the "no hits on wire" condition
3405  sflag = -2;
3406  for (wire = 0; wire < nwires; ++wire)
3407  WireHitRange[ipl][wire] = std::make_pair(sflag, sflag);
3408  // overwrite with the "dead wires" condition
3409  sflag = -1;
3410  // next overwrite with the index of the first/last hit on each wire
3411  lastWire[ipl] = firstWire[ipl];
3412  thisHit = firstHit[ipl];
3413  lastFirstHit = firstHit[ipl];
3414  for (unsigned int hit = firstHit[ipl]; hit <= lastHit[ipl]; ++hit) {
3415  thisWire = allhits[hit]->WireID().Wire;
3416  if (thisWire > lastWire[ipl]) {
3417  indx = lastWire[ipl] - firstWire[ipl];
3418  int tmp1 = lastFirstHit;
3419  int tmp2 = thisHit;
3420  WireHitRange[ipl][indx] = std::make_pair(tmp1, tmp2);
3421  lastWire[ipl] = thisWire;
3422  lastFirstHit = thisHit;
3423  }
3424  else if (thisWire < lastWire[ipl]) {
3425  mf::LogError("CCTM") << "Hit not in proper order in plane " << ipl;
3426  exit(1);
3427  }
3428  ++thisHit;
3429  } // hit
3430  // define the last wire
3431  indx = lastWire[ipl] - firstWire[ipl];
3432  int tmp1 = lastFirstHit;
3433  ++lastHit[ipl];
3434  int tmp2 = lastHit[ipl];
3435  WireHitRange[ipl][indx] = std::make_pair(tmp1, tmp2);
3436  // add one to lastWire and lastHit for more standard indexing
3437  ++lastWire[ipl];
3438  } // ipl
3439 
3440  // error checking
3441  for (unsigned int ipl = 0; ipl < nplanes; ++ipl) {
3442  if (firstWire[ipl] < INT_MAX) continue;
3443  if (lastWire[ipl] > 0) continue;
3444  if (firstHit[ipl] < INT_MAX) continue;
3445  if (lastHit[ipl] > 0) continue;
3446  std::cout << "FWHR problem\n";
3447  exit(1); // FIXME: Should not exit from library code
3448  } // ipl
3449 
3450  unsigned int nht = 0;
3451  std::vector<bool> hchk(allhits.size());
3452  for (unsigned int ii = 0; ii < hchk.size(); ++ii)
3453  hchk[ii] = false;
3454  for (unsigned int ipl = 0; ipl < nplanes; ++ipl) {
3455  for (unsigned int w = firstWire[ipl]; w < lastWire[ipl]; ++w) {
3456  indx = w - firstWire[ipl];
3457  if (indx > lastWire[ipl]) {
3458  std::cout << "FWHR bad index " << indx << "\n";
3459  exit(1);
3460  }
3461  // no hit on this wire
3462  if (WireHitRange[ipl][indx].first < 0) continue;
3463  unsigned int firhit = WireHitRange[ipl][indx].first;
3464  unsigned int lashit = WireHitRange[ipl][indx].second;
3465  for (unsigned int hit = firhit; hit < lashit; ++hit) {
3466  ++nht;
3467  if (hit > allhits.size() - 1) {
3468  std::cout << "FWHR: Bad hchk " << hit << " size " << allhits.size() << "\n";
3469  continue;
3470  }
3471  hchk[hit] = true;
3472  if (allhits[hit]->WireID().Plane != ipl || allhits[hit]->WireID().Wire != w) {
3473  std::cout << "FWHR bad plane " << allhits[hit]->WireID().Plane << " " << ipl
3474  << " or wire " << allhits[hit]->WireID().Wire << " " << w << "\n";
3475  exit(1);
3476  }
3477  } // hit
3478  } // w
3479  } // ipl
3480 
3481  if (nht != allhits.size()) {
3482  std::cout << "FWHR hit count problem " << nht << " " << allhits.size() << "\n";
3483  for (unsigned int ii = 0; ii < hchk.size(); ++ii)
3484  if (!hchk[ii])
3485  std::cout << " " << ii << " " << allhits[ii]->WireID().Plane << " "
3486  << allhits[ii]->WireID().Wire << " " << (int)allhits[ii]->PeakTime() << "\n";
3487  exit(1);
3488  }
3489 
3490  } // FillWireHitRange
3492 
3493 } // namespace
MaybeLogger_< ELseverityLevel::ELsev_info, true > LogVerbatim
float Length(const PFPStruct &pfp)
Definition: PFPUtils.cxx:3267
std::vector< MatchPars > matcomb
float ChargeNear(geo::PlaneID const &planeid, unsigned short wire1, float time1, unsigned short wire2, float time2)
constexpr auto const & right(const_AssnsIter< L, R, D, Dir > const &a, const_AssnsIter< L, R, D, Dir > const &b)
Definition: AssnsIter.h:102
art::ServiceHandle< geo::Geometry const > geom
unsigned int Nwires(PlaneID const &planeid) const
Returns the total number of wires in the specified plane.
TTree * t1
Definition: plottest35.C:26
std::string fVertexModuleLabel
double WireCoordinate(Point_t const &point) const
Returns the coordinate of the point on the plane, in wire units.
Definition: PlaneGeo.h:704
void StoreTrack(detinfo::DetectorPropertiesData const &detProp, art::FindManyP< recob::Hit > const &fmCluHits, unsigned short imat, unsigned short procCode, geo::TPCID const &tpcid)
std::array< float, 3 > ChgNorm
std::vector< Vector_t > convertCollToVector(std::vector< Vector > const &coll)
Definition: TrackingTypes.h:78
std::vector< float > fChgAsymFactor
std::array< unsigned short, 3 > End
Declaration of signal hit object.
void PrintClusters()
std::array< unsigned int, 3 > firstWire
void MakeClusterChains(detinfo::DetectorPropertiesData const &detProp, art::FindManyP< recob::Hit > const &fmCluHits, geo::TPCID const &tpcid)
Float_t Y
Definition: plot.C:37
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
std::vector< art::Ptr< recob::Hit > > allhits
void FindMaybeVertices(geo::TPCID const &tpcid)
geo::WireReadoutGeom const & wireReadoutGeom
std::array< short, 2 > Dir
TrackTrajectory::Flags_t Flags_t
Definition: Track.h:67
constexpr auto abs(T v)
Returns the absolute value of the argument.
float StartWire() const
Returns the wire coordinate of the start of the cluster.
Definition: Cluster.h:276
float dXClTraj(art::FindManyP< recob::Hit > const &fmCluHits, unsigned short ipl, unsigned short icl1, unsigned short end1)
std::array< std::vector< art::Ptr< recob::Hit > >, 3 > seedHits
std::string fClusterModuleLabel
void FitVertices(detinfo::DetectorPropertiesData const &detProp, geo::TPCID const &tpcid)
TrackTrajectoryAlg fTrackTrajectoryAlg
ROOT::Math::SMatrix< Double32_t, 5, 5, ROOT::Math::MatRepSym< Double32_t, 5 >> SMatrixSym55
void FillWireHitRange(geo::TPCID inTPCID)
Definition: Utils.cxx:4325
cout<< "Opened file "<< fin<< " ixs= "<< ixs<< endl;if(ixs==0) hhh=(TH1F *) fff-> Get("h1")
Definition: AddMC.C:8
Set of hits with a 2D structure.
Definition: Cluster.h:69
std::array< short, 2 > BrkIndex
float EndTick() const
Returns the tick coordinate of the end of the cluster.
Definition: Cluster.h:331
Float_t tmp
Definition: plot.C:35
std::vector< unsigned short > pfpToTrkID
void FillTrkHits(art::FindManyP< recob::Hit > const &fmCluHits, unsigned short imat, unsigned short nplanes)
data_const_reference data(size_type i) const
Definition: FindManyP.h:446
std::vector< TVector3 > TrjPos
std::vector< TrkPar > trk
bool FindMissingCluster(unsigned short kpl, short &kcl, unsigned short &kend, float kWir, float kX, float okWir, float okX)
MaybeLogger_< ELseverityLevel::ELsev_error, false > LogError
Cluster finding and building.
std::vector< float > fAngleMatchErr
std::array< float, 2 > ChgNear
std::array< std::vector< clPar >, 3 > cls
float StartAngle() const
Returns the starting angle of the cluster.
Definition: Cluster.h:461
Definition of vertex object for LArSoft.
Definition: Vertex.h:35
std::array< float, 2 > Slope
std::array< bool, 2 > EndInTPC
double Length() const
Length is associated with z coordinate [cm].
Definition: TPCGeo.h:110
Float_t Z
Definition: plot.C:37
PutHandle< PROD > put(std::unique_ptr< PROD > &&edp, std::string const &instance={})
Definition: Event.h:77
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
std::array< std::vector< art::Ptr< recob::Hit > >, 3 > trkHits
std::array< std::vector< ClsChainPar >, 3 > clsChain
float EndCharge() const
Returns the charge on the last wire of the cluster.
Definition: Cluster.h:483
Point_t GetCenter() const
Returns the center of the TPC volume in world coordinates [cm].
Definition: TPCGeo.h:132
TCanvas * c1
Definition: plotHisto.C:7
TCanvas * c2
Definition: plot_hist.C:75
void TagCosmics(geo::TPCID const &tpcid)
#define DEFINE_ART_MODULE(klass)
Definition: ModuleMacros.h:65
IDparameter< geo::WireID > WireID
Member type of validated geo::WireID parameter.
long seed
Definition: chem4.cc:67
A trajectory in space reconstructed from hits.
Interface for a class providing readout channel mapping to geometry.
void FillEndMatch(detinfo::DetectorPropertiesData const &detProp, geo::TPCID const &tpcid, MatchPars &match)
std::vector< float > fMatchMinLen
std::vector< unsigned short > Order
Geometry information for a single wire plane.The plane is represented in the geometry by a solid whic...
Definition: PlaneGeo.h:67
float AngleFactor(float slope)
std::array< short, 2 > VtxIndex
std::array< short, 2 > mVtxIndex
double ConvertXToTicks(double X, int p, int t, int c) const
std::vector< vtxPar > vtx
std::array< float, 2 > MergeError
void produce(art::Event &evt) override
void fill(const art::PtrVector< recob::Hit > &hits, int only_plane)
std::array< bool, 2 > GoodEnd
std::vector< Point_t > convertCollToPoint(std::vector< Point > const &coll)
Definition: TrackingTypes.h:68
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
Declaration of cluster object.
void TrackTrajectory(std::array< std::vector< geo::WireID >, 3 > trkWID, std::array< std::vector< double >, 3 > trkX, std::array< std::vector< double >, 3 > trkXErr, std::vector< TVector3 > &TrajPos, std::vector< TVector3 > &TrajDir)
bool DupMatch(MatchPars &match, unsigned short nplanes)
std::array< unsigned int, 3 > firstHit
std::array< float, 2 > Charge
void VtxMatch(detinfo::DetectorPropertiesData const &detProp, art::FindManyP< recob::Hit > const &fmCluHits, geo::TPCID const &tpcid)
std::vector< unsigned short > ClsIndex
std::vector< unsigned short > ClsEvtIndices
Detector simulation of raw signals on wires.
std::array< unsigned int, 3 > lastWire
TTree * t2
Definition: plottest35.C:36
bool WireIDsIntersect(WireID const &wid1, WireID const &wid2, Point_t &intersection) const
Computes the intersection between two wires.
Class defining a plane for tracking. It provides various functionalities to convert track parameters ...
Definition: TrackingPlane.h:37
void MakeFamily(geo::TPCID const &tpcid)
unsigned int Nplanes(TPCID const &tpcid=details::tpc_zero) const
Returns the total number of planes in the specified TPC.
double ConvertTicksToX(double ticks, int p, int t, int c) const
double HalfHeight() const
Height is associated with y coordinate [cm].
Definition: TPCGeo.h:106
bool greaterThan(CluLen c1, CluLen c2)
bool CreateAssn(art::Event &evt, std::vector< T > const &a, art::Ptr< U > const &b, art::Assns< U, T > &assn, std::string a_instance, size_t index=UINT_MAX)
Creates a single one-to-one association.
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
void VertexFit(std::vector< std::vector< geo::WireID >> const &hitWID, std::vector< std::vector< double >> const &hitX, std::vector< std::vector< double >> const &hitXErr, TVector3 &VtxPos, TVector3 &VtxPosErr, std::vector< TVector3 > &TrkDir, std::vector< TVector3 > &TrkDirErr, float &ChiDOF) const
Hierarchical representation of particle flow.
Definition: PFParticle.h:44
bool getByLabel(std::string const &label, std::string const &instance, Handle< PROD > &result) const
void FillEndMatch2(MatchPars &match)
Utility object to perform functions of association.
Float_t norm
float StartCharge() const
Returns the charge on the first wire of the cluster.
Definition: Cluster.h:440
Provides recob::Track data product.
TDirectory * dir
Definition: macro.C:5
std::array< short, 2 > VtxIndex
void PrintClusters(detinfo::DetectorPropertiesData const &detProp, geo::TPCID const &tpcid) const
std::array< unsigned int, 3 > lastHit
std::array< float, 2 > X
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
range_type< T > Iterate() const
Definition: Iterable.h:121
void PlnMatch(detinfo::DetectorPropertiesData const &detProp, geo::TPCID const &tpcid)
bool lessThan(CluLen c1, CluLen c2)
static constexpr WireIDIntersection invalid()
Definition: geo_types.h:594
decltype(auto) constexpr begin(T &&obj)
ADL-aware version of std::begin.
Definition: StdUtils.h:69
std::vector< TVector3 > TrjDir
void FillWireHitRange(geo::TPCID const &tpcid)
PlaneGeo const & Plane(TPCID const &tpcid, View_t view) const
Returns the specified wire.
TCEvent evt
Definition: DataStructs.cxx:8
std::vector< bool > fMakeAlgTracks
std::vector< short > fMatchAlgs
std::vector< float > fXMatchErr
void fill_ptr_vector(std::vector< Ptr< T >> &ptrs, H const &h)
Definition: Ptr.h:306
TPCGeo const & TPC(TPCID const &tpcid=details::tpc_zero) const
Returns the specified TPC.
Definition: GeometryCore.h:448
float EndAngle() const
Returns the ending angle of the cluster.
Definition: Cluster.h:504
recob::tracking::Plane Plane
Definition: TrackState.h:17
float ChargeAsym(std::array< float, 3 > &mChg)
Float_t X
Definition: plot.C:37
Float_t track
Definition: plot.C:35
std::array< float, 2 > Wire
Float_t w
Definition: plot.C:20
float StartTick() const
Returns the tick coordinate of the start of the cluster.
Definition: Cluster.h:287
std::array< std::vector< art::Ptr< recob::Hit > >, 3 > TrkHits
void FillChgNear(detinfo::DetectorPropertiesData const &detProp, geo::TPCID const &tpcid)
std::array< short, 2 > Time
art framework interface to geometry description
double HalfWidth() const
Width is associated with x coordinate [cm].
Definition: TPCGeo.h:102
double ThirdPlaneSlope(PlaneID const &pid1, double slope1, PlaneID const &pid2, double slope2, PlaneID const &output_plane) const
Returns the slope on the third plane, given it in the other two.
Track from a non-cascading particle.A recob::Track consists of a recob::TrackTrajectory, plus additional members relevant for a "fitted" track:
Definition: Track.h:49
std::array< float, 2 > Angle
vec_iX clear()
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
Encapsulate the construction of a single detector plane .
std::array< std::vector< std::pair< int, int > >, 3 > WireHitRange
std::array< std::vector< unsigned short >, 3 > vxCls
std::array< unsigned short, 3 > nClusInPln
unsigned short fNVtxTrkHitsFit
float Integral() const
Returns the total charge of the cluster from hit shape.
Definition: Cluster.h:581
float EndWire() const
Returns the wire coordinate of the end of the cluster.
Definition: Cluster.h:318
void SortMatches(detinfo::DetectorPropertiesData const &detProp, art::FindManyP< recob::Hit > const &fmCluHits, unsigned short procCode, geo::TPCID const &tpcid)
vertex reconstruction