SkeletonAlg.cxx
Go to the documentation of this file.
1 /**
2  * @file SkeletonAlg.cxx
3  *
4  * @brief This algorithm accepts a list of 3D space points and attempts to find the "medial skeleton"
5  * describing the "best" path through them. Here, "medial" is defined by the best time match
6  * between associated 2D hits along the "best" directions.
7  *
8  */
9 
10 // Framework Includes
11 #include "fhiclcpp/ParameterSet.h"
12 
13 // LArSoft includes
16 
17 // std includes
18 #include <iostream>
19 
20 //------------------------------------------------------------------------------------------------------------------------------------------
21 
22 namespace lar_cluster3d
23 {
24 
26 {
27  reconfigure(pset);
28 }
29 
30 //------------------------------------------------------------------------------------------------------------------------------------------
31 
33 {
34 }
35 
36 //------------------------------------------------------------------------------------------------------------------------------------------
37 
39 {
40  m_minimumDeltaTicks = pset.get<double>("MinimumDeltaTicks", 0.05);
41  m_maximumDeltaTicks = pset.get<double>("MaximumDeltaTicks", 10.0 );
42 }
43 
44 double SkeletonAlg::FindFirstAndLastWires(std::vector<const reco::ClusterHit3D*>& hitVec,
45  int planeToCheck,
46  int referenceWire,
47  double referenceTicks,
48  int& firstWire,
49  int& lastWire) const
50 {
51  // In the simple case the first and last wires are simply the front and back of the input vector
52  firstWire = hitVec.front()->getHits()[planeToCheck]->WireID().Wire;
53  lastWire = hitVec.back()->getHits()[planeToCheck]->WireID().Wire;
54 
55  double maxDeltaTicks = referenceTicks - hitVec.front()->getHits()[planeToCheck]->getTimeTicks();
56  double minDeltaTicks = referenceTicks - hitVec.back()->getHits()[planeToCheck]->getTimeTicks();
57 
58  if (minDeltaTicks > maxDeltaTicks) std::swap(maxDeltaTicks, minDeltaTicks);
59 
60  double bestDeltaTicks = 1000000.;
61 
62  // Can't have a gap if only one element
63  if (hitVec.size() > 1)
64  {
65  // The issue is that there may be a gap in wires which we need to find.
66  // Reset the last wire
67  lastWire = firstWire;
68 
69  // Keep track of all the deltas
70  double nextBestDeltaTicks = bestDeltaTicks;
71 
72  for(const auto& hitPair : hitVec)
73  {
74  int curWire = hitPair->getHits()[planeToCheck]->WireID().Wire;
75  double deltaTicks = referenceTicks - hitPair->getHits()[planeToCheck]->getTimeTicks();
76 
77  maxDeltaTicks = std::max(maxDeltaTicks, deltaTicks);
78  minDeltaTicks = std::min(minDeltaTicks, deltaTicks);
79 
80  if (bestDeltaTicks > fabs(deltaTicks))
81  {
82  // By definition, the "next best" is now the current best
83  nextBestDeltaTicks = bestDeltaTicks;
84  bestDeltaTicks = fabs(deltaTicks);
85  }
86  else if (nextBestDeltaTicks > fabs(deltaTicks))
87  {
88  nextBestDeltaTicks = fabs(deltaTicks);
89  }
90 
91  // if gap detected, take action depending on where the input wire is
92  // But remember we need to be willing to accept a 1 wire gap... for efficiency
93  if (fabs(curWire - lastWire) > 2000)
94  {
95  if (referenceWire <= lastWire) break;
96 
97  // Stepped over gap, reset everything
98  firstWire = curWire;
99  maxDeltaTicks = deltaTicks;
100  minDeltaTicks = deltaTicks;
101  bestDeltaTicks = fabs(deltaTicks);
102  nextBestDeltaTicks = bestDeltaTicks;
103  }
104 
105  lastWire = curWire;
106  }
107 
108  bestDeltaTicks = nextBestDeltaTicks;
109  }
110 
111  bestDeltaTicks = std::max(std::min(bestDeltaTicks,m_maximumDeltaTicks), m_minimumDeltaTicks);
112 
113  if (minDeltaTicks * maxDeltaTicks > 0. && bestDeltaTicks > 30.) bestDeltaTicks = 0.;
114 
115  return bestDeltaTicks;
116 }
117 
118 class OrderHitsAlongWire
119 {
120 public:
121  OrderHitsAlongWire(int plane = 0) : m_plane(plane) {}
122 
124  {
125  for(const auto leftHit : left->getHits())
126  {
127  if (leftHit->WireID().Plane == m_plane)
128  {
129  for(const auto rightHit : right->getHits())
130  {
131  if (rightHit->WireID().Plane == m_plane)
132  {
133  return leftHit->WireID().Wire < rightHit->WireID().Wire;
134  }
135  }
136  return true;
137  }
138  }
139  return false;
140  }
141 private:
142  size_t m_plane;
143 };
144 
145 struct OrderBestPlanes
146 {
147  bool operator()(const std::pair<size_t,size_t>& left, const std::pair<size_t,size_t>& right)
148  {
149  return left.second < right.second;
150  }
151 
152 };
153 
155 {
156  // Our mission is to try to find the medial skeletion of the input list of hits
157  // We define that as the set of hit pairs where the pairs share the same hit in a given direction
158  // and the selected medial hit is equal distance from the edges.
159  // The first step in trying to do this is to build a map which relates a give 2D hit to an ordered list of hitpairs using it
160  typedef std::map<const reco::ClusterHit2D*, std::vector<const reco::ClusterHit3D*> > Hit2DtoHit3DMapU;
161 
162  Hit2DtoHit3DMapU hit2DToHit3DMap[3];
163 
164  for(const auto& hitPair : hitPairList)
165  {
166  // Don't consider points "rejected" earlier
167  if (hitPair->bitsAreSet(reco::ClusterHit3D::REJECTEDHIT)) continue;
168 
169  // Don't consider hitPair's with less than 3 hits
170  unsigned status = hitPair->getStatusBits();
171 
172  if ((status & 0x7) != 0x7) continue;
173 
174  for(const auto& hit2D : hitPair->getHits())
175  {
176  size_t plane = hit2D->WireID().Plane;
177 
178  hit2DToHit3DMap[plane][hit2D].push_back(hitPair);
179  }
180  }
181 
182  // Do an explicit loop through to sort?
183  for(size_t idx = 0; idx < 3; idx++)
184  {
185  for(auto& mapPair : hit2DToHit3DMap[idx])
186  {
187  size_t numHitPairs = mapPair.second.size();
188  int planeToCheck = (idx + 1) % 3; // have forgotten reasoning here...
189 
190  if (numHitPairs > 1) std::sort(mapPair.second.begin(), mapPair.second.end(), OrderHitsAlongWire(planeToCheck));
191  }
192  }
193 
194  // Keep a count of the number of skeleton points to be returned
195  int nSkeletonPoints(0);
196 
197  // The idea is go through all the hits again and determine if they could be "skeleton" elements
198  for(const auto& hitPair : hitPairList)
199  {
200  // Don't consider points "rejected" earlier
201  if (hitPair->bitsAreSet(reco::ClusterHit3D::REJECTEDHIT)) continue;
202 
203  // If a hit pair we skip for now
204  if ((hitPair->getStatusBits() & 0x7) != 7) continue;
205 
206  // Hopefully I am not confusing myself here.
207  // The goal is to know, for a given 3D hit, how many other 3D hits share the 2D hits that it is made of
208  // We want this to be a bit more precise in that we don't count other 3D hits if there is a gap between
209  // the current hit and the one we are comparing.
210  // How do we do this?
211  // We consider each 2D hit comprising the 3D hit in turn... for each 2D hit we have a list of the 3D
212  // hits which share this hit. Note that for the 3D hits to be unique, there must be an equal number of 2D
213  // hits from the other two views. So, to count "wires" to the boundary, we can simply pick one of the other views
214 
215  // so the idea is to through each of the views to determine
216  // a) the difference in distance to the last hit on this wire (in each direction)
217  // b) the number of 3D hits using the 2D hit in the given 3D hit
218  size_t numHitPairs[3] = {0, 0, 0}; // This keeps track of the number of 3D hits a given 2D hit is associated with
219  int deltaWires[3] = {0, 0, 0};
220  double planeDeltaT[3] = {0., 0., 0.};
221  double bestDeltaTicks[3] = {0., 0., 0.};
222  int wireNumByPlane[3] = {int(hitPair->getHits()[0]->WireID().Wire),
223  int(hitPair->getHits()[1]->WireID().Wire),
224  int(hitPair->getHits()[2]->WireID().Wire)};
225 
226  size_t bestPlaneIdx(0);
227 
228  // Initiate the loop over views
229  for(size_t planeIdx = 0; planeIdx < 3; planeIdx++)
230  {
231  const reco::ClusterHit2D* hit2D = hitPair->getHits()[planeIdx];
232  double hit2DTimeTicks = hit2D->getTimeTicks();
233 
234  std::vector<const reco::ClusterHit3D*>& hitVec(hit2DToHit3DMap[planeIdx][hit2D]);
235 
236  numHitPairs[planeIdx] = hitVec.size();
237 
238  if (numHitPairs[planeIdx] > 1)
239  {
240  int planeToCheck = (planeIdx + 1) % 3;
241  int firstWire;
242  int lastWire;
243 
244  bestDeltaTicks[planeIdx] = FindFirstAndLastWires(hitVec,
245  planeToCheck,
246  wireNumByPlane[planeToCheck],
247  hit2DTimeTicks,
248  firstWire,
249  lastWire);
250 
251  int deltaFirst = wireNumByPlane[planeToCheck] - firstWire;
252  int deltaLast = wireNumByPlane[planeToCheck] - lastWire;
253 
254  // If either distance to the first or last wire is zero then we are an edge point
255  if (deltaFirst == 0 || deltaLast == 0) hitPair->setStatusBit(reco::ClusterHit3D::EDGEHIT);
256 
257  deltaWires[planeIdx] = deltaFirst + deltaLast;
258  numHitPairs[planeIdx] = lastWire - firstWire + 1;
259  planeDeltaT[planeIdx] = fabs(hit2DTimeTicks - hitPair->getHits()[planeToCheck]->getTimeTicks());
260  }
261  // Otherwise, by definition, it is both a skeleton point and an edge point
262  else hitPair->setStatusBit(reco::ClusterHit3D::SKELETONHIT | reco::ClusterHit3D::EDGEHIT);
263 
264  if (numHitPairs[planeIdx] < numHitPairs[bestPlaneIdx])
265  {
266  bestPlaneIdx = planeIdx;
267  }
268  }
269 
270  // Make sure a real index was found (which should always happen)
271  if (bestPlaneIdx < 3 && numHitPairs[bestPlaneIdx] > 0)
272  {
273  // Make a sliding value for the width of the core region
274  int maxBestWires = 0.05 * numHitPairs[bestPlaneIdx] + 1;
275 
276  maxBestWires = 5000;
277 
278  // Check condition for a "skeleton" point
279  if (fabs(deltaWires[bestPlaneIdx]) < maxBestWires + 1)
280  {
281 
282  // If exactly in the middle then we are done
283  // Set a bit in the ClusterHit3D word to signify
284 // if (fabs(deltaWires[bestViewIdx]) < maxBestWires || numHitPairs[bestViewIdx] < 3)
285 // hitPair->setStatusBit(reco::ClusterHit3D::SKELETONHIT);
286 
287  // Otherwise we can try to look at the other view
288 // else
289  {
290  // Find the next best view
291  int nextBestIdx = (bestPlaneIdx + 1) % 3;
292 
293  for(size_t idx = 0; idx < 3; idx++)
294  {
295  if (idx == bestPlaneIdx) continue;
296 
297  if (numHitPairs[idx] < numHitPairs[nextBestIdx]) nextBestIdx = idx;
298  }
299 
300  if (nextBestIdx > 2)
301  {
302  std::cout << "***** invalid next best plane: " << nextBestIdx << " *******" << std::endl;
303  continue;
304  }
305 
306  if (planeDeltaT[bestPlaneIdx] < 1.01*bestDeltaTicks[bestPlaneIdx] && planeDeltaT[nextBestIdx] < 6.01*bestDeltaTicks[nextBestIdx])
307  hitPair->setStatusBit(reco::ClusterHit3D::SKELETONHIT);
308  }
309  }
310  }
311 
312  // We want to keep count of "pure" skeleton points only
313  if (hitPair->bitsAreSet(reco::ClusterHit3D::SKELETONHIT) && !hitPair->bitsAreSet(reco::ClusterHit3D::EDGEHIT)) nSkeletonPoints++;
314  }
315 
316  return nSkeletonPoints;
317 }
318 
319 void SkeletonAlg::GetSkeletonHits(const reco::HitPairListPtr& inputHitList, reco::HitPairListPtr& skeletonHitList) const
320 {
321  for(const auto& hit3D : inputHitList) if (hit3D->bitsAreSet(reco::ClusterHit3D::SKELETONHIT)) skeletonHitList.emplace_back(hit3D);
322 
323  return;
324 }
325 
327 {
328  // NOTE: This method assumes the list being given to it is comprised of skeleton hits
329  // YMMV if you send in a complete hit collection!
330 
331  // Define a mapping between 2D hits and the 3D hits they make
332  typedef std::map<const reco::ClusterHit2D*, std::vector<const reco::ClusterHit3D*> > Hit2DtoHit3DMap;
333 
334  // We want to keep track of this mapping by view
335  Hit2DtoHit3DMap hit2DToHit3DMap[3];
336 
337  // Keep count of the number of skeleton hits selected
338  unsigned int nSkeletonHits(0);
339 
340  // Execute the first loop through the hits to build the map
341  for(const auto& hitPair : skeletonHitList)
342  {
343  // Don't consider points "rejected" earlier
344  if (hitPair->bitsAreSet(reco::ClusterHit3D::REJECTEDHIT)) continue;
345 
346  // Count only those skeleton hits which have not been averaged
347  if (!hitPair->bitsAreSet(reco::ClusterHit3D::SKELETONPOSAVE)) nSkeletonHits++;
348 
349  for(const auto& hit2D : hitPair->getHits())
350  {
351  size_t plane = hit2D->WireID().Plane;
352 
353  hit2DToHit3DMap[plane][hit2D].push_back(hitPair);
354  }
355  }
356 
357  // Exit early if no skeleton hits to process
358  if (!nSkeletonHits) return;
359 
360  // The list of 3D hits associated to any given 2D hit is most useful to us if it is ordered
361  // So this will loop through entries in the map and do the ordering
362  for(size_t idx = 0; idx < 3; idx++)
363  {
364  for(auto& mapPair : hit2DToHit3DMap[idx])
365  {
366  size_t numHitPairs = mapPair.second.size();
367  int planeToCheck = (idx + 1) % 3; // have forgotten reasoning here...
368 
369  if (numHitPairs > 1) std::sort(mapPair.second.begin(), mapPair.second.end(), OrderHitsAlongWire(planeToCheck));
370  }
371  }
372 
373  // Ok, so the basic strategy is not entirely different from that used to build the skeleton hits in the first
374  // place. The idea is to loop through all skeleton hits and then determine the average position for the hit
375  // based on the wire direction with the fewest hits to the edge.
376  // Currently this is a pretty simple minded approach and it would appear that some effort could be spent
377  // here to improve this.
378  // NOTE: the averaging being performed is ONLY in the Y-Z plane since this is where the hit ambiguity
379  // issue arises.
380  reco::HitPairListPtr::iterator hitPairItr = skeletonHitList.begin();
381 
382  for(int bestPlaneVecIdx = 0; bestPlaneVecIdx < 2; bestPlaneVecIdx++)
383  {
384  std::list<reco::ClusterHit3D> tempHitPairList;
385  reco::HitPairListPtr tempHitPairListPtr;
386 
387  std::map<const reco::ClusterHit3D*, const reco::ClusterHit3D*> hit3DToHit3DMap;
388 
389  while(hitPairItr != skeletonHitList.end())
390  {
391  const reco::ClusterHit3D* hit3D = *hitPairItr++;
392 
393  std::vector<std::pair<size_t,size_t> > bestPlaneVec;
394 
395  for(const auto& hit2D : hit3D->getHits())
396  {
397  bestPlaneVec.push_back(std::pair<size_t,size_t>(hit2D->WireID().Plane,hit2DToHit3DMap[hit2D->WireID().Plane][hit2D].size()));
398  }
399 
400  std::sort(bestPlaneVec.begin(), bestPlaneVec.end(), OrderBestPlanes());
401 
402  size_t bestPlaneIdx = bestPlaneVec[bestPlaneVecIdx].first;
403  size_t bestPlaneCnt = bestPlaneVec[bestPlaneVecIdx].second;
404 
405  if (bestPlaneCnt > 5) continue;
406 
407  std::vector<const reco::ClusterHit3D*>& hit3DVec = hit2DToHit3DMap[bestPlaneIdx][hit3D->getHits()[bestPlaneIdx]];
408 
409  Eigen::Vector3f avePosition(hit3D->getPosition()[0],0.,0.);
410 
411  for(const auto& tempHit3D : hit3DVec)
412  {
413  avePosition[1] += tempHit3D->getPosition()[1];
414  avePosition[2] += tempHit3D->getPosition()[2];
415  }
416 
417  avePosition[1] *= 1./double(hit3DVec.size());
418  avePosition[2] *= 1./double(hit3DVec.size());
419 
420  tempHitPairList.emplace_back(reco::ClusterHit3D(hit3D->getID(),
421  hit3D->getStatusBits(),
422  avePosition,
423  hit3D->getTotalCharge(),
424  hit3D->getAvePeakTime(),
425  hit3D->getDeltaPeakTime(),
426  hit3D->getSigmaPeakTime(),
427  hit3D->getHitChiSquare(),
428  hit3D->getOverlapFraction(),
429  hit3D->getChargeAsymmetry(),
430  hit3D->getDocaToAxis(),
431  hit3D->getArclenToPoca(),
432  hit3D->getHits(),
433  hit3D->getHitDelTSigVec(),
434  hit3D->getWireIDs()));
435 
436  tempHitPairListPtr.push_back(&tempHitPairList.back());
437 
438  hit3DToHit3DMap[tempHitPairListPtr.back()] = hit3D;
439  }
440 
441  for(const auto& pair : hit3DToHit3DMap)
442  {
443  pair.second->setPosition(pair.first->getPosition());
444  pair.second->setStatusBit(reco::ClusterHit3D::SKELETONPOSAVE);
445  }
446  }
447 
448  return;
449 }
450 
451 
452 } // namespace lar_cluster3d
intermediate_table::iterator iterator
float getTimeTicks() const
Definition: Cluster3D.h:76
const Eigen::Vector3f getPosition() const
Definition: Cluster3D.h:158
Hit is an "edge" hit.
Definition: Cluster3D.h:101
const std::vector< float > getHitDelTSigVec() const
Definition: Cluster3D.h:172
float getTotalCharge() const
Definition: Cluster3D.h:162
Hit has been rejected for any reason.
Definition: Cluster3D.h:99
float getSigmaPeakTime() const
Definition: Cluster3D.h:165
float getOverlapFraction() const
Definition: Cluster3D.h:167
unsigned int getStatusBits() const
Definition: Cluster3D.h:157
void GetSkeletonHits(const reco::HitPairListPtr &inputHitList, reco::HitPairListPtr &skeletonHitList) const
Return the skeleton hits from the input list.
float getDocaToAxis() const
Definition: Cluster3D.h:169
SkeletonAlg(fhicl::ParameterSet const &pset)
Constructor.
Definition: SkeletonAlg.cxx:31
void reconfigure(fhicl::ParameterSet const &pset)
a handler for the case where the algorithm control parameters are to be reset
Definition: SkeletonAlg.cxx:44
float getAvePeakTime() const
Definition: Cluster3D.h:163
float getChargeAsymmetry() const
Definition: Cluster3D.h:168
void swap(Handle< T > &a, Handle< T > &b)
int FindMedialSkeleton(reco::HitPairListPtr &hitPairList) const
This is intended to find the medial skeleton given a list of input hit pairs.
T get(std::string const &key) const
Definition: ParameterSet.h:271
void AverageSkeletonPositions(reco::HitPairListPtr &skeletonHitList) const
Modifies the position of input skeleton hits by averaging along the "best" wire direction.
size_t getID() const
Definition: Cluster3D.h:156
std::list< const reco::ClusterHit3D * > HitPairListPtr
Definition: Cluster3D.h:335
bool operator()(const reco::ClusterHit3D *left, const reco::ClusterHit3D *right)
static int max(int a, int b)
Definition of data types for geometry description.
double FindFirstAndLastWires(std::vector< const reco::ClusterHit3D * > &hitVec, int viewToCheck, int referenceWire, double referenceTicks, int &firstWire, int &lastWire) const
A function to find the bounding wires in a given view.
Definition: SkeletonAlg.cxx:50
bool operator()(const std::pair< size_t, size_t > &left, const std::pair< size_t, size_t > &right)
T min(sqlite3 *const db, std::string const &table_name, std::string const &column_name)
Definition: statistics.h:55
virtual ~SkeletonAlg()
Destructor.
Definition: SkeletonAlg.cxx:38
const std::vector< geo::WireID > & getWireIDs() const
Definition: Cluster3D.h:173
float getArclenToPoca() const
Definition: Cluster3D.h:170
float getDeltaPeakTime() const
Definition: Cluster3D.h:164
Skeleton hit position averaged.
Definition: Cluster3D.h:106
float getHitChiSquare() const
Definition: Cluster3D.h:166
Hit is a "skeleton" hit.
Definition: Cluster3D.h:100
const ClusterHit2DVec & getHits() const
Definition: Cluster3D.h:171
void setPosition(const Eigen::Vector3f &pos) const
Definition: Cluster3D.h:186
QTextStream & endl(QTextStream &s)