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