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