LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
MinSpanTreeAlg_tool.cc
Go to the documentation of this file.
1 
8 // Framework Includes
12 #include "cetlib/cpu_timer.h"
13 #include "fhiclcpp/ParameterSet.h"
15 
16 // LArSoft includes
27 
28 // std includes
29 #include <iostream>
30 #include <memory>
31 #include <unordered_map>
32 
33 // Eigen includes
34 #include <Eigen/Core>
35 
36 #include "TVector3.h"
37 
38 //------------------------------------------------------------------------------------------------------------------------------------------
39 // implementation follows
40 
41 namespace lar_cluster3d {
42 
43  class MinSpanTreeAlg : virtual public IClusterAlg {
44  public:
50  explicit MinSpanTreeAlg(const fhicl::ParameterSet&);
51 
56 
57  void configure(fhicl::ParameterSet const& pset) override;
58 
66  void Cluster3DHits(reco::HitPairList& hitPairList,
67  reco::ClusterParametersList& clusterParametersList) const override;
68 
69  void Cluster3DHits(reco::HitPairListPtr& /* hitPairList */,
70  reco::ClusterParametersList& /* clusterParametersList */) const override
71  {}
72 
76  float getTimeToExecute(TimeValues index) const override { return m_timeVector.at(index); }
77 
78  private:
85 
90 
95 
100  const reco::Hit3DToEdgeMap&,
101  float&) const;
102 
107 
111  void AStar(const reco::ClusterHit3D*,
112  const reco::ClusterHit3D*,
113  reco::ClusterParameters&) const;
114 
115  using BestNodeTuple = std::tuple<const reco::ClusterHit3D*, float, float>;
116  using BestNodeMap = std::unordered_map<const reco::ClusterHit3D*, BestNodeTuple>;
117 
119  BestNodeMap&,
121  reco::EdgeList&) const;
122 
123  float DistanceBetweenNodes(const reco::ClusterHit3D*, const reco::ClusterHit3D*) const;
124 
128  void LeastCostPath(const reco::EdgeTuple&,
129  const reco::ClusterHit3D*,
131  float&) const;
132 
133  void CheckHitSorting(reco::ClusterParameters& clusterParams) const;
134 
138  using ChannelStatusVec = std::vector<size_t>;
139  using ChannelStatusByPlaneVec = std::vector<ChannelStatusVec>;
140 
145  mutable std::vector<float> m_timeVector;
146  std::vector<std::vector<float>> m_wireDir;
147 
148  geo::Geometry const* m_geometry; //< pointer to the Geometry service
149 
150  PrincipalComponentsAlg m_pcaAlg; // For running Principal Components Analysis
151  kdTree m_kdTree; // For the kdTree
152 
153  std::unique_ptr<lar_cluster3d::IClusterParametersBuilder>
155  };
156 
158  : m_pcaAlg(pset.get<fhicl::ParameterSet>("PrincipalComponentsAlg"))
159  , m_kdTree(pset.get<fhicl::ParameterSet>("kdTree"))
160  {
161  this->configure(pset);
162  }
163 
164  //------------------------------------------------------------------------------------------------------------------------------------------
165 
167 
168  //------------------------------------------------------------------------------------------------------------------------------------------
169 
171  {
172  m_enableMonitoring = pset.get<bool>("EnableMonitoring", true);
173 
175 
176  m_geometry = &*geometry;
177 
178  m_timeVector.resize(NUMTIMEVALUES, 0.);
179 
180  // Determine the unit directon and normal vectors to the wires
181  m_wireDir.resize(3);
182 
183  raw::ChannelID_t uChannel(0);
184  std::vector<geo::WireID> uWireID = m_geometry->ChannelToWire(uChannel);
185  const geo::WireGeo* uWireGeo = m_geometry->WirePtr(uWireID[0]);
186 
187  auto const uWireDir = uWireGeo->Direction();
188  m_wireDir[0] = {(float)uWireDir.X(), (float)-uWireDir.Z(), (float)uWireDir.Y()};
189 
190  raw::ChannelID_t vChannel(2400);
191  std::vector<geo::WireID> vWireID = m_geometry->ChannelToWire(vChannel);
192  const geo::WireGeo* vWireGeo = m_geometry->WirePtr(vWireID[0]);
193 
194  auto const vWireDir = vWireGeo->Direction();
195  m_wireDir[1] = {(float)vWireDir.X(), (float)-vWireDir.Z(), (float)vWireDir.Y()};
196  m_wireDir[2] = {0., 0., 1.};
197 
198  m_clusterBuilder = art::make_tool<lar_cluster3d::IClusterParametersBuilder>(
199  pset.get<fhicl::ParameterSet>("ClusterParamsBuilder"));
200  }
201 
203  reco::ClusterParametersList& clusterParametersList) const
204  {
210  // Zero the time vector
211  if (m_enableMonitoring) std::fill(m_timeVector.begin(), m_timeVector.end(), 0.);
212 
213  // DBScan is driven of its "epsilon neighborhood". Computing adjacency within DBScan can be time
214  // consuming so the idea is the prebuild the adjaceny map and then run DBScan.
215  // The following call does this work
216  kdTree::KdTreeNodeList kdTreeNodeContainer;
217  kdTree::KdTreeNode topNode = m_kdTree.BuildKdTree(hitPairList, kdTreeNodeContainer);
218 
220 
221  // Run DBScan to get candidate clusters
222  RunPrimsAlgorithm(hitPairList, topNode, clusterParametersList);
223 
224  // Initial clustering is done, now trim the list and get output parameters
225  cet::cpu_timer theClockBuildClusters;
226 
227  // Start clocks if requested
228  if (m_enableMonitoring) theClockBuildClusters.start();
229 
230  m_clusterBuilder->BuildClusterInfo(clusterParametersList);
231 
232  if (m_enableMonitoring) {
233  theClockBuildClusters.stop();
234 
235  m_timeVector[BUILDCLUSTERINFO] = theClockBuildClusters.accumulated_real_time();
236  }
237 
238  // Test run the path finding algorithm
239  for (auto& clusterParams : clusterParametersList)
240  FindBestPathInCluster(clusterParams, topNode);
241 
242  mf::LogDebug("MinSpanTreeAlg") << ">>>>> Cluster3DHits done, found "
243  << clusterParametersList.size() << " clusters" << std::endl;
244 
245  return;
246  }
247 
248  //------------------------------------------------------------------------------------------------------------------------------------------
250  kdTree::KdTreeNode& topNode,
251  reco::ClusterParametersList& clusterParametersList) const
252  {
253  // If no hits then no work
254  if (hitPairList.empty()) return;
255 
256  // Now proceed with building the clusters
257  cet::cpu_timer theClockDBScan;
258 
259  // Start clocks if requested
260  if (m_enableMonitoring) theClockDBScan.start();
261 
262  // Initialization
263  size_t clusterIdx(0);
264 
265  // This will contain our list of edges
266  reco::EdgeList curEdgeList;
267 
268  // Get the first point
269  reco::HitPairList::iterator freeHitItr = hitPairList.begin();
270  const reco::ClusterHit3D* lastAddedHit = &(*freeHitItr++);
271 
273 
274  // Make a cluster...
275  clusterParametersList.push_back(reco::ClusterParameters());
276 
277  // Get an iterator to the first cluster
278  reco::ClusterParametersList::iterator curClusterItr = --clusterParametersList.end();
279 
280  // We use pointers here because the objects they point to will change in the loop below
281  reco::Hit3DToEdgeMap* curEdgeMap = &(*curClusterItr).getHit3DToEdgeMap();
282  reco::HitPairListPtr* curCluster = &(*curClusterItr).getHitPairListPtr();
283 
284  // Loop until all hits have been associated to a cluster
285  while (1) {
286  // and the 3D hit status bits
288 
289  // Purge the current list to get rid of edges which point to hits already in the cluster
290  for (reco::EdgeList::iterator curEdgeItr = curEdgeList.begin();
291  curEdgeItr != curEdgeList.end();) {
292  if (std::get<1>(*curEdgeItr)->getStatusBits() & reco::ClusterHit3D::CLUSTERATTACHED)
293  curEdgeItr = curEdgeList.erase(curEdgeItr);
294  else
295  curEdgeItr++;
296  }
297 
298  // Add the lastUsedHit to the current cluster
299  curCluster->push_back(lastAddedHit);
300 
301  // Set up to find the list of nearest neighbors to the last used hit...
302  kdTree::CandPairList CandPairList;
303  float bestDistance(1.5); //std::numeric_limits<float>::max());
304 
305  // And find them... result will be an unordered list of neigbors
306  m_kdTree.FindNearestNeighbors(lastAddedHit, topNode, CandPairList, bestDistance);
307 
308  // Copy edges to the current list (but only for hits not already in a cluster)
309  // for(auto& pair : CandPairList)
310  // if (!(pair.second->getStatusBits() & reco::ClusterHit3D::CLUSTERATTACHED)) curEdgeList.push_back(reco::EdgeTuple(lastAddedHit,pair.second,pair.first));
311  for (auto& pair : CandPairList) {
312  if (!(pair.second->getStatusBits() & reco::ClusterHit3D::CLUSTERATTACHED)) {
313  double edgeWeight = lastAddedHit->getHitChiSquare() * pair.second->getHitChiSquare();
314 
315  curEdgeList.push_back(reco::EdgeTuple(lastAddedHit, pair.second, edgeWeight));
316  }
317  }
318 
319  // If the edge list is empty then we have a complete cluster
320  if (curEdgeList.empty()) {
321  std::cout << "-----------------------------------------------------------------------------"
322  "------------"
323  << std::endl;
324  std::cout << "**> Cluster idx: " << clusterIdx++ << " has " << curCluster->size() << " hits"
325  << std::endl;
326 
327  // Look for the next "free" hit
328  freeHitItr = std::find_if(freeHitItr, hitPairList.end(), [](const auto& hit) {
329  return !(hit.getStatusBits() & reco::ClusterHit3D::CLUSTERATTACHED);
330  });
331 
332  // If at end of input list we are done with all hits
333  if (freeHitItr == hitPairList.end()) break;
334 
335  std::cout << "##################################################################>"
336  "Processing another cluster"
337  << std::endl;
338 
339  // Otherwise, get a new cluster and set up
340  clusterParametersList.push_back(reco::ClusterParameters());
341 
342  curClusterItr = --clusterParametersList.end();
343 
344  curEdgeMap = &(*curClusterItr).getHit3DToEdgeMap();
345  curCluster = &(*curClusterItr).getHitPairListPtr();
346  lastAddedHit = &(*freeHitItr++);
347  }
348  // Otherwise we are still processing the current cluster
349  else {
350  // Sort the list of edges by distance
351  curEdgeList.sort([](const auto& left, const auto& right) {
352  return std::get<2>(left) < std::get<2>(right);
353  });
354 
355  // Populate the map with the edges...
356  reco::EdgeTuple& curEdge = curEdgeList.front();
357 
358  (*curEdgeMap)[std::get<0>(curEdge)].push_back(curEdge);
359  (*curEdgeMap)[std::get<1>(curEdge)].push_back(
360  reco::EdgeTuple(std::get<1>(curEdge), std::get<0>(curEdge), std::get<2>(curEdge)));
361 
362  // Update the last hit to be added to the collection
363  lastAddedHit = std::get<1>(curEdge);
364  }
365  }
366 
367  if (m_enableMonitoring) {
368  theClockDBScan.stop();
369 
370  m_timeVector[RUNDBSCAN] = theClockDBScan.accumulated_real_time();
371  }
372 
373  return;
374  }
375 
377  {
378  reco::HitPairListPtr longestCluster;
379  float bestQuality(0.);
380  float aveNumEdges(0.);
381  size_t maxNumEdges(0);
382  size_t nIsolatedHits(0);
383 
384  // Now proceed with building the clusters
385  cet::cpu_timer theClockPathFinding;
386 
387  // Start clocks if requested
388  if (m_enableMonitoring) theClockPathFinding.start();
389 
390  reco::HitPairListPtr& hitPairList = curCluster.getHitPairListPtr();
391  reco::Hit3DToEdgeMap& curEdgeMap = curCluster.getHit3DToEdgeMap();
392  reco::EdgeList& bestEdgeList = curCluster.getBestEdgeList();
393 
394  // Do some spelunking...
395  for (const auto& hit : hitPairList) {
396  if (!curEdgeMap[hit].empty() && curEdgeMap[hit].size() == 1) {
397  float quality(0.);
398 
399  reco::HitPairListPtr tempList =
400  DepthFirstSearch(curEdgeMap[hit].front(), curEdgeMap, quality);
401 
402  tempList.push_front(std::get<0>(curEdgeMap[hit].front()));
403 
404  if (quality > bestQuality) {
405  longestCluster = tempList;
406  bestQuality = quality;
407  }
408 
409  nIsolatedHits++;
410  }
411 
412  aveNumEdges += float(curEdgeMap[hit].size());
413  maxNumEdges = std::max(maxNumEdges, curEdgeMap[hit].size());
414  }
415 
416  aveNumEdges /= float(hitPairList.size());
417  std::cout << "----> # isolated hits: " << nIsolatedHits
418  << ", longest branch: " << longestCluster.size()
419  << ", cluster size: " << hitPairList.size() << ", ave # edges: " << aveNumEdges
420  << ", max: " << maxNumEdges << std::endl;
421 
422  if (!longestCluster.empty()) {
423  hitPairList = longestCluster;
424  for (const auto& hit : hitPairList) {
425  for (const auto& edge : curEdgeMap[hit])
426  bestEdgeList.emplace_back(edge);
427  }
428 
429  std::cout << " ====> new cluster size: " << hitPairList.size() << std::endl;
430  }
431 
432  if (m_enableMonitoring) {
433  theClockPathFinding.stop();
434 
435  m_timeVector[PATHFINDING] += theClockPathFinding.accumulated_real_time();
436  }
437 
438  return;
439  }
440 
442  kdTree::KdTreeNode& /* topNode */) const
443  {
444  // Set up for timing the function
445  cet::cpu_timer theClockPathFinding;
446 
447  // Start clocks if requested
448  if (m_enableMonitoring) theClockPathFinding.start();
449 
450  // Trial A* here
451  if (clusterParams.getHitPairListPtr().size() > 2) {
452  // Get references to what we need....
453  reco::HitPairListPtr& curCluster = clusterParams.getHitPairListPtr();
454  reco::Hit3DToEdgeMap& curEdgeMap = clusterParams.getHit3DToEdgeMap();
455 
456  // Do a quick PCA to determine our parameter "alpha"
458  m_pcaAlg.PCAAnalysis_3D(curCluster, pca);
459 
460  // The chances of a failure are remote, still we should check
461  if (pca.getSvdOK()) {
462  float pcaLen = 3.0 * sqrt(pca.getEigenValues()[2]);
463  float pcaWidth = 3.0 * sqrt(pca.getEigenValues()[1]);
464  float pcaHeight = 3.0 * sqrt(pca.getEigenValues()[0]);
465  const Eigen::Vector3f& pcaCenter = pca.getAvePosition();
466  float alpha = std::min(float(1.), std::max(float(0.001), pcaWidth / pcaLen));
467 
468  // Create a temporary container for the isolated points
469  reco::ProjectedPointList isolatedPointList;
470 
471  // Go through and find the isolated points, for those get the projection to the plane of maximum spread
472  for (const auto& hit3D : curCluster) {
473  // the definition of an isolated hit is that it only has one associated edge
474  if (!curEdgeMap[hit3D].empty() && curEdgeMap[hit3D].size() == 1) {
475  Eigen::Vector3f pcaToHitVec(hit3D->getPosition()[0] - pcaCenter(0),
476  hit3D->getPosition()[1] - pcaCenter(1),
477  hit3D->getPosition()[2] - pcaCenter(2));
478  Eigen::Vector3f pcaToHit = pca.getEigenVectors() * pcaToHitVec;
479 
480  // This sets x,y where x is the longer spread, y the shorter
481  isolatedPointList.emplace_back(pcaToHit(2), pcaToHit(1), hit3D);
482  }
483  }
484 
485  std::cout << "************* Finding best path with A* in cluster *****************"
486  << std::endl;
487  std::cout << "**> There are " << curCluster.size() << " hits, " << isolatedPointList.size()
488  << " isolated hits, the alpha parameter is " << alpha << std::endl;
489  std::cout << "**> PCA len: " << pcaLen << ", wid: " << pcaWidth << ", height: " << pcaHeight
490  << ", ratio: " << pcaHeight / pcaWidth << std::endl;
491 
492  // If no isolated points then nothing to do...
493  if (isolatedPointList.size() > 1) {
494  // Sort the point vec by increasing x, if same then by increasing y.
495  isolatedPointList.sort([](const auto& left, const auto& right) {
496  return (std::abs(std::get<0>(left) - std::get<0>(right)) >
497  std::numeric_limits<float>::epsilon()) ?
498  std::get<0>(left) < std::get<0>(right) :
499  std::get<1>(left) < std::get<1>(right);
500  });
501 
502  // Ok, get the two most distance points...
503  const reco::ClusterHit3D* startHit = std::get<2>(isolatedPointList.front());
504  const reco::ClusterHit3D* stopHit = std::get<2>(isolatedPointList.back());
505 
506  std::cout << "**> Sorted " << isolatedPointList.size()
507  << " hits, longest distance: " << DistanceBetweenNodes(startHit, stopHit)
508  << std::endl;
509 
510  // Call the AStar function to try to find the best path...
511  // AStar(startHit,stopHit,alpha,topNode,clusterParams);
512 
513  float cost(std::numeric_limits<float>::max());
514 
515  LeastCostPath(curEdgeMap[startHit].front(), stopHit, clusterParams, cost);
516 
517  clusterParams.getBestHitPairListPtr().push_front(startHit);
518 
519  std::cout << "**> Best path has " << clusterParams.getBestHitPairListPtr().size()
520  << " hits, " << clusterParams.getBestEdgeList().size() << " edges" << std::endl;
521  }
522  }
523  else {
524  std::cout << "++++++>>> PCA failure! # hits: " << curCluster.size() << std::endl;
525  }
526  }
527 
528  if (m_enableMonitoring) {
529  theClockPathFinding.stop();
530 
531  m_timeVector[PATHFINDING] += theClockPathFinding.accumulated_real_time();
532  }
533 
534  return;
535  }
536 
538  const reco::ClusterHit3D* goalNode,
539  reco::ClusterParameters& clusterParams) const
540  {
541  // Recover the list of hits and edges
542  reco::HitPairListPtr& pathNodeList = clusterParams.getBestHitPairListPtr();
543  reco::EdgeList& bestEdgeList = clusterParams.getBestEdgeList();
544  reco::Hit3DToEdgeMap& curEdgeMap = clusterParams.getHit3DToEdgeMap();
545 
546  // Find the shortest path from start to goal using an A* algorithm
547  // Keep track of the nodes which have already been evaluated
548  reco::HitPairListPtr closedList;
549 
550  // Keep track of the nodes that have been "discovered" but yet to be evaluated
551  reco::HitPairListPtr openList = {startNode};
552 
553  // Create a map which, for each node, will tell us the node it can be most efficiencly reached from.
554  BestNodeMap bestNodeMap;
555 
556  bestNodeMap[startNode] =
557  BestNodeTuple(startNode, 0., DistanceBetweenNodes(startNode, goalNode));
558 
559  while (!openList.empty()) {
560  // The list is not empty so by def we will return something
561  reco::HitPairListPtr::iterator currentNodeItr = openList.begin();
562 
563  // If the list contains more than one element then we need to find the one with the smallest total estimated cost to the end
564  if (openList.size() > 1)
565  currentNodeItr = std::min_element(
566  openList.begin(), openList.end(), [bestNodeMap](const auto& next, const auto& best) {
567  return std::get<2>(bestNodeMap.at(next)) < std::get<2>(bestNodeMap.at(best));
568  });
569 
570  // Easier to deal directly with the pointer to the node
571  const reco::ClusterHit3D* currentNode = *currentNodeItr;
572 
573  // Check to see if we have reached the goal and need to evaluate the path
574  if (currentNode == goalNode) {
575  // The path reconstruction will
576  ReconstructBestPath(goalNode, bestNodeMap, pathNodeList, bestEdgeList);
577 
578  break;
579  }
580 
581  // Otherwise need to keep evaluating
582  else {
583  openList.erase(currentNodeItr);
585 
586  // Get tuple values for the current node
587  const BestNodeTuple& currentNodeTuple = bestNodeMap.at(currentNode);
588  float currentNodeScore = std::get<1>(currentNodeTuple);
589 
590  // Recover the edges associated to the current point
591  const reco::EdgeList& curEdgeList = curEdgeMap[currentNode];
592 
593  for (const auto& curEdge : curEdgeList) {
594  const reco::ClusterHit3D* candHit3D = std::get<1>(curEdge);
595 
596  if (candHit3D->getStatusBits() & reco::ClusterHit3D::PATHCHECKED) continue;
597 
598  float tentative_gScore = currentNodeScore + std::get<2>(curEdge);
599 
600  // Have we seen the candidate node before?
601  BestNodeMap::iterator candNodeItr = bestNodeMap.find(candHit3D);
602 
603  if (candNodeItr == bestNodeMap.end()) { openList.push_back(candHit3D); }
604  else if (tentative_gScore > std::get<1>(candNodeItr->second))
605  continue;
606 
607  // Make a guess at score to get to target...
608  float guessToTarget = DistanceBetweenNodes(candHit3D, goalNode) / 0.3;
609 
610  bestNodeMap[candHit3D] =
611  BestNodeTuple(currentNode, tentative_gScore, tentative_gScore + guessToTarget);
612  }
613  }
614  }
615 
616  return;
617  }
618 
620  BestNodeMap& bestNodeMap,
621  reco::HitPairListPtr& pathNodeList,
622  reco::EdgeList& bestEdgeList) const
623  {
624  while (std::get<0>(bestNodeMap.at(goalNode)) != goalNode) {
625  const reco::ClusterHit3D* nextNode = std::get<0>(bestNodeMap[goalNode]);
626  reco::EdgeTuple bestEdge =
627  reco::EdgeTuple(goalNode, nextNode, DistanceBetweenNodes(goalNode, nextNode));
628 
629  pathNodeList.push_front(goalNode);
630  bestEdgeList.push_front(bestEdge);
631 
632  goalNode = nextNode;
633  }
634 
635  pathNodeList.push_front(goalNode);
636 
637  return;
638  }
639 
641  const reco::ClusterHit3D* goalNode,
642  reco::ClusterParameters& clusterParams,
643  float& showMeTheMoney) const
644  {
645  // Recover the mapping between hits and edges
646  reco::Hit3DToEdgeMap& curEdgeMap = clusterParams.getHit3DToEdgeMap();
647 
648  reco::Hit3DToEdgeMap::const_iterator edgeListItr = curEdgeMap.find(std::get<1>(curEdge));
649 
650  showMeTheMoney = std::numeric_limits<float>::max();
651 
652  if (edgeListItr != curEdgeMap.end() && !edgeListItr->second.empty()) {
653  reco::HitPairListPtr& bestNodeList = clusterParams.getBestHitPairListPtr();
654  reco::EdgeList& bestEdgeList = clusterParams.getBestEdgeList();
655 
656  for (const auto& edge : edgeListItr->second) {
657  // skip the self reference
658  if (std::get<1>(edge) == std::get<0>(curEdge)) continue;
659 
660  // Have we found the droid we are looking for?
661  if (std::get<1>(edge) == goalNode) {
662  bestNodeList.push_back(goalNode);
663  bestEdgeList.push_back(edge);
664  showMeTheMoney = std::get<2>(edge);
665  break;
666  }
667 
668  // Keep searching, it is out there somewhere...
669  float currentCost(0.);
670 
671  LeastCostPath(edge, goalNode, clusterParams, currentCost);
672 
673  if (currentCost < std::numeric_limits<float>::max()) {
674  showMeTheMoney = std::get<2>(edge) + currentCost;
675  break;
676  }
677  }
678  }
679 
680  if (showMeTheMoney < std::numeric_limits<float>::max()) {
681  clusterParams.getBestHitPairListPtr().push_front(std::get<1>(curEdge));
682  clusterParams.getBestEdgeList().push_front(curEdge);
683  }
684 
685  return;
686  }
687 
689  const reco::ClusterHit3D* node2) const
690  {
691  const Eigen::Vector3f& node1Pos = node1->getPosition();
692  const Eigen::Vector3f& node2Pos = node2->getPosition();
693  float deltaNode[] = {
694  node1Pos[0] - node2Pos[0], node1Pos[1] - node2Pos[1], node1Pos[2] - node2Pos[2]};
695 
696  // Standard euclidean distance
697  return std::sqrt(deltaNode[0] * deltaNode[0] + deltaNode[1] * deltaNode[1] +
698  deltaNode[2] * deltaNode[2]);
699 
700  // Manhatten distance
701  //return std::fabs(deltaNode[0]) + std::fabs(deltaNode[1]) + std::fabs(deltaNode[2]);
702  /*
703  // Chebyshev distance
704  // We look for maximum distance by wires...
705 
706  // Now go through the hits and compare view by view to look for delta wire and tigher constraint on delta t
707  int wireDeltas[] = {0,0,0};
708 
709  for(size_t idx = 0; idx < 3; idx++)
710  wireDeltas[idx] = std::abs(int(node1->getWireIDs()[idx].Wire) - int(node2->getWireIDs()[idx].Wire));
711 
712  // put wire deltas in order...
713  std::sort(wireDeltas, wireDeltas + 3);
714 
715  return std::sqrt(deltaNode[0]*deltaNode[0] + 0.09 * float(wireDeltas[2]*wireDeltas[2]));
716  */
717  }
718 
720  const reco::Hit3DToEdgeMap& hitToEdgeMap,
721  float& bestTreeQuality) const
722  {
723  reco::HitPairListPtr hitPairListPtr;
724  float bestQuality(0.);
725  float curEdgeWeight = std::max(0.3, std::get<2>(curEdge));
726  float curEdgeProj(1. / curEdgeWeight);
727 
728  reco::Hit3DToEdgeMap::const_iterator edgeListItr = hitToEdgeMap.find(std::get<1>(curEdge));
729 
730  if (edgeListItr != hitToEdgeMap.end()) {
731  // The input edge weight has quality factors applied, recalculate just the position difference
732  const Eigen::Vector3f& firstHitPos = std::get<0>(curEdge)->getPosition();
733  const Eigen::Vector3f& secondHitPos = std::get<1>(curEdge)->getPosition();
734  float curEdgeVec[] = {secondHitPos[0] - firstHitPos[0],
735  secondHitPos[1] - firstHitPos[1],
736  secondHitPos[2] - firstHitPos[2]};
737  float curEdgeMag = std::sqrt(curEdgeVec[0] * curEdgeVec[0] + curEdgeVec[1] * curEdgeVec[1] +
738  curEdgeVec[2] * curEdgeVec[2]);
739 
740  curEdgeMag = std::max(float(0.1), curEdgeMag);
741 
742  for (const auto& edge : edgeListItr->second) {
743  // skip the self reference
744  if (std::get<1>(edge) == std::get<0>(curEdge)) continue;
745 
746  float quality(0.);
747 
748  reco::HitPairListPtr tempList = DepthFirstSearch(edge, hitToEdgeMap, quality);
749 
750  if (quality > bestQuality) {
751  hitPairListPtr = tempList;
752  bestQuality = quality;
753  curEdgeProj = 1. / curEdgeMag;
754  }
755  }
756  }
757 
758  hitPairListPtr.push_front(std::get<1>(curEdge));
759 
760  bestTreeQuality += bestQuality + curEdgeProj;
761 
762  return hitPairListPtr;
763  }
764 
766  reco::Hit2DToClusterMap& hit2DToClusterMap) const
767  {
768 
769  // Recover the HitPairListPtr from the input clusterParams (which will be the
770  // only thing that has been provided)
771  reco::HitPairListPtr& hitPairVector = clusterParams.getHitPairListPtr();
772 
773  size_t nStartedWith(hitPairVector.size());
774  size_t nRejectedHits(0);
775 
776  reco::HitPairListPtr goodHits;
777 
778  // Loop through the hits and try to week out the clearly ambiguous ones
779  for (const auto& hit3D : hitPairVector) {
780  // Loop to try to remove ambiguous hits
781  size_t n2DHitsIn3DHit(0);
782  size_t nThisClusterOnly(0);
783  size_t nOtherCluster(0);
784 
785  // reco::ClusterParameters* otherCluster;
786  const std::set<const reco::ClusterHit3D*>* otherClusterHits = 0;
787 
788  for (const auto& hit2D : hit3D->getHits()) {
789  if (!hit2D) continue;
790 
791  n2DHitsIn3DHit++;
792 
793  if (hit2DToClusterMap[hit2D].size() < 2)
794  nThisClusterOnly = hit2DToClusterMap[hit2D][&clusterParams].size();
795  else {
796  for (const auto& clusterHitMap : hit2DToClusterMap[hit2D]) {
797  if (clusterHitMap.first == &clusterParams) continue;
798 
799  if (clusterHitMap.second.size() > nOtherCluster) {
800  nOtherCluster = clusterHitMap.second.size();
801  // otherCluster = clusterHitMap.first;
802  otherClusterHits = &clusterHitMap.second;
803  }
804  }
805  }
806  }
807 
808  if (n2DHitsIn3DHit < 3 && nThisClusterOnly > 1 && nOtherCluster > 0) {
809  bool skip3DHit(false);
810 
811  for (const auto& otherHit3D : *otherClusterHits) {
812  size_t nOther2DHits(0);
813 
814  for (const auto& otherHit2D : otherHit3D->getHits()) {
815  if (!otherHit2D) continue;
816 
817  nOther2DHits++;
818  }
819 
820  if (nOther2DHits > 2) {
821  skip3DHit = true;
822  nRejectedHits++;
823  break;
824  }
825  }
826 
827  if (skip3DHit) continue;
828  }
829 
830  goodHits.emplace_back(hit3D);
831  }
832 
833  std::cout << "###>> Input " << nStartedWith << " hits, rejected: " << nRejectedHits
834  << std::endl;
835 
836  hitPairVector.resize(goodHits.size());
837  std::copy(goodHits.begin(), goodHits.end(), hitPairVector.begin());
838 
839  return;
840  }
841 
845  {
846  // Watch out for the case where two clusters can have the same number of hits!
847  return (*left).getHitPairListPtr().size() > (*right).getHitPairListPtr().size();
848  }
849  };
850 
852  public:
853  SetCheckHitOrder(const std::vector<size_t>& plane) : m_plane(plane) {}
854 
856  {
857  // Check if primary view's hit is on the same wire
858  if (left->getWireIDs()[m_plane[0]] == right->getWireIDs()[m_plane[0]]) {
859  // Same wire but not same hit, order by primary hit time
860  if (left->getHits()[m_plane[0]] && right->getHits()[m_plane[0]] &&
861  left->getHits()[m_plane[0]] != right->getHits()[m_plane[0]]) {
862  return left->getHits()[m_plane[0]]->getHit()->PeakTime() <
863  right->getHits()[m_plane[0]]->getHit()->PeakTime();
864  }
865 
866  // Primary view is same hit, look at next view's wire
867  if (left->getWireIDs()[m_plane[1]] == right->getWireIDs()[m_plane[1]]) {
868  // Same wire but not same hit, order by secondary hit time
869  if (left->getHits()[m_plane[1]] && right->getHits()[m_plane[1]] &&
870  left->getHits()[m_plane[1]] != right->getHits()[m_plane[1]]) {
871  return left->getHits()[m_plane[1]]->getHit()->PeakTime() <
872  right->getHits()[m_plane[1]]->getHit()->PeakTime();
873  }
874 
875  // All that is left is the final view... and this can't be the same hit... (else it is the same 3D hit)
876  return left->getWireIDs()[m_plane[2]] < right->getWireIDs()[m_plane[2]];
877  }
878 
879  return left->getWireIDs()[m_plane[1]] < right->getWireIDs()[m_plane[1]];
880  }
881 
882  // Order by primary view's wire number
883  return left->getWireIDs()[m_plane[0]] < right->getWireIDs()[m_plane[0]];
884  }
885 
886  private:
887  const std::vector<size_t>& m_plane;
888  };
889 
891  {
892  reco::HitPairListPtr& curCluster = clusterParams.getHitPairListPtr();
893 
894  // Trial A* here
895  if (curCluster.size() > 2) {
896  // Do a quick PCA to determine our parameter "alpha"
898  m_pcaAlg.PCAAnalysis_3D(curCluster, pca);
899 
900  if (pca.getSvdOK()) {
901  const Eigen::Vector3f& pcaAxis = pca.getEigenVectors().row(2);
902 
903  std::vector<size_t> closestPlane = {0, 0, 0};
904  std::vector<float> bestAngle = {0., 0., 0.};
905 
906  for (size_t plane = 0; plane < 3; plane++) {
907  const std::vector<float>& wireDir = m_wireDir[plane];
908 
909  float dotProd =
910  std::fabs(pcaAxis[0] * wireDir[0] + pcaAxis[1] * wireDir[1] + pcaAxis[2] * wireDir[2]);
911 
912  if (dotProd > bestAngle[0]) {
913  bestAngle[2] = bestAngle[1];
914  closestPlane[2] = closestPlane[1];
915  bestAngle[1] = bestAngle[0];
916  closestPlane[1] = closestPlane[0];
917  closestPlane[0] = plane;
918  bestAngle[0] = dotProd;
919  }
920  else if (dotProd > bestAngle[1]) {
921  bestAngle[2] = bestAngle[1];
922  closestPlane[2] = closestPlane[1];
923  closestPlane[1] = plane;
924  bestAngle[1] = dotProd;
925  }
926  else {
927  closestPlane[2] = plane;
928  bestAngle[2] = dotProd;
929  }
930  }
931 
932  // Get a copy of our 3D hits
933  reco::HitPairListPtr localHitList = curCluster;
934 
935  // Sort the hits
936  localHitList.sort(SetCheckHitOrder(closestPlane));
937 
938  // Ok, let's print it all and take a look
939  std::cout << "*****************************************************************************"
940  "***************"
941  << std::endl;
942  std::cout << "**>>>>> longest axis: " << closestPlane[0] << ", best angle: " << bestAngle[0]
943  << std::endl;
944  std::cout << "**>>>>> second axis: " << closestPlane[1] << ", best angle: " << bestAngle[1]
945  << std::endl;
946  std::cout << " " << std::endl;
947 
948  reco::HitPairListPtr::iterator firstHitItr = localHitList.begin();
949  reco::HitPairListPtr::iterator lastHitItr = localHitList.begin();
950 
951  size_t bestPlane = closestPlane[0];
952 
953  reco::HitPairListPtr testList;
954 
955  while (firstHitItr != localHitList.end()) {
956  const reco::ClusterHit3D* currentHit = *firstHitItr;
957 
958  // Search for the last matching best view hit
959  while (lastHitItr != localHitList.end()) {
960  // If a different wire on the best view then we're certainly done
961  if (currentHit->getWireIDs()[bestPlane] != (*lastHitItr)->getWireIDs()[bestPlane])
962  break;
963 
964  // More subtle test to see if same wire but different hit (being careful of case of no hit)
965  if (currentHit->getHits()[bestPlane] && (*lastHitItr)->getHits()[bestPlane] &&
966  currentHit->getHits()[bestPlane] != (*lastHitItr)->getHits()[bestPlane])
967  break;
968 
969  // Yet event more subtle test...
970  if ((!(currentHit->getHits()[bestPlane]) && (*lastHitItr)->getHits()[bestPlane]) ||
971  (currentHit->getHits()[bestPlane] && !((*lastHitItr)->getHits()[bestPlane])))
972  break;
973 
974  // Not there yet...
975  lastHitItr++;
976  }
977 
978  // How many hits in this chain?
979  // size_t numHits(std::distance(firstHitItr,lastHitItr));
980  // float minOverlapFraction(0.);
981 
982  // if (numHits > 1)
983  // {
984  // reco::HitPairListPtr::iterator bestMinOverlapItr = std::max_element(firstHitItr,lastHitItr,[](const auto& left, const auto& right){return left->getMinOverlapFraction() < right->getMinOverlapFraction();});
985  //
986  // minOverlapFraction = std::min(0.999*(*bestMinOverlapItr)->getMinOverlapFraction(),0.90);
987  // }
988 
989  while (firstHitItr != lastHitItr) {
990  // if (currentHit->getMinOverlapFraction() > minOverlapFraction) testList.push_back(currentHit); //currentHit->setStatusBit(reco::ClusterHit3D::SKELETONHIT);
991 
992  currentHit = *++firstHitItr;
993  }
994 
995  firstHitItr = lastHitItr;
996  }
997  /*
998  for(const auto& hit : localHitList)
999  {
1000  std::cout << "- wires: ";
1001 
1002  for(size_t idx = 0; idx < 3; idx++)
1003  {
1004  float viewTime = -1.;
1005 
1006  if (hit->getHits()[closestView[idx]]) viewTime = hit->getHits()[closestView[idx]]->getTimeTicks();
1007 
1008  std::cout << closestView[idx] << ":" << hit->getWireIDs()[closestView[idx]].Wire << " - " << viewTime << ", ";
1009  }
1010 
1011  bool isSkeleton = hit->getStatusBits() & reco::ClusterHit3D::SKELETONHIT;
1012 
1013  std::cout << "ave time: " << hit->getAvePeakTime() << ", min/max overlap: " << hit->getMinOverlapFraction() << "/" << hit->getMaxOverlapFraction() << ", tagged: " << isSkeleton << std::endl;
1014 
1015  if (isSkeleton) testList.push_back(hit);
1016  }
1017 */
1018  curCluster = testList;
1019  }
1020  }
1021 
1022  return;
1023  }
1024 
1026 } // namespace lar_cluster3d
bool operator()(const reco::ClusterHit3D *left, const reco::ClusterHit3D *right) const
reco::HitPairListPtr & getBestHitPairListPtr()
Definition: Cluster3D.h:467
intermediate_table::iterator iterator
Geometry description of a TPC wireThe wire is a single straight segment on a wire plane...
Definition: WireGeo.h:114
std::tuple< const reco::ClusterHit3D *, float, float > BestNodeTuple
void configure(fhicl::ParameterSet const &pset) override
std::list< reco::ClusterHit3D > HitPairList
Definition: Cluster3D.h:330
bool getSvdOK() const
Definition: Cluster3D.h:240
std::vector< std::vector< float > > m_wireDir
constexpr auto const & right(const_AssnsIter< L, R, D, Dir > const &a, const_AssnsIter< L, R, D, Dir > const &b)
Definition: AssnsIter.h:102
#define DEFINE_ART_CLASS_TOOL(tool)
Definition: ToolMacros.h:42
void LeastCostPath(const reco::EdgeTuple &, const reco::ClusterHit3D *, reco::ClusterParameters &, float &) const
Find the lowest cost path between two nodes using MST edges.
void PCAAnalysis_3D(const reco::HitPairListPtr &hitPairList, reco::PrincipalComponents &pca, bool skeletonOnly=false) const
float getTimeToExecute(TimeValues index) const override
If monitoring, recover the time to execute a particular function.
std::vector< WireID > ChannelToWire(raw::ChannelID_t const channel) const
Returns a list of wires connected to the specified TPC channel.
kdTree class definiton
Definition: kdTree.h:31
bool m_enableMonitoring
Data members to follow.
const Eigen::Vector3f getPosition() const
Definition: Cluster3D.h:155
std::list< ProjectedPoint > ProjectedPointList
Definition: Cluster3D.h:346
Declaration of signal hit object.
std::list< KdTreeNode > KdTreeNodeList
Definition: kdTree.h:69
constexpr auto abs(T v)
Returns the absolute value of the argument.
Implements a kdTree for use in clustering.
size_t FindNearestNeighbors(const reco::ClusterHit3D *, const KdTreeNode &, CandPairList &, float &) const
Definition: kdTree.cxx:183
const std::vector< size_t > & m_plane
reco::EdgeList & getBestEdgeList()
Definition: Cluster3D.h:468
MinSpanTreeAlg(const fhicl::ParameterSet &)
Constructor.
reco::Hit3DToEdgeMap & getHit3DToEdgeMap()
Definition: Cluster3D.h:466
intermediate_table::const_iterator const_iterator
reco::HitPairListPtr & getHitPairListPtr()
Definition: Cluster3D.h:463
IClusterAlg interface class definiton.
Definition: IClusterAlg.h:26
std::vector< size_t > ChannelStatusVec
define data structure for keeping track of channel status
float DistanceBetweenNodes(const reco::ClusterHit3D *, const reco::ClusterHit3D *) const
unsigned int getStatusBits() const
Definition: Cluster3D.h:154
void AStar(const reco::ClusterHit3D *, const reco::ClusterHit3D *, reco::ClusterParameters &) const
Algorithm to find shortest path between two 3D hits.
decltype(auto) constexpr size(T &&obj)
ADL-aware version of std::size.
Definition: StdUtils.h:101
std::list< EdgeTuple > EdgeList
Definition: Cluster3D.h:337
define a kd tree node
Definition: kdTree.h:127
void PruneAmbiguousHits(reco::ClusterParameters &, reco::Hit2DToClusterMap &) const
Prune the obvious ambiguous hits.
const EigenValues & getEigenValues() const
Definition: Cluster3D.h:242
std::vector< ChannelStatusVec > ChannelStatusByPlaneVec
std::unordered_map< const reco::ClusterHit3D *, BestNodeTuple > BestNodeMap
Vector_t Direction() const
Definition: WireGeo.h:289
std::tuple< const reco::ClusterHit3D *, const reco::ClusterHit3D *, double > EdgeTuple
Definition: Cluster3D.h:336
parameter set interface
std::unordered_map< const reco::ClusterHit2D *, ClusterToHitPairSetMap > Hit2DToClusterMap
Definition: Cluster3D.h:499
T get(std::string const &key) const
Definition: ParameterSet.h:314
const Eigen::Vector3f & getAvePosition() const
Definition: Cluster3D.h:244
void RunPrimsAlgorithm(reco::HitPairList &, kdTree::KdTreeNode &, reco::ClusterParametersList &) const
Driver for Prim&#39;s algorithm.
Path checking algorithm has seen this hit.
Definition: Cluster3D.h:108
TimeValues
enumerate the possible values for time checking if monitoring timing
Definition: IClusterAlg.h:61
void CheckHitSorting(reco::ClusterParameters &clusterParams) const
void Cluster3DHits(reco::HitPairList &hitPairList, reco::ClusterParametersList &clusterParametersList) const override
Given a set of recob hits, run DBscan to form 3D clusters.
void fill(const art::PtrVector< recob::Hit > &hits, int only_plane)
std::list< const reco::ClusterHit3D * > HitPairListPtr
Definition: Cluster3D.h:326
This provides an art tool interface definition for 3D Cluster algorithms.
The geometry of one entire detector, as served by art.
Definition: Geometry.h:181
void Cluster3DHits(reco::HitPairListPtr &, reco::ClusterParametersList &) const override
Given a set of recob hits, run DBscan to form 3D clusters.
Definition of data types for geometry description.
std::unique_ptr< lar_cluster3d::IClusterParametersBuilder > m_clusterBuilder
Common cluster builder tool.
Detector simulation of raw signals on wires.
std::list< CandPair > CandPairList
Definition: kdTree.h:84
This header file defines the interface to a principal components analysis designed to be used within ...
Encapsulate the geometry of a wire.
constexpr auto const & left(const_AssnsIter< L, R, D, Dir > const &a, const_AssnsIter< L, R, D, Dir > const &b)
Definition: AssnsIter.h:94
decltype(auto) get(T &&obj)
ADL-aware version of std::to_string.
Definition: StdUtils.h:120
MaybeLogger_< ELseverityLevel::ELsev_success, false > LogDebug
const std::vector< geo::WireID > & getWireIDs() const
Definition: Cluster3D.h:170
bool operator()(const reco::ClusterParametersList::iterator &left, const reco::ClusterParametersList::iterator &right)
float getHitChiSquare() const
Definition: Cluster3D.h:163
std::unordered_map< const reco::ClusterHit3D *, reco::EdgeList > Hit3DToEdgeMap
Definition: Cluster3D.h:339
unsigned int ChannelID_t
Type representing the ID of a readout channel.
Definition: RawTypes.h:28
float getTimeToExecute() const
Definition: kdTree.h:104
WireGeo const * WirePtr(WireID const &wireid) const
Returns the specified wire.
KdTreeNode & BuildKdTree(Hit3DVec::iterator, Hit3DVec::iterator, KdTreeNodeList &, int depth=0) const
Given an input set of ClusterHit3D objects, build a kd tree structure.
Definition: kdTree.cxx:109
const ClusterHit2DVec & getHits() const
Definition: Cluster3D.h:168
art framework interface to geometry description
void ReconstructBestPath(const reco::ClusterHit3D *, BestNodeMap &, reco::HitPairListPtr &, reco::EdgeList &) const
std::list< ClusterParameters > ClusterParametersList
Definition: Cluster3D.h:393
decltype(auto) constexpr empty(T &&obj)
ADL-aware version of std::empty.
Definition: StdUtils.h:109
const EigenVectors & getEigenVectors() const
Definition: Cluster3D.h:243
attached to a cluster
Definition: Cluster3D.h:106
SetCheckHitOrder(const std::vector< size_t > &plane)
void FindBestPathInCluster(reco::ClusterParameters &) const
Algorithm to find the best path through the given cluster.
void setStatusBit(unsigned bits) const
Definition: Cluster3D.h:177
reco::HitPairListPtr DepthFirstSearch(const reco::EdgeTuple &, const reco::Hit3DToEdgeMap &, float &) const
a depth first search to find longest branches