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