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