LArSoft  v06_85_00
Liquid Argon Software toolkit - http://larsoft.org/
kdTree.cxx
Go to the documentation of this file.
1 
8 // Framework Includes
9 #include "cetlib/search_path.h"
10 #include "cetlib/cpu_timer.h"
11 
14 
15 // LArSoft includes
19 
20 // std includes
21 #include <string>
22 #include <functional>
23 #include <iostream>
24 #include <memory>
25 #include <unordered_map>
26 
27 //------------------------------------------------------------------------------------------------------------------------------------------
28 // implementation follows
29 
30 namespace lar_cluster3d {
31 
33 {
34  this->configure(pset);
35 }
36 
37 //------------------------------------------------------------------------------------------------------------------------------------------
38 
40 {
41 }
42 
43 //------------------------------------------------------------------------------------------------------------------------------------------
44 
46 {
47  m_enableMonitoring = pset.get<bool> ("EnableMonitoring", true);
48  m_pairSigmaPeakTime = pset.get<float>("PairSigmaPeakTime", 3. );
49  m_refLeafBestDist = pset.get<float>("RefLeafBestDist", 0.5 );
50 
51  m_timeToBuild = 0;
52 
53  return;
54 }
55 
56 //------------------------------------------------------------------------------------------------------------------------------------------
58  KdTreeNodeList& kdTreeNodeContainer) const
59 {
60 
61  // The first task is to build the kd tree
62  cet::cpu_timer theClockBuildNeighborhood;
63 
64  if (m_enableMonitoring) theClockBuildNeighborhood.start();
65 
66  // The input is a list and we need to copy to a vector so we can sort ranges
67  Hit3DVec hit3DVec;
68 
69  hit3DVec.reserve(hitPairList.size());
70 
71  for(const auto& hitPtr : hitPairList) hit3DVec.emplace_back(hitPtr.get());
72 
73  KdTreeNode topNode = BuildKdTree(hit3DVec.begin(), hit3DVec.end(), kdTreeNodeContainer);
74 
76  {
77  theClockBuildNeighborhood.stop();
78  m_timeToBuild = theClockBuildNeighborhood.accumulated_real_time();
79  }
80 
81  return topNode;
82 }
83 
84 //------------------------------------------------------------------------------------------------------------------------------------------
86  KdTreeNodeList& kdTreeNodeContainer) const
87 {
88 
89  // The first task is to build the kd tree
90  cet::cpu_timer theClockBuildNeighborhood;
91 
92  if (m_enableMonitoring) theClockBuildNeighborhood.start();
93 
94  // The input is a list and we need to copy to a vector so we can sort ranges
95  //Hit3DVec hit3DVec{std::begin(hitPairList),std::end(hitPairList)};
96  Hit3DVec hit3DVec;
97 
98  hit3DVec.reserve(hitPairList.size());
99 
100  for(const auto& hit3D : hitPairList)
101  {
102  // Make sure all the bits used by the clustering stage have been cleared
104  for(const auto& hit2D : hit3D->getHits())
105  if (hit2D) hit2D->clearStatusBits(0xFFFFFFFF);
106  hit3DVec.emplace_back(hit3D);
107  }
108 
109  KdTreeNode topNode = BuildKdTree(hit3DVec.begin(), hit3DVec.end(), kdTreeNodeContainer);
110 
111  if (m_enableMonitoring)
112  {
113  theClockBuildNeighborhood.stop();
114  m_timeToBuild = theClockBuildNeighborhood.accumulated_real_time();
115  }
116 
117  return topNode;
118 }
119 
121  Hit3DVec::iterator last,
122  KdTreeNodeList& kdTreeNodeContainer,
123  int depth) const
124 {
125  // Ok, so if the input list is more than one element then we have work to do... but if less then handle end condition
126  if (std::distance(first,last) < 2)
127  {
128  if (first != last) kdTreeNodeContainer.emplace_back(*first);
129  else kdTreeNodeContainer.emplace_back(KdTreeNode());
130  }
131  // Otherwise we need to keep splitting...
132  else
133  {
134  // First task is to find "d" with the largest range. We need to find the min/max for the four dimensions
135  std::pair<Hit3DVec::iterator,Hit3DVec::iterator> minMaxXPair = std::minmax_element(first,last,[](const reco::ClusterHit3D* left, const reco::ClusterHit3D* right){return left->getPosition()[0] < right->getPosition()[0];});
136  std::pair<Hit3DVec::iterator,Hit3DVec::iterator> minMaxYPair = std::minmax_element(first,last,[](const reco::ClusterHit3D* left, const reco::ClusterHit3D* right){return left->getPosition()[1] < right->getPosition()[1];});
137  std::pair<Hit3DVec::iterator,Hit3DVec::iterator> minMaxZPair = std::minmax_element(first,last,[](const reco::ClusterHit3D* left, const reco::ClusterHit3D* right){return left->getPosition()[2] < right->getPosition()[2];});
138 
139  std::vector<float> rangeVec(3,0.);
140 
141  rangeVec[0] = (*minMaxXPair.second)->getPosition()[0] - (*minMaxXPair.first)->getPosition()[0];
142  rangeVec[1] = (*minMaxYPair.second)->getPosition()[1] - (*minMaxYPair.first)->getPosition()[1];
143  rangeVec[2] = (*minMaxZPair.second)->getPosition()[2] - (*minMaxZPair.first)->getPosition()[2];
144 
145  std::vector<float>::iterator maxRangeItr = std::max_element(rangeVec.begin(),rangeVec.end());
146 
147  size_t maxRangeIdx = std::distance(rangeVec.begin(),maxRangeItr);
148 
149  // Sort the list so we can do the split
150  std::sort(first,last,[maxRangeIdx](const auto& left, const auto& right){return left->getPosition()[maxRangeIdx] < right->getPosition()[maxRangeIdx];});
151 
152  size_t middleElem = std::distance(first,last) / 2;
153  Hit3DVec::iterator middleItr = first;
154 
155  std::advance(middleItr, middleElem);
156 
157  // Take care of the special case where the value of the median may be repeated so we actually want to make sure we point at the first occurence
158  if (std::distance(first,middleItr) > 1)
159  {
160  while(middleItr != first+1)
161  {
162  if (!((*(middleItr-1))->getPosition()[maxRangeIdx] < (*middleItr)->getPosition()[maxRangeIdx])) middleItr--;
163  else break;
164  }
165  }
166 
168  float axisVal = 0.5*((*middleItr)->getPosition()[maxRangeIdx] + (*(middleItr-1))->getPosition()[maxRangeIdx]);
169  KdTreeNode& leftNode = BuildKdTree(first, middleItr, kdTreeNodeContainer, depth+1);
170  KdTreeNode& rightNode = BuildKdTree(middleItr, last, kdTreeNodeContainer, depth+1);
171 
172  kdTreeNodeContainer.push_back(KdTreeNode(axis[maxRangeIdx],axisVal,leftNode,rightNode));
173  }
174 
175  return kdTreeNodeContainer.back();
176 }
177 
178 size_t kdTree::FindNearestNeighbors(const reco::ClusterHit3D* refHit, const KdTreeNode& node, CandPairList& CandPairList, float& bestDist) const
179 {
180  // If at a leaf then time to decide to add hit or not
181  if (node.isLeafNode())
182  {
183  // Is this the droid we are looking for?
184  if (refHit == node.getClusterHit3D()) bestDist = m_refLeafBestDist; // This distance will grab neighbors with delta wire # = 1 in all three planes
185  // This is the tight constraint on the hits
186  else if (consistentPairs(refHit, node.getClusterHit3D(), bestDist))
187  {
188  CandPairList.emplace_back(bestDist,node.getClusterHit3D());
189 
190  bestDist = std::max(m_refLeafBestDist, bestDist); // This insures we will always consider neighbors with wire # changing in 2 planes
191  }
192  }
193  // Otherwise we need to keep searching
194  else
195  {
196  float refPosition = refHit->getPosition()[node.getSplitAxis()];
197 
198  if (refPosition < node.getAxisValue())
199  {
200  FindNearestNeighbors(refHit, node.leftTree(), CandPairList, bestDist);
201 
202  if (refPosition + bestDist > node.getAxisValue()) FindNearestNeighbors(refHit, node.rightTree(), CandPairList, bestDist);
203  }
204  else
205  {
206  FindNearestNeighbors(refHit, node.rightTree(), CandPairList, bestDist);
207 
208  if (refPosition - bestDist < node.getAxisValue()) FindNearestNeighbors(refHit, node.leftTree(), CandPairList, bestDist);
209  }
210  }
211 
212  return CandPairList.size();
213 }
214 
215 bool kdTree::FindEntry(const reco::ClusterHit3D* refHit, const KdTreeNode& node, CandPairList& CandPairList, float& bestDist, bool& selfNotFound, int depth) const
216 {
217  bool foundEntry(false);
218 
219  // If at a leaf then time to decide to add hit or not
220  if (node.isLeafNode())
221  {
222  float hitSeparation(0.);
223 
224  // Is this the droid we are looking for?
225  if (refHit == node.getClusterHit3D()) selfNotFound = false;
226 
227  // This is the tight constraint on the hits
228  if (consistentPairs(refHit, node.getClusterHit3D(), hitSeparation))
229  {
230  CandPairList.emplace_back(hitSeparation,node.getClusterHit3D());
231 
232  if (bestDist < std::numeric_limits<float>::max()) bestDist = std::max(bestDist,hitSeparation);
233  else bestDist = std::max(float(0.5),hitSeparation);
234  }
235 
236  foundEntry = !selfNotFound;
237  }
238  // Otherwise we need to keep searching
239  else
240  {
241  float refPosition = refHit->getPosition()[node.getSplitAxis()];
242 
243  if (refPosition < node.getAxisValue())
244  {
245  foundEntry = FindEntry(refHit, node.leftTree(), CandPairList, bestDist, selfNotFound, depth+1);
246 
247  if (!foundEntry && refPosition + bestDist > node.getAxisValue()) foundEntry = FindEntry(refHit, node.rightTree(), CandPairList, bestDist, selfNotFound, depth+1);
248  }
249  else
250  {
251  foundEntry = FindEntry(refHit, node.rightTree(), CandPairList, bestDist, selfNotFound, depth+1);
252 
253  if (!foundEntry && refPosition - bestDist < node.getAxisValue()) foundEntry = FindEntry(refHit, node.leftTree(), CandPairList, bestDist, selfNotFound, depth+1);
254  }
255  }
256 
257  return foundEntry;
258 }
259 
260 bool kdTree::FindEntryBrute(const reco::ClusterHit3D* refHit, const KdTreeNode& node, int depth) const
261 {
262  // If at a leaf then time to decide to add hit or not
263  if (node.isLeafNode())
264  {
265  // This is the tight constraint on the hits
266  if (refHit == node.getClusterHit3D()) return true;
267  }
268  // Otherwise we need to keep searching
269  else
270  {
271  if (FindEntryBrute(refHit, node.leftTree(), depth+1)) return true;
272  if (FindEntryBrute(refHit, node.rightTree(), depth+1)) return true;
273  }
274 
275  return false;
276 }
277 
278 //------------------------------------------------------------------------------------------------------------------------------------------
279 
280 bool kdTree::consistentPairs(const reco::ClusterHit3D* pair1, const reco::ClusterHit3D* pair2, float& bestDist) const
281 {
282  // Strategy: We consider comparing "hit pairs" which may consist of 2 or 3 actual hits.
283  // Also, if only pairs, they can be U-V, U-W or V-W so we can't assume which views we have
284  // So do a simplified comparison:
285  // 1) compare the pair times and require "overlap" (in the sense of hit pair production)
286  // 2) look at distance between pairs in each of the wire directions
287 
288  bool consistent(false);
289 
290  if (bestDist < std::numeric_limits<float>::max() && pair1->getWireIDs()[0].Cryostat == pair2->getWireIDs()[0].Cryostat && pair1->getWireIDs()[0].TPC == pair2->getWireIDs()[0].TPC)
291  {
292  // Loose constraint to weed out the obviously bad combinations
293  // So this is not strictly correct but is close enough and should save computation time...
294  if (std::fabs(pair1->getAvePeakTime() - pair2->getAvePeakTime()) < m_pairSigmaPeakTime * (pair1->getSigmaPeakTime() + pair2->getSigmaPeakTime()))
295  {
296  int wireDeltas[] = {0, 0, 0};
297 
298  // Now go through the hits and compare view by view to look for delta wire and tigher constraint on delta t
299  for(size_t idx = 0; idx < 3; idx++)
300  wireDeltas[idx] = std::abs(int(pair1->getWireIDs()[idx].Wire) - int(pair2->getWireIDs()[idx].Wire));
301 
302  // put wire deltas in order...
303  std::sort(wireDeltas, wireDeltas + 3);
304 
305  // Requirement to be considered a nearest neighbor
306  //if (wireDeltas[0] < 2 && wireDeltas[1] < 2 && wireDeltas[2] < 3)
307  if (wireDeltas[2] < 3) //2)
308  {
309  float hitSeparation = std::max(float(0.0001),DistanceBetweenNodes(pair1,pair2));
310 
311  // Final cut...
312  if (hitSeparation < bestDist)
313  {
314  bestDist = hitSeparation;
315  consistent = true;
316  }
317  }
318  }
319  }
320 
321  return consistent;
322 }
323 
325 {
326  const float* node1Pos = node1->getPosition();
327  const float* node2Pos = node2->getPosition();
328  float deltaNode[] = {node1Pos[0]-node2Pos[0], node1Pos[1]-node2Pos[1], node1Pos[2]-node2Pos[2]};
329 
330  // Standard euclidean distance
331  return std::sqrt(deltaNode[0]*deltaNode[0]+deltaNode[1]*deltaNode[1]+deltaNode[2]*deltaNode[2]);
332 
333  // Manhatten distance
334  //return std::fabs(deltaNode[0]) + std::fabs(deltaNode[1]) + std::fabs(deltaNode[2]);
335  /*
336  // Chebyshev distance
337  // We look for maximum distance by wires...
338 
339  // Now go through the hits and compare view by view to look for delta wire and tigher constraint on delta t
340  int wireDeltas[] = {0,0,0};
341 
342  for(size_t idx = 0; idx < 3; idx++)
343  wireDeltas[idx] = std::abs(int(node1->getWireIDs()[idx].Wire) - int(node2->getWireIDs()[idx].Wire));
344 
345  // put wire deltas in order...
346  std::sort(wireDeltas, wireDeltas + 3);
347 
348  return std::sqrt(deltaNode[0]*deltaNode[0] + 0.09 * float(wireDeltas[2]*wireDeltas[2]));
349  */
350 }
351 
352 } // namespace lar_cluster3d
Hit contains 2D hit from view 2 (w plane)
Definition: Cluster3D.h:107
constexpr auto const & right(const_AssnsIter< L, R, D, Dir > const &a, const_AssnsIter< L, R, D, Dir > const &b)
Definition: AssnsIter.h:112
intermediate_table::iterator iterator
Declaration of signal hit object.
void configure(fhicl::ParameterSet const &pset)
Configure our kdTree...
Definition: kdTree.cxx:45
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
float m_pairSigmaPeakTime
Consider hits consistent if "significance" less than this.
Definition: kdTree.h:99
Hit contains 2D hit from view 0 (u plane)
Definition: Cluster3D.h:105
float getSigmaPeakTime() const
Definition: Cluster3D.h:152
kdTree(fhicl::ParameterSet const &pset)
Constructor.
Definition: kdTree.cxx:32
std::vector< const reco::ClusterHit3D * > Hit3DVec
Definition: kdTree.h:59
std::list< std::unique_ptr< reco::ClusterHit3D >> HitPairList
Definition: Cluster3D.h:319
float DistanceBetweenNodes(const reco::ClusterHit3D *, const reco::ClusterHit3D *) const
Definition: kdTree.cxx:324
float getAvePeakTime() const
Definition: Cluster3D.h:150
define a kd tree node
Definition: kdTree.h:107
Int_t max
Definition: plot.C:27
float m_refLeafBestDist
Set neighborhood distance to this when ref leaf found.
Definition: kdTree.h:100
virtual ~kdTree()
Destructor.
Definition: kdTree.cxx:39
T get(std::string const &key) const
Definition: ParameterSet.h:231
Hit contains 2D hit from view 1 (v plane)
Definition: Cluster3D.h:106
std::list< const reco::ClusterHit3D * > HitPairListPtr
Definition: Cluster3D.h:315
This provides an art tool interface definition for 3D Cluster algorithms.
bool m_enableMonitoring
Definition: kdTree.h:97
bool consistentPairs(const reco::ClusterHit3D *pair1, const reco::ClusterHit3D *pair2, float &hitSeparation) const
The bigger question: are two pairs of hits consistent?
Definition: kdTree.cxx:280
const float * getPosition() const
Definition: Cluster3D.h:145
std::list< CandPair > CandPairList
Definition: kdTree.h:70
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
bool FindEntry(const reco::ClusterHit3D *, const KdTreeNode &, CandPairList &, float &, bool &, int) const
Definition: kdTree.cxx:215
Encapsulate the construction of a single detector plane.
bool FindEntryBrute(const reco::ClusterHit3D *, const KdTreeNode &, int) const
Definition: kdTree.cxx:260
const KdTreeNode & rightTree() const
Definition: kdTree.h:147
const std::vector< geo::WireID > & getWireIDs() const
Definition: Cluster3D.h:158
const reco::ClusterHit3D * getClusterHit3D() const
Definition: kdTree.h:145
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 KdTreeNode & leftTree() const
Definition: kdTree.h:146
SplitAxis getSplitAxis() const
Definition: kdTree.h:143