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