All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
HoughSeedFinderAlg.cxx
Go to the documentation of this file.
1 /**
2  * @file HoughSeedFinderAlg.cxx
3  *
4  * @brief Implementation of the Seed Finder Algorithm based on a Hough Transform
5  *
6  * The intent of this algorithm is to take an input list of 3D space points and from those
7  * to find candidate track start points and directions. It does so by first performing a
8  * Principal Components Analysis of the input 3D hits and then projects them to the plane
9  * of largest spread. A standard Hough Transform method is then applied to attempt to identify
10  * straight line segments which can be used as seeds to the kalman filter tracker.
11  */
12 
13 // The main include
15 
16 // Framework Includes
17 #include "art/Framework/Services/Registry/ServiceHandle.h"
18 
19 // LArSoft includes
22 
23 // ROOT includes
24 #include "TCanvas.h"
25 #include "TDecompSVD.h"
26 #include "TFrame.h"
27 #include "TH2D.h"
28 #include "TMatrixD.h"
29 #include "TVectorD.h"
30 
31 // std includes
32 #include <memory>
33 
34 //------------------------------------------------------------------------------------------------------------------------------------------
35 
36 //------------------------------------------------------------------------------------------------------------------------------------------
37 // implementation follows
38 
39 namespace lar_cluster3d {
40 
41  HoughSeedFinderAlg::HoughSeedFinderAlg(fhicl::ParameterSet const& pset)
42  : m_minimum3DHits(5)
43  , m_thetaBins(360)
44  , m_rhoBins(75)
45  , m_hiThresholdMin(5)
46  , m_hiThresholdFrac(.05)
47  , m_loThresholdFrac(0.85)
48  , m_numSeed2DHits(80)
49  , m_numAveDocas(6.)
50  , m_numSkippedHits(10)
51  , m_maxLoopsPerCluster(3)
52  , m_maximumGap(5.)
53  , m_pcaAlg(pset.get<fhicl::ParameterSet>("PrincipalComponentsAlg"))
54  , m_displayHist(false)
55  {
56  m_minimum3DHits = pset.get<size_t>("Minimum3DHits", 5);
57  m_thetaBins = pset.get<int>("ThetaBins", 360);
58  m_rhoBins = pset.get<int>("HalfRhoBins", 75);
59  m_hiThresholdMin = pset.get<size_t>("HiThresholdMin", 5);
60  m_hiThresholdFrac = pset.get<double>("HiThresholdFrac", 0.05);
61  m_loThresholdFrac = pset.get<double>("LoThresholdFrac", 0.85);
62  m_numSeed2DHits = pset.get<size_t>("NumSeed2DHits", 80);
63  m_numAveDocas = pset.get<double>("NumAveDocas", 6.);
64  m_numSkippedHits = pset.get<int>("NumSkippedHits", 10);
65  m_maxLoopsPerCluster = pset.get<int>("MaxLoopsPerCluster", 3);
66  m_maximumGap = pset.get<double>("MaximumGap", 5.);
67  m_displayHist = pset.get<bool>("DisplayHoughHist", false);
68  m_geometry = art::ServiceHandle<geo::Geometry const>{}.get();
69  }
70 
71  //------------------------------------------------------------------------------------------------------------------------------------------
72 
74  /**
75  * @brief A utility class to contain the values of a given "bin" in Hough Space
76  *
77  * Specifically, this is keeping track of the projected x,y coordinates of a given
78  * 3D hit projected to the plane of largest spread in PCA and an interator to
79  * that hit in the input container
80  */
81  public:
82  AccumulatorValues() : m_position(0., 0., 0.) {}
83  AccumulatorValues(const Eigen::Vector3f& position,
84  const reco::HitPairListPtr::const_iterator& itr)
85  : m_position(position), m_hit3DIterator(itr)
86  {}
87 
88  const Eigen::Vector3f&
89  getPosition() const
90  {
91  return m_position;
92  }
93  reco::HitPairListPtr::const_iterator
95  {
96  return m_hit3DIterator;
97  }
98 
99  private:
100  Eigen::Vector3f
101  m_position; ///< We really only need the x,y coordinates here but keep all three for now
102  reco::HitPairListPtr::const_iterator
103  m_hit3DIterator; ///< This will be used to take us back to our 3D hit
104  };
105 
106  typedef std::vector<AccumulatorValues> AccumulatorValuesVec;
107 
109  /**
110  * @brief A utility class used to accumulate the above values
111  *
112  * One of these objects will exist for each "bin" in rho-theta space and this will
113  * be used to accumulate the 3D hits which contribute to this bin
114  */
115  public:
118  {
119  m_accumulatorValuesVec.push_back(values);
120  }
121 
122  void
124  {
125  m_visited = true;
126  }
127  void
129  {
130  m_noise = true;
131  }
132  void
134  {
135  m_inCluster = true;
136  }
137 
138  void
140  {
141  m_accumulatorValuesVec.push_back(value);
142  }
143 
144  bool
145  isVisited() const
146  {
147  return m_visited;
148  }
149  bool
150  isNoise() const
151  {
152  return m_noise;
153  }
154  bool
155  isInCluster() const
156  {
157  return m_inCluster;
158  }
159 
160  const AccumulatorValuesVec&
162  {
163  return m_accumulatorValuesVec;
164  }
165 
166  private:
167  bool m_visited;
168  bool m_noise;
171  };
172 
174  /**
175  * @brief This is used to sort "Hough Clusters" by the maximum entries in a bin
176  */
177  public:
179  {}
180 
181  bool
184  {
185  size_t peakCountLeft(0);
186  size_t peakCountRight(0);
187 
188  for (const auto& binIndex : left)
189  peakCountLeft = std::max(peakCountLeft, m_accMap[binIndex].getAccumulatorValues().size());
190  for (const auto& binIndex : right)
191  peakCountRight = std::max(peakCountRight, m_accMap[binIndex].getAccumulatorValues().size());
192 
193  return peakCountLeft > peakCountRight;
194  }
195 
196  private:
198  };
199 
201  /**
202  * @brief This is used to sort bins in Hough Space
203  */
204  bool
205  operator()(const HoughSeedFinderAlg::RhoThetaAccumulatorBinMap::iterator& left,
206  const HoughSeedFinderAlg::RhoThetaAccumulatorBinMap::iterator& right)
207  {
208  size_t leftSize = left->second.getAccumulatorValues().size();
209  size_t rightSize = right->second.getAccumulatorValues().size();
210 
211  return leftSize > rightSize;
212  }
213  };
214 
215  void
217  RhoThetaAccumulatorBinMap& rhoThetaAccumulatorBinMap,
218  HoughCluster& neighborPts,
219  size_t threshold) const
220  {
221  /**
222  * @brief Does a query of nearest neighbors to look for matching bins
223  */
224 
225  // We simply loop over the nearest indices and see if we have any friends over threshold
226  for (int rhoIdx = curBin.first - 1; rhoIdx <= curBin.first + 1; rhoIdx++) {
227  for (int jdx = curBin.second - 1; jdx <= curBin.second + 1; jdx++) {
228  // Skip the self reference
229  if (rhoIdx == curBin.first && jdx == curBin.second) continue;
230 
231  // Theta bin needs to handle the wrap.
232  int thetaIdx(jdx);
233 
234  if (thetaIdx < 0)
235  thetaIdx = m_thetaBins - 1;
236  else if (thetaIdx > m_thetaBins - 1)
237  thetaIdx = 0;
238 
239  BinIndex binIndex(rhoIdx, thetaIdx);
240  RhoThetaAccumulatorBinMap::iterator mapItr = rhoThetaAccumulatorBinMap.find(binIndex);
241 
242  if (mapItr != rhoThetaAccumulatorBinMap.end()) {
243  if (mapItr->second.getAccumulatorValues().size() >= threshold)
244  neighborPts.push_back(binIndex);
245  }
246  }
247  }
248 
249  return;
250  }
251 
252  void
254  HoughCluster& neighborPts,
255  HoughCluster& houghCluster,
256  RhoThetaAccumulatorBinMap& rhoThetaAccumulatorBinMap,
257  size_t threshold) const
258  {
259  /**
260  * @brief The workhorse routine for a DBScan like clustering routine to identify peak bins in Hough Space
261  */
262 
263  // Start by adding the input point to our Hough Cluster
264  houghCluster.push_back(curBin);
265 
266  for (auto& binIndex : neighborPts) {
267  AccumulatorBin& accBin = rhoThetaAccumulatorBinMap[binIndex];
268 
269  if (!accBin.isVisited()) {
270  accBin.setVisited();
271 
272  HoughCluster nextNeighborPts;
273 
274  HoughRegionQuery(binIndex, rhoThetaAccumulatorBinMap, nextNeighborPts, threshold);
275 
276  if (!nextNeighborPts.empty()) {
277  for (auto& neighborIdx : nextNeighborPts) {
278  neighborPts.push_back(neighborIdx);
279  }
280  }
281  }
282 
283  if (!accBin.isInCluster()) {
284  houghCluster.push_back(binIndex);
285  accBin.setInCluster();
286  }
287  }
288 
289  return;
290  }
291 
292  bool
294  {
295  return *left < *right;
296  }
297 
299  bool
301  {
302  return Hit3DCompare(left, right);
303  }
304  };
305 
307  public:
308  OrderHitsAlongWire(int plane = 0) : m_plane(plane) {}
309 
310  bool
312  {
313  int planeToCheck = (m_plane + 1) % 3;
314 
315  return left->getHits()[planeToCheck]->WireID().Wire <
316  right->getHits()[planeToCheck]->WireID().Wire;
317  }
318 
319  private:
320  int m_plane;
321  };
322 
324  bool
325  operator()(const std::pair<size_t, size_t>& left, const std::pair<size_t, size_t>& right)
326  {
327  return left.second < right.second;
328  }
329  };
330 
331  void
333  reco::HitPairListPtr& outputList) const
334  {
335  // Intention is to try to find the largest "contiguous" block of hits in the input list
336  // In a nutshell, the idea is to order input hits according to the pca, then
337  // loop through the hits and store them in a new hit list. If a gap is detected then
338  // we terminate the previous list, create a new one and continue. After the loop over
339  // hits is complete then simply pick the biggest list.
340  // Step I is to run the pca and order the hits
342 
343  m_pcaAlg.PCAAnalysis_3D(inputHitList, pca, true);
344 
345  // It would seem that the analysis can fail!
346  if (!pca.getSvdOK()) {
347  outputList = inputHitList;
348  return;
349  }
350 
351  m_pcaAlg.PCAAnalysis_calc3DDocas(inputHitList, pca);
352 
353  inputHitList.sort(SeedFinderAlgBase::Sort3DHitsByArcLen3D());
354  outputList.clear();
355 
356  // Create containers to hold our hit lists
357  reco::HitPairListPtr continuousHitList;
358 
359  std::map<size_t, reco::HitPairListPtr> gapHitListMap;
360 
361  // Remember the distance arc length of the last hit
362  double arcLenLastHit = inputHitList.front()->getArclenToPoca();
363 
364  // Loop through the input hits
365  for (const auto& hit3D : inputHitList) {
366  // Hits in order, delta arclength should always be positive
367  double arcLen = hit3D->getArclenToPoca();
368  double deltaArcLen = arcLen - arcLenLastHit;
369 
370  if (deltaArcLen > m_maximumGap) {
371  gapHitListMap[continuousHitList.size()] = continuousHitList;
372  continuousHitList.clear();
373  }
374 
375  continuousHitList.emplace_back(hit3D);
376 
377  arcLenLastHit = arcLen;
378  }
379 
380  if (!continuousHitList.empty()) gapHitListMap[continuousHitList.size()] = continuousHitList;
381 
382  // Get the largest list of hits
383  std::map<size_t, reco::HitPairListPtr>::reverse_iterator longestListItr =
384  gapHitListMap.rbegin();
385 
386  if (longestListItr != gapHitListMap.rend()) {
387  size_t nContinuousHits = longestListItr->first;
388  reco::HitPairListPtr longestList = longestListItr->second;
389 
390  outputList.resize(nContinuousHits);
391  std::copy(longestList.begin(), longestList.end(), outputList.begin());
392  }
393 
394  return;
395  }
396 
397  void
400  int& nLoops,
401  RhoThetaAccumulatorBinMap& rhoThetaAccumulatorBinMap,
402  HoughClusterList& houghClusters) const
403  {
404  // The goal of this function is to do a basic Hough Transform on the input list of 3D hits.
405  // In order to transform this to a 2D problem, the 3D hits are projected to the plane of the two
406  // largest eigen values from the input principal components analysis axis.
407  // There are two basic steps to the job here:
408  // 1) Build and accumlate a rho-theta map
409  // 2) Go through that rho-theta map and find candidate Hough "clusters"
410  // Unfortunately, the following may not be suitable viewing for those who may be feint of heart
411  //
412  // Define some constants
413  static int histCount(0);
414  const double maxTheta(M_PI); // Obviously, 180 degrees
415  const double thetaBinSize(maxTheta / double(m_thetaBins)); // around 4 degrees (45 bins)
416  const double rhoBinSizeMin(m_geometry->WirePitch()); // Wire spacing gives a natural bin size?
417 
418  // Recover the parameters from the Principal Components Analysis that we need to project and accumulate
419  Eigen::Vector3f pcaCenter(
420  pca.getAvePosition()[0], pca.getAvePosition()[1], pca.getAvePosition()[2]);
421  Eigen::Vector3f planeVec0(pca.getEigenVectors().row(2));
422  Eigen::Vector3f planeVec1(pca.getEigenVectors().row(1));
423  Eigen::Vector3f pcaPlaneNrml(pca.getEigenVectors().row(0));
424  double eigenVal0 = 3. * sqrt(pca.getEigenValues()[2]);
425  double eigenVal1 = 3. * sqrt(pca.getEigenValues()[1]);
426  double maxRho = std::sqrt(eigenVal0 * eigenVal0 + eigenVal1 * eigenVal1) * 2. / 3.;
427  double rhoBinSize = maxRho / double(m_rhoBins);
428 
429  if (rhoBinSize < rhoBinSizeMin) rhoBinSize = rhoBinSizeMin;
430 
431  // **********************************************************************
432  // Part I: Accumulate values in the rho-theta map
433  // **********************************************************************
434 
435  size_t maxBinCount(0);
436  int nAccepted3DHits(0);
437 
438  // Commence looping over the input 3D hits and fill our accumulator bins
439  for (reco::HitPairListPtr::const_iterator hit3DItr = hitPairListPtr.begin();
440  hit3DItr != hitPairListPtr.end();
441  hit3DItr++) {
442  // Recover the hit
443  const reco::ClusterHit3D* hit3D(*hit3DItr);
444 
445  // Skip hits which are not skeleton points
446  if (!(hit3D->getStatusBits() & 0x10000000)) continue;
447 
448  nAccepted3DHits++;
449 
450  Eigen::Vector3f hit3DPosition(
451  hit3D->getPosition()[0], hit3D->getPosition()[1], hit3D->getPosition()[2]);
452  Eigen::Vector3f pcaToHitVec = hit3DPosition - pcaCenter;
453  Eigen::Vector3f pcaToHitPlaneVec(pcaToHitVec.dot(planeVec0), pcaToHitVec.dot(planeVec1), 0.);
454  double xPcaToHit = pcaToHitPlaneVec[0];
455  double yPcaToHit = pcaToHitPlaneVec[1];
456 
457  // Create an accumulator value
458  AccumulatorValues accValue(pcaToHitPlaneVec, hit3DItr);
459 
460  // Commence loop over theta to fill accumulator bins
461  // Note that with theta in the range 0-pi then we can have negative values for rho
462  for (int thetaIdx = 0; thetaIdx < m_thetaBins; thetaIdx++) {
463  // We need to convert our theta index to an angle
464  double theta = thetaBinSize * double(thetaIdx);
465 
466  // calculate rho for this angle
467  double rho = xPcaToHit * std::cos(theta) + yPcaToHit * std::sin(theta);
468 
469  // Get the rho index
470  int rhoIdx = std::round(rho / rhoBinSize);
471 
472  // Accumulate
473  BinIndex binIndex(rhoIdx, thetaIdx);
474 
475  rhoThetaAccumulatorBinMap[binIndex].addAccumulatorValue(accValue);
476 
477  if (rhoThetaAccumulatorBinMap[binIndex].getAccumulatorValues().size() > maxBinCount)
478  maxBinCount = rhoThetaAccumulatorBinMap[binIndex].getAccumulatorValues().size();
479  }
480  }
481 
482  // Accumulation done, if asked now display the hist
483  if (m_displayHist) {
484  std::ostringstream ostr;
485  ostr << "Hough Histogram " << histCount++;
486  m_Canvases.emplace_back(new TCanvas(ostr.str().c_str(), ostr.str().c_str(), 1000, 1000));
487 
488  std::ostringstream ostr2;
489  ostr2 << "Plane";
490 
491  m_Canvases.back()->GetFrame()->SetFillColor(46);
492  m_Canvases.back()->SetFillColor(19);
493  m_Canvases.back()->SetBorderMode(19);
494  m_Canvases.back()->cd(1);
495 
496  double zmin = 0.06;
497  double zmax = 0.94;
498  double xmin = 0.04;
499  double xmax = 0.95;
500  TPad* p = new TPad(ostr2.str().c_str(), ostr2.str().c_str(), zmin, xmin, zmax, xmax);
501  p->SetBit(kCanDelete); // Give away ownership.
502  p->Range(zmin, xmin, zmax, xmax);
503  p->SetFillStyle(4000); // Transparent.
504  p->Draw();
505  m_Pads.push_back(p);
506 
507  TH2D* houghHist = new TH2D("HoughHist",
508  "Hough Space",
509  2 * m_rhoBins,
510  -m_rhoBins + 0.5,
511  m_rhoBins + 0.5,
512  m_thetaBins,
513  0.,
514  m_thetaBins);
515 
516  for (const auto& rhoThetaMap : rhoThetaAccumulatorBinMap) {
517  houghHist->Fill(rhoThetaMap.first.first,
518  rhoThetaMap.first.second + 0.5,
519  rhoThetaMap.second.getAccumulatorValues().size());
520  }
521 
522  houghHist->SetBit(kCanDelete);
523  houghHist->Draw();
524  m_Canvases.back()->Update();
525  }
526 
527  // **********************************************************************
528  // Part II: Use DBScan (or a slight variation) to find clusters of bins
529  // **********************************************************************
530 
531  size_t thresholdLo = std::max(size_t(m_hiThresholdFrac * nAccepted3DHits), m_hiThresholdMin);
532  size_t thresholdHi = m_loThresholdFrac * maxBinCount;
533 
534  std::list<RhoThetaAccumulatorBinMap::iterator> binIndexList;
535 
536  for (RhoThetaAccumulatorBinMap::iterator mapItr = rhoThetaAccumulatorBinMap.begin();
537  mapItr != rhoThetaAccumulatorBinMap.end();
538  mapItr++)
539  binIndexList.push_back(mapItr);
540 
541  binIndexList.sort(SortBinIndexList());
542 
543  for (auto& mapItr : binIndexList) {
544  // If we have been here before we skip
545  //if (mapItr.second.isVisited()) continue;
546  if (mapItr->second.isInCluster()) continue;
547 
548  // Mark this bin as visited
549  // Actually, don't mark it since we are double thresholding and don't want it missed
550  //mapItr.second.setVisited();
551 
552  // Make sure over threshold
553  if (mapItr->second.getAccumulatorValues().size() < thresholdLo) {
554  mapItr->second.setNoise();
555  continue;
556  }
557 
558  // Set the low threshold to make sure we merge bins that might be either side of a boundary trajectory
559  thresholdHi = std::max(
560  size_t(m_loThresholdFrac * mapItr->second.getAccumulatorValues().size()), m_hiThresholdMin);
561 
562  // Recover our neighborhood
563  HoughCluster neighborhood;
564  BinIndex curBin(mapItr->first);
565 
566  HoughRegionQuery(curBin, rhoThetaAccumulatorBinMap, neighborhood, thresholdHi);
567 
568  houghClusters.push_back(HoughCluster());
569 
570  HoughCluster& houghCluster = houghClusters.back();
571 
573  curBin, neighborhood, houghCluster, rhoThetaAccumulatorBinMap, thresholdHi);
574  }
575 
576  // Sort the clusters using the SortHoughClusterList metric
577  if (!houghClusters.empty()) houghClusters.sort(SortHoughClusterList(rhoThetaAccumulatorBinMap));
578 
579  return;
580  }
581 
582  bool
584  SeedHitPairListPair& seedHitPair) const
585  {
586  if (seed3DHits.size() < m_minimum3DHits) return false;
587 
588  reco::PrincipalComponents seedFullPca;
589 
590  m_pcaAlg.PCAAnalysis_3D(seed3DHits, seedFullPca, true);
591 
592  if (!seedFullPca.getSvdOK()) return false;
593 
594  // Use the following to set the 3D doca and arclength for each hit
595  m_pcaAlg.PCAAnalysis_calc3DDocas(seed3DHits, seedFullPca);
596 
597  // Use this info to sort the hits along the principle axis
598  //seed3DHits.sort(SeedFinderAlgBase::Sort3DHitsByArcLen3D());
600 
601  // The idea here is to search for the first hit that lies "close" to the principle axis
602  // At that point we count out n hits to use as the seed
603  reco::HitPairListPtr seedHit3DList;
604  std::set<const reco::ClusterHit2D*> seedHitSet;
605  double aveDocaToAxis = seedFullPca.getAveHitDoca();
606  int gapCount(0);
607 
608  // Now loop through hits to search for a "continuous" block of at least m_numSeed2DHits
609  // We'll arrive at that number by collecting 2D hits in an stl set which will keep track of unique occurances
610  for (reco::HitPairListPtr::iterator peakBinItr = seed3DHits.begin();
611  peakBinItr != seed3DHits.end();
612  peakBinItr++) {
613  const reco::ClusterHit3D* hit3D = *peakBinItr;
614 
615  if (hit3D->getDocaToAxis() < m_numAveDocas * aveDocaToAxis) {
616  // Check if we need to reset because of gap count
617  if (gapCount > m_numSkippedHits) {
618  seedHit3DList.clear();
619  seedHitSet.clear();
620  }
621 
622  seedHit3DList.push_back(hit3D);
623 
624  for (const auto& hit : hit3D->getHits())
625  seedHitSet.insert(hit);
626 
627  gapCount = 0;
628  }
629  else
630  gapCount++;
631 
632  if (seedHitSet.size() > m_numSeed2DHits) break;
633  }
634 
635  // If not enough hits then we are done
636  if (seedHit3DList.size() < m_minimum3DHits) return false;
637 
639 
640  // Use only the "seed" 3D hits to get new PCA axes
641  m_pcaAlg.PCAAnalysis_3D(seedHit3DList, seedPca, true);
642 
643  if (!seedPca.getSvdOK()) return false;
644 
645  m_pcaAlg.PCAAnalysis_calc3DDocas(seedHit3DList, seedPca);
646  //seedHit3DList.sort(SeedFinderAlgBase::Sort3DHitsByAbsArcLen3D());
647  seedHit3DList.sort(SeedFinderAlgBase::Sort3DHitsByArcLen3D());
648 
649  // Now translate the seedCenter by the arc len to the first hit
650  double seedCenter[3] = {
651  seedPca.getAvePosition()[0], seedPca.getAvePosition()[1], seedPca.getAvePosition()[2]};
652  double seedDir[3] = {seedPca.getEigenVectors().row(2)[0],
653  seedPca.getEigenVectors().row(2)[1],
654  seedPca.getEigenVectors().row(2)[2]};
655 
656  double arcLen = seedHit3DList.front()->getArclenToPoca();
657  double seedStart[3] = {seedCenter[0] + arcLen * seedDir[0],
658  seedCenter[1] + arcLen * seedDir[1],
659  seedCenter[2] + arcLen * seedDir[2]};
660 
661  //seedStart[0] = seedHit3DList.front()->getX();
662  //seedStart[1] = seedHit3DList.front()->getY();
663  //seedStart[2] = seedHit3DList.front()->getZ();
664 
665  if (seedHitSet.size() >= 10) {
666  TVector3 newSeedPos;
667  TVector3 newSeedDir;
668  double chiDOF;
669 
670  LineFit2DHits(seedHitSet, seedStart[0], newSeedPos, newSeedDir, chiDOF);
671 
672  if (chiDOF > 0.) {
673  // check angles between new/old directions
674  double cosAng =
675  seedDir[0] * newSeedDir[0] + seedDir[1] * newSeedDir[1] + seedDir[2] * newSeedDir[2];
676 
677  if (cosAng < 0.) newSeedDir *= -1.;
678 
679  seedStart[0] = newSeedPos[0];
680  seedStart[1] = newSeedPos[1];
681  seedStart[2] = newSeedPos[2];
682  seedDir[0] = newSeedDir[0];
683  seedDir[1] = newSeedDir[1];
684  seedDir[2] = newSeedDir[2];
685  }
686  }
687 
688  // Keep track of this seed and the 3D hits that make it up
689  seedHitPair = SeedHitPairListPair(recob::Seed(seedStart, seedDir), seedHit3DList);
690 
691  // We are going to drop a few hits off the ends in the hope this facilitates finding more track like objects, provided there are enough hits
692  if (seed3DHits.size() > 100) {
693  // Need to reset the doca/arclen first
694  m_pcaAlg.PCAAnalysis_calc3DDocas(seed3DHits, seedFullPca);
695 
696  // Now resort the hits
698 
699  size_t numToKeep = seed3DHits.size() - 10;
700 
701  reco::HitPairListPtr::iterator endItr = seed3DHits.begin();
702 
703  std::advance(endItr, numToKeep);
704 
705  seed3DHits.erase(endItr, seed3DHits.end());
706  }
707 
708  return true;
709  }
710 
711  bool
713  reco::PrincipalComponents& inputPCA,
714  SeedHitPairListPairVec& seedHitPairVec) const
715  {
716  // This will be a busy routine... the basic tasks are:
717  // 1) loop through hits and project to the plane defined by the two largest eigen values, accumulate in Hough space
718  // 2) "Cluster" the Hough space to associate hits which are common to a line
719  // 3) Process these clusters (still to be defined exactly)
720 
721  // Create an interim data structure which will allow us to sort our seeds by "best"
722  // before we return them in the seedHitPairVec
723  typedef std::map<size_t, SeedHitPairListPairVec> SizeToSeedToHitMap;
724 
725  SizeToSeedToHitMap seedHitPairMap;
726  SeedHitPairListPair seedHitPair;
727 
728  // Make sure we are using the right pca
729  reco::HitPairListPtr hitPairListPtr = inputHitPairListPtr;
730 
731  int nLoops(0);
732 
733  // Make a local copy of the input PCA
734  reco::PrincipalComponents pca = inputPCA;
735 
736  // We loop over hits in our list until there are no more
737  while (!hitPairListPtr.empty()) {
738  // We also require that there be some spread in the data, otherwise not worth running?
739  double eigenVal0 = 3. * sqrt(pca.getEigenValues()[2]);
740  double eigenVal1 = 3. * sqrt(pca.getEigenValues()[1]);
741 
742  if (eigenVal0 > 5. && eigenVal1 > 0.001) {
743  // **********************************************************************
744  // Part I: Build Hough space and find Hough clusters
745  // **********************************************************************
746  RhoThetaAccumulatorBinMap rhoThetaAccumulatorBinMap;
747  HoughClusterList houghClusters;
748 
749  findHoughClusters(hitPairListPtr, pca, nLoops, rhoThetaAccumulatorBinMap, houghClusters);
750 
751  // If no clusters then done
752  if (houghClusters.empty()) break;
753 
754  // **********************************************************************
755  // Part II: Go through the clusters to find the peak bins
756  // **********************************************************************
757 
758  // We need to use a set so we can be sure to have unique hits
759  reco::HitPairListPtr clusterHitsList;
760  std::set<const reco::ClusterHit3D*> masterHitPtrList;
761  std::set<const reco::ClusterHit3D*> peakBinPtrList;
762 
763  size_t firstPeakCount(0);
764 
765  // Loop through the list of all clusters found above
766  for (auto& houghCluster : houghClusters) {
767  BinIndex peakBin = houghCluster.front();
768  size_t peakCount = 0;
769  size_t totalHits = 0;
770 
771  // Make a local (to this cluster) set of of hits
772  std::set<const reco::ClusterHit3D*> localHitPtrList;
773 
774  // Now loop through the bins that were attached to this cluster
775  for (auto& binIndex : houghCluster) {
776  // An even more local list so we can keep track of peak values
777  std::set<const reco::ClusterHit3D*> tempHitPtrList;
778 
779  // Recover the hits associated to this cluster
780  for (auto& hitItr : rhoThetaAccumulatorBinMap[binIndex].getAccumulatorValues()) {
781  reco::HitPairListPtr::const_iterator hit3DItr = hitItr.getHitIterator();
782 
783  tempHitPtrList.insert(*hit3DItr);
784  }
785 
786  // count hits before we remove any
787  totalHits += tempHitPtrList.size();
788 
789  // Trim out any hits already used by a bigger/better cluster
790  std::set<const reco::ClusterHit3D*> tempHit3DSet;
791 
792  std::set_difference(tempHitPtrList.begin(),
793  tempHitPtrList.end(),
794  masterHitPtrList.begin(),
795  masterHitPtrList.end(),
796  std::inserter(tempHit3DSet, tempHit3DSet.end()));
797 
798  tempHitPtrList = tempHit3DSet;
799 
800  size_t binCount = tempHitPtrList.size();
801 
802  if (peakCount < binCount) {
803  peakCount = binCount;
804  peakBin = binIndex;
805  peakBinPtrList = tempHitPtrList;
806  }
807 
808  // Add this to our local list
809  localHitPtrList.insert(tempHitPtrList.begin(), tempHitPtrList.end());
810  }
811 
812  if (localHitPtrList.size() < m_minimum3DHits) continue;
813 
814  if (!firstPeakCount) firstPeakCount = peakCount;
815 
816  // If the peak counts are significantly less than the first cluster's peak then skip
817  if (peakCount < firstPeakCount / 10) continue;
818 
819  // **********************************************************************
820  // Part III: Make a Seed from the peak bin hits
821  // **********************************************************************
822 
823  reco::HitPairListPtr allPeakBinHits;
824 
825  for (const auto& hit3D : localHitPtrList)
826  allPeakBinHits.push_back(hit3D);
827 
828  reco::HitPairListPtr peakBinHits;
829 
830  // Find longest "continuous" set of hits and use these for the seed
831  findHitGaps(allPeakBinHits, peakBinHits);
832 
833  if (peakBinHits.size() < m_minimum3DHits) continue;
834 
835  // We now build the actual seed.
836  if (buildSeed(peakBinHits, seedHitPair)) {
837  // Keep track of this in our map (which will do ordering for us)
838  seedHitPairMap[peakBinHits.size()].push_back(seedHitPair);
839 
840  // For visual testing in event display, mark all the hits in the first seed so we can see them
841  if (seedHitPairMap.size() == 1) {
842  for (const auto& hit3D : peakBinHits)
843  hit3D->setStatusBit(0x40000000);
844  }
845 
846  // for(const auto& hit3D : seedHitPair.second) hit3D->setStatusBit(0x40000000);
847  }
848 
849  // Our peakBinHits collection will most likely be a subset of the localHitPtrList collection
850  // We want to remove only the "pure" hits which are those in the peakBinHits collection
851  // So, sort them and then add to our master list
852  peakBinHits.sort();
853 
854  masterHitPtrList.insert(peakBinHits.begin(), peakBinHits.end());
855 
856  if (hitPairListPtr.size() - masterHitPtrList.size() < m_minimum3DHits) break;
857  } // end loop over hough clusters
858 
859  // If the masterHitPtrList is empty then nothing happened and we're done
860  if (masterHitPtrList.empty()) break;
861 
862  // **********************************************************************
863  // Part IV: Remove remaining peak bin hits from HitPairPtrList
864  // **********************************************************************
865 
866  hitPairListPtr.sort();
867 
868  reco::HitPairListPtr::iterator newListEnd = std::set_difference(hitPairListPtr.begin(),
869  hitPairListPtr.end(),
870  masterHitPtrList.begin(),
871  masterHitPtrList.end(),
872  hitPairListPtr.begin());
873 
874  hitPairListPtr.erase(newListEnd, hitPairListPtr.end());
875 
876  if (hitPairListPtr.size() < m_minimum3DHits) break;
877 
878  if (nLoops++ > m_maxLoopsPerCluster) break;
879 
880  // ********************************************************
881  }
882  else
883  break; // eigen values not in range
884 
885  // At this point run the PCA on the remaining hits
886  m_pcaAlg.PCAAnalysis_3D(hitPairListPtr, pca, true);
887 
888  if (!pca.getSvdOK()) break;
889  }
890 
891  // The final task before returning is to transfer the stored seeds into the output seed vector
892  // What we want to do is make sure the first seeds are the "best" seeds which is defined as the
893  // seeds which were associated to the most hits by the Hough Transform. Our seed map will have
894  // the reverse of this ordering so we simply iterate through it "backwards"
895  for (SizeToSeedToHitMap::reverse_iterator seedMapItr = seedHitPairMap.rbegin();
896  seedMapItr != seedHitPairMap.rend();
897  seedMapItr++) {
898  for (const auto& seedHitPair : seedMapItr->second) {
899  seedHitPairVec.emplace_back(seedHitPair);
900  }
901  }
902 
903  return true;
904  }
905 
906  bool
908  reco::PrincipalComponents& inputPCA,
909  reco::HitPairListPtrList& hitPairListPtrList) const
910  {
911  // The goal of this routine is run the Hough Transform on the input set of hits
912  // and then to return a list of lists of hits which are associated to a given line
913 
914  // Make sure we are using the right pca
915  reco::HitPairListPtr hitPairListPtr = inputHitPairListPtr;
916 
917  int nLoops(0);
918 
919  // Make a local copy of the input PCA
920  reco::PrincipalComponents pca = inputPCA;
921 
922  // We also require that there be some spread in the data, otherwise not worth running?
923  double eigenVal0 = 3. * sqrt(pca.getEigenValues()[2]);
924  double eigenVal1 = 3. * sqrt(pca.getEigenValues()[1]);
925 
926  if (eigenVal0 > 5. && eigenVal1 > 0.001) {
927  // **********************************************************************
928  // Part I: Build Hough space and find Hough clusters
929  // **********************************************************************
930  RhoThetaAccumulatorBinMap rhoThetaAccumulatorBinMap;
931  HoughClusterList houghClusters;
932 
933  findHoughClusters(hitPairListPtr, pca, nLoops, rhoThetaAccumulatorBinMap, houghClusters);
934 
935  // **********************************************************************
936  // Part II: Go through the clusters to find the peak bins
937  // **********************************************************************
938 
939  // We need to use a set so we can be sure to have unique hits
940  reco::HitPairListPtr clusterHitsList;
941  std::set<const reco::ClusterHit3D*> masterHitPtrList;
942  std::set<const reco::ClusterHit3D*> peakBinPtrList;
943 
944  size_t firstPeakCount(0);
945 
946  // Loop through the list of all clusters found above
947  for (auto& houghCluster : houghClusters) {
948  BinIndex peakBin = houghCluster.front();
949  size_t peakCount = 0;
950  size_t totalHits = 0;
951 
952  // Make a local (to this cluster) set of of hits
953  std::set<const reco::ClusterHit3D*> localHitPtrList;
954 
955  // Now loop through the bins that were attached to this cluster
956  for (auto& binIndex : houghCluster) {
957  // An even more local list so we can keep track of peak values
958  std::set<const reco::ClusterHit3D*> tempHitPtrList;
959 
960  // Recover the hits associated to this cluster
961  for (auto& hitItr : rhoThetaAccumulatorBinMap[binIndex].getAccumulatorValues()) {
962  reco::HitPairListPtr::const_iterator hit3DItr = hitItr.getHitIterator();
963 
964  tempHitPtrList.insert(*hit3DItr);
965  }
966 
967  // count hits before we remove any
968  totalHits += tempHitPtrList.size();
969 
970  // Trim out any hits already used by a bigger/better cluster
971  std::set<const reco::ClusterHit3D*> tempHit3DSet;
972 
973  std::set_difference(tempHitPtrList.begin(),
974  tempHitPtrList.end(),
975  masterHitPtrList.begin(),
976  masterHitPtrList.end(),
977  std::inserter(tempHit3DSet, tempHit3DSet.end()));
978 
979  tempHitPtrList = tempHit3DSet;
980 
981  size_t binCount = tempHitPtrList.size();
982 
983  if (peakCount < binCount) {
984  peakCount = binCount;
985  peakBin = binIndex;
986  peakBinPtrList = tempHitPtrList;
987  }
988 
989  // Add this to our local list
990  localHitPtrList.insert(tempHitPtrList.begin(), tempHitPtrList.end());
991  }
992 
993  if (localHitPtrList.size() < m_minimum3DHits) continue;
994 
995  if (!firstPeakCount) firstPeakCount = peakCount;
996 
997  // If the peak counts are significantly less than the first cluster's peak then skip
998  if (peakCount < firstPeakCount / 10) continue;
999 
1000  // **********************************************************************
1001  // Part III: Make a list of hits from the total number associated
1002  // **********************************************************************
1003 
1004  hitPairListPtrList.push_back(reco::HitPairListPtr());
1005 
1006  hitPairListPtrList.back().resize(localHitPtrList.size());
1007  std::copy(
1008  localHitPtrList.begin(), localHitPtrList.end(), hitPairListPtrList.back().begin());
1009 
1010  // We want to remove the hits which have been used from further contention
1011  masterHitPtrList.insert(localHitPtrList.begin(), localHitPtrList.end());
1012 
1013  if (hitPairListPtr.size() - masterHitPtrList.size() < m_minimum3DHits) break;
1014  } // end loop over hough clusters
1015  }
1016 
1017  return true;
1018  }
1019 
1020  //------------------------------------------------------------------------------
1021  void
1022  HoughSeedFinderAlg::LineFit2DHits(std::set<const reco::ClusterHit2D*>& hit2DSet,
1023  double XOrigin,
1024  TVector3& Pos,
1025  TVector3& Dir,
1026  double& ChiDOF) const
1027  {
1028  // The following is lifted from Bruce Baller to try to get better
1029  // initial parameters for a candidate Seed. It is slightly reworked
1030  // which is why it is included here instead of used as is.
1031  //
1032  // Linear fit using X as the independent variable. Hits to be fitted
1033  // are passed in the hits vector in a pair form (X, WireID). The
1034  // fitted track position at XOrigin is returned in the Pos vector.
1035  // The direction cosines are returned in the Dir vector.
1036  //
1037  // SVD fit adapted from $ROOTSYS/tutorials/matrix/solveLinear.C
1038  // Fit equation is w = A(X)v, where w is a vector of hit wires, A is
1039  // a matrix to calculate a track projected to a point at X, and v is
1040  // a vector (Yo, Zo, dY/dX, dZ/dX).
1041  //
1042  // Note: The covariance matrix should also be returned
1043  // B. Baller August 2014
1044 
1045  // assume failure
1046  ChiDOF = -1;
1047 
1048  if (hit2DSet.size() < 4) return;
1049 
1050  const unsigned int nvars = 4;
1051  unsigned int npts = hit2DSet.size();
1052 
1053  TMatrixD A(npts, nvars);
1054  // vector holding the Wire number
1055  TVectorD w(npts);
1056  unsigned short ninpl[3] = {0};
1057  unsigned short nok = 0;
1058  unsigned short iht(0), cstat, tpc, ipl;
1059  double x, cw, sw, off;
1060 
1061  // Loop over unique 2D hits from the input list of 3D hits
1062  for (const auto& hit : hit2DSet) {
1063  geo::WireID wireID = hit->WireID();
1064 
1065  cstat = wireID.Cryostat;
1066  tpc = wireID.TPC;
1067  ipl = wireID.Plane;
1068 
1069  // get the wire plane offset
1070  off = m_geometry->WireCoordinate(0, 0, ipl, tpc, cstat);
1071 
1072  // get the "cosine-like" component
1073  cw = m_geometry->WireCoordinate(1, 0, ipl, tpc, cstat) - off;
1074 
1075  // the "sine-like" component
1076  sw = m_geometry->WireCoordinate(0, 1, ipl, tpc, cstat) - off;
1077 
1078  x = hit->getXPosition() - XOrigin;
1079 
1080  A[iht][0] = cw;
1081  A[iht][1] = sw;
1082  A[iht][2] = cw * x;
1083  A[iht][3] = sw * x;
1084  w[iht] = wireID.Wire - off;
1085 
1086  ++ninpl[ipl];
1087 
1088  // need at least two points in a plane
1089  if (ninpl[ipl] == 2) ++nok;
1090 
1091  iht++;
1092  }
1093 
1094  // need at least 2 planes with at least two points
1095  if (nok < 2) return;
1096 
1097  TDecompSVD svd(A);
1098  bool ok;
1099  TVectorD tVec = svd.Solve(w, ok);
1100 
1101  ChiDOF = 0;
1102 
1103  // not enough points to calculate Chisq
1104  if (npts <= 4) return;
1105 
1106  double ypr, zpr, diff;
1107 
1108  for (const auto& hit : hit2DSet) {
1109  geo::WireID wireID = hit->WireID();
1110 
1111  cstat = wireID.Cryostat;
1112  tpc = wireID.TPC;
1113  ipl = wireID.Plane;
1114  off = m_geometry->WireCoordinate(0, 0, ipl, tpc, cstat);
1115  cw = m_geometry->WireCoordinate(1, 0, ipl, tpc, cstat) - off;
1116  sw = m_geometry->WireCoordinate(0, 1, ipl, tpc, cstat) - off;
1117  x = hit->getXPosition() - XOrigin;
1118  ypr = tVec[0] + tVec[2] * x;
1119  zpr = tVec[1] + tVec[3] * x;
1120  diff = ypr * cw + zpr * sw - (wireID.Wire - off);
1121  ChiDOF += diff * diff;
1122  }
1123 
1124  float werr2 = m_geometry->WirePitch() * m_geometry->WirePitch();
1125  ChiDOF /= werr2;
1126  ChiDOF /= (float)(npts - 4);
1127 
1128  double norm = sqrt(1 + tVec[2] * tVec[2] + tVec[3] * tVec[3]);
1129  Dir[0] = 1 / norm;
1130  Dir[1] = tVec[2] / norm;
1131  Dir[2] = tVec[3] / norm;
1132 
1133  Pos[0] = XOrigin;
1134  Pos[1] = tVec[0];
1135  Pos[2] = tVec[1];
1136 
1137  } // TrkLineFit()
1138 
1139 } // namespace lar_cluster3d
geo::Length_t WireCoordinate(double YPos, double ZPos, geo::PlaneID const &planeid) const
Returns the index of the nearest wire to the specified position.
void LineFit2DHits(std::set< const reco::ClusterHit2D * > &hitList, double XOrigin, TVector3 &Pos, TVector3 &Dir, double &ChiDOF) const
Define a comparator which will sort hits by arc length along a PCA axis.
void PCAAnalysis_calc3DDocas(const reco::HitPairListPtr &hitPairVector, const reco::PrincipalComponents &pca) const
void findHitGaps(reco::HitPairListPtr &inputHitList, reco::HitPairListPtr &outputList) const
Using Principal Components Axis, look for gaps in a list of 3D hits.
bool getSvdOK() const
Definition: Cluster3D.h:243
void PCAAnalysis_3D(const reco::HitPairListPtr &hitPairList, reco::PrincipalComponents &pca, bool skeletonOnly=false) const
std::vector< TVirtualPad * > m_Pads
View pads in current canvas.
process_name opflash particleana ie x
const Eigen::Vector3f getPosition() const
Definition: Cluster3D.h:157
walls no right
Definition: selectors.fcl:105
pdgs p
Definition: selectors.fcl:22
std::vector< AccumulatorValues > AccumulatorValuesVec
CryostatID_t Cryostat
Index of cryostat.
Definition: geo_types.h:212
std::size_t size(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:561
HoughSeedFinderAlg(fhicl::ParameterSet const &pset)
Constructor.
WireID_t Wire
Index of the wire within its plane.
Definition: geo_types.h:580
std::list< HitPairListPtr > HitPairListPtrList
Definition: Cluster3D.h:336
process_name hit
Definition: cheaterreco.fcl:51
bool operator()(const reco::ClusterHit3D *left, const reco::ClusterHit3D *right) const
void findHoughClusters(const reco::HitPairListPtr &inputHits, reco::PrincipalComponents &pca, int &nLoops, RhoThetaAccumulatorBinMap &rhoThetaMap, HoughClusterList &clusterList) const
unsigned int getStatusBits() const
Definition: Cluster3D.h:156
geo::Length_t WirePitch(geo::PlaneID const &planeid) const
Returns the distance between two consecutive wires.
float getDocaToAxis() const
Definition: Cluster3D.h:168
process_name pandoraGausCryo1 vertexChargeCryo1 vertexStubCryo1 xmin
Define a comparator which will sort hits by the absolute value of arc length so hits are ordered clos...
void expandHoughCluster(BinIndex &curBin, HoughCluster &neighborPts, HoughCluster &houghCluster, RhoThetaAccumulatorBinMap &rhoThetaAccumulatorBinMap, size_t threshold) const
HoughSeedFinderAlg::RhoThetaAccumulatorBinMap & m_accMap
const EigenValues & getEigenValues() const
Definition: Cluster3D.h:245
This is an algorithm for finding recob::Seed objects in 3D clusters.
void HoughRegionQuery(BinIndex &curBin, RhoThetaAccumulatorBinMap &rhoThetaAccumulatorBinMap, HoughCluster &neighborPts, size_t threshold) const
const Eigen::Vector3f & getAvePosition() const
Definition: Cluster3D.h:247
const AccumulatorValuesVec & getAccumulatorValues() const
std::list< HoughCluster > HoughClusterList
std::list< const reco::ClusterHit3D * > HitPairListPtr
Definition: Cluster3D.h:334
walls no left
Definition: selectors.fcl:105
bool operator()(const HoughSeedFinderAlg::HoughCluster &left, const HoughSeedFinderAlg::HoughCluster &right)
bool operator()(const reco::ClusterHit3D *left, const reco::ClusterHit3D *right)
PlaneID_t Plane
Index of the plane within its TPC.
Definition: geo_types.h:493
bool buildSeed(reco::HitPairListPtr &seed3DHits, SeedHitPairListPair &seedHitPair) const
Given a list of candidate &quot;seed&quot; 3D hits, build the seed and get associated unique 2D hits...
bool operator()(const HoughSeedFinderAlg::RhoThetaAccumulatorBinMap::iterator &left, const HoughSeedFinderAlg::RhoThetaAccumulatorBinMap::iterator &right)
This is used to sort bins in Hough Space.
std::map< BinIndex, AccumulatorBin > RhoThetaAccumulatorBinMap
auto norm(Vector const &v)
Return norm of the specified vector.
reco::HitPairListPtr::const_iterator m_hit3DIterator
This will be used to take us back to our 3D hit.
bool findTrackHits(reco::HitPairListPtr &hitPairListPtr, reco::PrincipalComponents &inputPCA, reco::HitPairListPtrList &hitPairListPtrList) const
Given the list of hits this will return the sets of hits which belong on the same line...
const Eigen::Vector3f & getPosition() const
bool operator()(const std::pair< size_t, size_t > &left, const std::pair< size_t, size_t > &right)
const float getAveHitDoca() const
Definition: Cluster3D.h:248
std::vector< SeedHitPairListPair > SeedHitPairListPairVec
Eigen::Vector3f m_position
We really only need the x,y coordinates here but keep all three for now.
std::pair< recob::Seed, reco::HitPairListPtr > SeedHitPairListPair
T copy(T const &v)
SortHoughClusterList(HoughSeedFinderAlg::RhoThetaAccumulatorBinMap &accMap)
This is used to sort &quot;Hough Clusters&quot; by the maximum entries in a bin.
temporary value
TPCID_t TPC
Index of the TPC within its cryostat.
Definition: geo_types.h:406
AccumulatorValues()
A utility class to contain the values of a given &quot;bin&quot; in Hough Space.
AccumulatorBin()
A utility class used to accumulate the above values.
float A
Definition: dedx.py:137
std::vector< std::unique_ptr< TCanvas > > m_Canvases
Graphical trace canvases.
const ClusterHit2DVec & getHits() const
Definition: Cluster3D.h:170
AccumulatorValues(const Eigen::Vector3f &position, const reco::HitPairListPtr::const_iterator &itr)
bool Hit3DCompare(const reco::ClusterHit3D *left, const reco::ClusterHit3D *right)
reco::HitPairListPtr::const_iterator getHitIterator() const
art framework interface to geometry description
const EigenVectors & getEigenVectors() const
Definition: Cluster3D.h:246
bool findTrackSeeds(reco::HitPairListPtr &hitPairListPtr, reco::PrincipalComponents &inputPCA, SeedHitPairListPairVec &seedHitPairVec) const override
Given the list of hits this will search for candidate Seed objects and return them.