LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
PCASeedFinderAlg.cxx
Go to the documentation of this file.
1 
11 
12 // Framework Includes
14 #include "fhiclcpp/ParameterSet.h"
15 
16 // LArSoft includes
19 
20 // ROOT includes
21 #include "TDecompSVD.h"
22 #include "TMatrixD.h"
23 #include "TVector3.h"
24 #include "TVectorD.h"
25 
26 // std includes
27 #include <memory>
28 
29 //------------------------------------------------------------------------------------------------------------------------------------------
30 
31 //------------------------------------------------------------------------------------------------------------------------------------------
32 // implementation follows
33 
34 namespace lar_cluster3d {
35 
37  : m_gapDistance(5.)
38  , m_numSeed2DHits(80)
39  , m_minAllowedCosAng(0.7)
40  , m_pcaAlg(pset.get<fhicl::ParameterSet>("PrincipalComponentsAlg"))
41  {
42  m_gapDistance = pset.get<double>("GapDistance", 5.);
43  m_numSeed2DHits = pset.get<size_t>("NumSeed2DHits", 80);
44  m_minAllowedCosAng = pset.get<double>("MinAllowedCosAng", 0.7);
46  }
47 
49  reco::PrincipalComponents& inputPCA,
50  SeedHitPairListPairVec& seedHitPairVec) const
51  {
52  bool foundGoodSeed(false);
53 
54  // Make sure we are using the right pca
55  reco::HitPairListPtr hitPairListPtr = inputHitPairListPtr;
56 
57  // Make a local copy of the input pca
58  reco::PrincipalComponents pca = inputPCA;
59 
60  // We also require that there be some spread in the data, otherwise not worth running?
61  double eigenVal0 = 3. * sqrt(pca.getEigenValues()[2]);
62  double eigenVal1 = 3. * sqrt(pca.getEigenValues()[1]);
63 
64  if (eigenVal0 > 5. && eigenVal1 > 0.001) {
65  // Presume CR muons will be "downward going"...
66  if (pca.getEigenVectors().row(2)(1) > 0.) pca.flipAxis(0);
67 
68  // Use the following to set the 3D doca and arclength for each hit
69  m_pcaAlg.PCAAnalysis_calc3DDocas(hitPairListPtr, pca);
70 
71  // Use this info to sort the hits along the principle axis
72  hitPairListPtr.sort(SeedFinderAlgBase::Sort3DHitsByArcLen3D());
73  //hitPairListPtr.sort(PcaSort3DHitsByAbsArcLen3D());
74 
75  // Make a local copy of the 3D hits
76  reco::HitPairListPtr hit3DList;
77 
78  hit3DList.resize(hitPairListPtr.size());
79 
80  std::copy(hitPairListPtr.begin(), hitPairListPtr.end(), hit3DList.begin());
81 
82  reco::PrincipalComponents seedPca = pca;
83 
84  if (getHitsAtEnd(hit3DList, seedPca)) {
85  // We can use the same routine to check hits at the opposite end to make sure
86  // we have consistency between both ends of the track.
87  // So... follow the same general program as above
88  reco::HitPairListPtr checkList;
89 
90  checkList.resize(hitPairListPtr.size());
91 
92  std::copy(hitPairListPtr.begin(), hitPairListPtr.end(), checkList.begin());
93 
94  std::reverse(checkList.begin(), checkList.end());
95 
96  reco::PrincipalComponents checkPca = pca;
97 
98  if (getHitsAtEnd(checkList, checkPca)) {
99  // We want our seed to be in the middle of our seed points
100  // First step to getting there is to reorder the hits along the
101  // new pca axis
102  m_pcaAlg.PCAAnalysis_calc3DDocas(hit3DList, seedPca);
103 
104  // Use this info to sort the hits along the principle axis - note by absolute value of arc length
106 
107  // Now translate the seedCenter by the arc len to the first hit
108  double seedDir[3] = {seedPca.getEigenVectors().row(2)(0),
109  seedPca.getEigenVectors().row(2)(1),
110  seedPca.getEigenVectors().row(2)(2)};
111  double seedStart[3] = {
112  hit3DList.front()->getX(), hit3DList.front()->getY(), hit3DList.front()->getZ()};
113 
114  if (hit3DList.size() > 10) {
115  TVector3 newSeedPos;
116  TVector3 newSeedDir;
117  double chiDOF;
118 
119  LineFit2DHits(hit3DList, seedStart[0], newSeedPos, newSeedDir, chiDOF);
120 
121  if (chiDOF > 0.) {
122  // check angles between new/old directions
123  double cosAng = seedDir[0] * newSeedDir[0] + seedDir[1] * newSeedDir[1] +
124  seedDir[2] * newSeedDir[2];
125 
126  if (cosAng < 0.) newSeedDir *= -1.;
127 
128  seedStart[0] = newSeedPos[0];
129  seedStart[1] = newSeedPos[1];
130  seedStart[2] = newSeedPos[2];
131  seedDir[0] = newSeedDir[0];
132  seedDir[1] = newSeedDir[1];
133  seedDir[2] = newSeedDir[2];
134  }
135  }
136 
137  for (const auto& hit3D : hit3DList)
138  hit3D->setStatusBit(0x40000000);
139 
140  seedHitPairVec.emplace_back(std::pair<recob::Seed, reco::HitPairListPtr>(
141  recob::Seed(seedStart, seedDir), hit3DList));
142 
143  foundGoodSeed = true;
144  }
145  }
146  }
147 
148  return foundGoodSeed;
149  }
150 
152  reco::PrincipalComponents& seedPca) const
153  {
154  bool foundGoodSeedHits(false);
155 
156  // Use a set to count 2D hits by keeping only a single copy
157  std::set<const reco::ClusterHit2D*> hit2DSet;
158 
159  // Look for numSeed2DHits which are continuous
160  double lastArcLen = hit3DList.front()->getArclenToPoca();
161 
162  reco::HitPairListPtr::iterator startItr = hit3DList.begin();
163  reco::HitPairListPtr::iterator lastItr = hit3DList.begin();
164 
165  while (++lastItr != hit3DList.end()) {
166  const reco::ClusterHit3D* hit3D = *lastItr;
167  double arcLen = hit3D->getArclenToPoca();
168 
169  if (fabs(arcLen - lastArcLen) > m_gapDistance) {
170  startItr = lastItr;
171  hit2DSet.clear();
172  }
173 
174  for (const auto& hit2D : hit3D->getHits()) {
175  hit2DSet.insert(hit2D);
176  }
177 
178  if (hit2DSet.size() > m_numSeed2DHits) break;
179 
180  lastArcLen = arcLen;
181  }
182 
183  if (hit2DSet.size() > m_numSeed2DHits) {
184  if (startItr != hit3DList.begin()) hit3DList.erase(hit3DList.begin(), startItr);
185  if (lastItr != hit3DList.end()) hit3DList.erase(lastItr, hit3DList.end());
186 
187  // On input, the seedPca will contain the original values so we can recover the original axis now
188  Eigen::Vector3f planeVec0(seedPca.getEigenVectors().row(2));
189 
190  m_pcaAlg.PCAAnalysis_3D(hit3DList, seedPca, true);
191 
192  if (seedPca.getSvdOK()) {
193  // Still looking to point "down"
194  if (seedPca.getEigenVectors().row(2)(1) > 0.) seedPca.flipAxis(0);
195 
196  // Check that the seed PCA we have found is consistent with the input PCA
197  Eigen::Vector3f primarySeedAxis(seedPca.getEigenVectors().row(2));
198 
199  double cosAng = primarySeedAxis.dot(planeVec0);
200 
201  // If the proposed seed axis is not relatively aligned with the input PCA then
202  // we should not be using this method to return seeds. Check that here
203  if (cosAng > m_minAllowedCosAng) foundGoodSeedHits = true;
204  }
205  }
206 
207  return foundGoodSeedHits;
208  }
209 
210  //------------------------------------------------------------------------------
212  double XOrigin,
213  TVector3& Pos,
214  TVector3& Dir,
215  double& ChiDOF) const
216  {
217  // The following is lifted from Bruce Baller to try to get better
218  // initial parameters for a candidate Seed. It is slightly reworked
219  // which is why it is included here instead of used as is.
220  //
221  // Linear fit using X as the independent variable. Hits to be fitted
222  // are passed in the hits vector in a pair form (X, WireID). The
223  // fitted track position at XOrigin is returned in the Pos vector.
224  // The direction cosines are returned in the Dir vector.
225  //
226  // SVD fit adapted from $ROOTSYS/tutorials/matrix/solveLinear.C
227  // Fit equation is w = A(X)v, where w is a vector of hit wires, A is
228  // a matrix to calculate a track projected to a point at X, and v is
229  // a vector (Yo, Zo, dY/dX, dZ/dX).
230  //
231  // Note: The covariance matrix should also be returned
232  // B. Baller August 2014
233 
234  // assume failure
235  ChiDOF = -1;
236 
237  // Make a set of 2D hits so we consider only unique hits
238  std::set<const reco::ClusterHit2D*> hit2DSet;
239 
240  for (const auto& hit3D : hit3DList) {
241  for (const auto& hit2D : hit3D->getHits())
242  hit2DSet.insert(hit2D);
243  }
244 
245  if (hit2DSet.size() < 4) return;
246 
247  const unsigned int nvars = 4;
248  unsigned int npts = 3 * hit3DList.size();
249 
250  TMatrixD A(npts, nvars);
251  // vector holding the Wire number
252  TVectorD w(npts);
253  unsigned short ninpl[3] = {0};
254  unsigned short nok = 0;
255  unsigned short iht(0);
256 
257  // Loop over the 2D hits in the above
258  for (const auto& hit : hit2DSet) {
259  geo::WireID const& wireID = hit->WireID();
260  unsigned int ipl = wireID.Plane;
261 
262  // get the wire plane offset
263  double const off = m_geometry->WireCoordinate(geo::Point_t{0, 0, 0}, wireID);
264 
265  // get the "cosine-like" component
266  double const cw = m_geometry->WireCoordinate(geo::Point_t{0, 1, 0}, wireID) - off;
267 
268  // the "sine-like" component
269  double const sw = m_geometry->WireCoordinate(geo::Point_t{0, 0, 1}, wireID) - off;
270 
271  double const x = hit->getXPosition() - XOrigin;
272 
273  A[iht][0] = cw;
274  A[iht][1] = sw;
275  A[iht][2] = cw * x;
276  A[iht][3] = sw * x;
277  w[iht] = wireID.Wire - off;
278 
279  ++ninpl[ipl];
280 
281  // need at least two points in a plane
282  if (ninpl[ipl] == 2) ++nok;
283 
284  iht++;
285  }
286 
287  // need at least 2 planes with at least two points
288  if (nok < 2) return;
289 
290  TDecompSVD svd(A);
291  bool ok;
292  TVectorD tVec = svd.Solve(w, ok);
293 
294  ChiDOF = 0;
295 
296  // not enough points to calculate Chisq
297  if (npts <= 4) return;
298 
299  for (const auto& hit : hit2DSet) {
300  geo::WireID const& wireID = hit->WireID();
301  double const off = m_geometry->WireCoordinate(geo::Point_t{0, 0, 0}, wireID);
302  double const cw = m_geometry->WireCoordinate(geo::Point_t{0, 1, 0}, wireID) - off;
303  double const sw = m_geometry->WireCoordinate(geo::Point_t{0, 0, 1}, wireID) - off;
304  double const x = hit->getXPosition() - XOrigin;
305  double const ypr = tVec[0] + tVec[2] * x;
306  double const zpr = tVec[1] + tVec[3] * x;
307  double const diff = ypr * cw + zpr * sw - (wireID.Wire - off);
308  ChiDOF += diff * diff;
309  }
310 
311  float werr2 = m_geometry->WirePitch() * m_geometry->WirePitch();
312  ChiDOF /= werr2;
313  ChiDOF /= (float)(npts - 4);
314 
315  double norm = sqrt(1 + tVec[2] * tVec[2] + tVec[3] * tVec[3]);
316  Dir[0] = 1 / norm;
317  Dir[1] = tVec[2] / norm;
318  Dir[2] = tVec[3] / norm;
319 
320  Pos[0] = XOrigin;
321  Pos[1] = tVec[0];
322  Pos[2] = tVec[1];
323 
324  } // TrkLineFit()
325 
326  /*
327  //------------------------------------------------------------------------------
328  void PCASeedFinderAlg::LineFit2DHits(const reco::HitPairListPtr& hit3DList,
329  double XOrigin,
330  TVector3& Pos,
331  TVector3& Dir,
332  double& ChiDOF) const
333  {
334  // Linear fit using X as the independent variable. Hits to be fitted
335  // are passed in the hits vector in a pair form (X, WireID). The
336  // fitted track position at XOrigin is returned in the Pos vector.
337  // The direction cosines are returned in the Dir vector.
338  //
339  // SVD fit adapted from $ROOTSYS/tutorials/matrix/solveLinear.C
340  // Fit equation is w = A(X)v, where w is a vector of hit wires, A is
341  // a matrix to calculate a track projected to a point at X, and v is
342  // a vector (Yo, Zo, dY/dX, dZ/dX).
343  //
344  // Note: The covariance matrix should also be returned
345  // B. Baller August 2014
346 
347  // assume failure
348  ChiDOF = -1;
349 
350  if(hit3DList.size() < 4) return;
351 
352  const unsigned int nvars = 4;
353  unsigned int npts = 3 * hit3DList.size();
354 
355  TMatrixD A(npts, nvars);
356  // vector holding the Wire number
357  TVectorD w(npts);
358  unsigned short ninpl[3] = {0};
359  unsigned short nok = 0;
360  unsigned short iht(0), cstat, tpc, ipl;
361  double x, cw, sw, off;
362 
363  for (const auto& hit3D : hit3DList)
364  {
365  // Inner loop over the 2D hits in the above
366  for (const auto& hit : hit3D->getHits())
367  {
368  geo::WireID wireID = hit->getHit().WireID();
369 
370  cstat = wireID.Cryostat;
371  tpc = wireID.TPC;
372  ipl = wireID.Plane;
373 
374  // get the wire plane offset
375  off = m_geometry->WireCoordinate(0, 0, ipl, tpc, cstat);
376 
377  // get the "cosine-like" component
378  cw = m_geometry->WireCoordinate(1, 0, ipl, tpc, cstat) - off;
379 
380  // the "sine-like" component
381  sw = m_geometry->WireCoordinate(0, 1, ipl, tpc, cstat) - off;
382 
383  x = hit->getXPosition() - XOrigin;
384 
385  A[iht][0] = cw;
386  A[iht][1] = sw;
387  A[iht][2] = cw * x;
388  A[iht][3] = sw * x;
389  w[iht] = wireID.Wire - off;
390 
391  ++ninpl[ipl];
392 
393  // need at least two points in a plane
394  if(ninpl[ipl] == 2) ++nok;
395 
396  iht++;
397  }
398  }
399 
400  // need at least 2 planes with at least two points
401  if(nok < 2) return;
402 
403  TDecompSVD svd(A);
404  bool ok;
405  TVectorD tVec = svd.Solve(w, ok);
406 
407  ChiDOF = 0;
408 
409  // not enough points to calculate Chisq
410  if(npts <= 4) return;
411 
412  double ypr, zpr, diff;
413 
414  for(const auto& hit3D : hit3DList)
415  {
416  for (const auto& hit : hit3D->getHits())
417  {
418  geo::WireID wireID = hit->getHit().WireID();
419 
420  cstat = wireID.Cryostat;
421  tpc = wireID.TPC;
422  ipl = wireID.Plane;
423  off = m_geometry->WireCoordinate(0, 0, ipl, tpc, cstat);
424  cw = m_geometry->WireCoordinate(1, 0, ipl, tpc, cstat) - off;
425  sw = m_geometry->WireCoordinate(0, 1, ipl, tpc, cstat) - off;
426  x = hit->getXPosition() - XOrigin;
427  ypr = tVec[0] + tVec[2] * x;
428  zpr = tVec[1] + tVec[3] * x;
429  diff = ypr * cw + zpr * sw - (wireID.Wire - off);
430  ChiDOF += diff * diff;
431  }
432  }
433 
434  float werr2 = m_geometry->WirePitch() * m_geometry->WirePitch();
435  ChiDOF /= werr2;
436  ChiDOF /= (float)(npts - 4);
437 
438  double norm = sqrt(1 + tVec[2] * tVec[2] + tVec[3] * tVec[3]);
439  Dir[0] = 1 / norm;
440  Dir[1] = tVec[2] / norm;
441  Dir[2] = tVec[3] / norm;
442 
443  Pos[0] = XOrigin;
444  Pos[1] = tVec[0];
445  Pos[2] = tVec[1];
446 
447  } // TrkLineFit()
448  */
449 
450 } // namespace lar_cluster3d
Float_t x
Definition: compare.C:6
intermediate_table::iterator iterator
Define a comparator which will sort hits by arc length along a PCA axis.
Length_t WireCoordinate(Point_t const &pos, PlaneID const &planeid) const
Returns the index of the nearest wire to the specified position.
void PCAAnalysis_calc3DDocas(const reco::HitPairListPtr &hitPairVector, const reco::PrincipalComponents &pca) const
bool getHitsAtEnd(reco::HitPairListPtr &hit3DList, reco::PrincipalComponents &seedPca) const
Separate function to find hits at the ends of the input hits.
bool getSvdOK() const
Definition: Cluster3D.h:240
geo::Geometry const * m_geometry
void PCAAnalysis_3D(const reco::HitPairListPtr &hitPairList, reco::PrincipalComponents &pca, bool skeletonOnly=false) const
T * get() const
Definition: ServiceHandle.h:69
void flipAxis(size_t axis)
Definition: Cluster3D.cxx:232
WireID_t Wire
Index of the wire within its plane.
Definition: geo_types.h:563
PrincipalComponentsAlg m_pcaAlg
This is an algorithm for finding recob::Seed objects in 3D clusters.
Define a comparator which will sort hits by the absolute value of arc length so hits are ordered clos...
const EigenValues & getEigenValues() const
Definition: Cluster3D.h:242
parameter set interface
T get(std::string const &key) const
Definition: ParameterSet.h:314
bool findTrackSeeds(reco::HitPairListPtr &hitPairListPtr, reco::PrincipalComponents &inputPCA, SeedHitPairListPairVec &seedHitMap) const override
Given the list of hits this will search for candidate Seed objects and return them.
std::list< const reco::ClusterHit3D * > HitPairListPtr
Definition: Cluster3D.h:326
PlaneID_t Plane
Index of the plane within its TPC.
Definition: geo_types.h:481
Detector simulation of raw signals on wires.
double m_minAllowedCosAng
The minimum cos(ang) between input and seed axes.
ROOT::Math::PositionVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Point_t
Type for representation of position in physical 3D space.
Definition: geo_vectors.h:180
Float_t sw
Definition: plot.C:20
PCASeedFinderAlg(fhicl::ParameterSet const &pset)
Constructor.
decltype(auto) get(T &&obj)
ADL-aware version of std::to_string.
Definition: StdUtils.h:120
Float_t norm
std::vector< SeedHitPairListPair > SeedHitPairListPairVec
float getArclenToPoca() const
Definition: Cluster3D.h:167
void LineFit2DHits(const reco::HitPairListPtr &hitList, double XOrigin, TVector3 &Pos, TVector3 &Dir, double &ChiDOF) const
Float_t w
Definition: plot.C:20
const ClusterHit2DVec & getHits() const
Definition: Cluster3D.h:168
Length_t WirePitch(PlaneID const &planeid=plane_zero) const
Returns the distance between two consecutive wires.
art framework interface to geometry description
const EigenVectors & getEigenVectors() const
Definition: Cluster3D.h:243