LArSoft  v07_13_02
Liquid Argon Software toolkit - http://larsoft.org/
ClusterMergeAlg_tool.cc
Go to the documentation of this file.
1 
8 // Framework Includes
11 #include "cetlib/search_path.h"
12 #include "cetlib/cpu_timer.h"
13 
15 
16 // LArSoft includes
23 
24 // Root includes
25 #include "TVector3.h"
26 
27 // std includes
28 #include <string>
29 #include <functional>
30 #include <iostream>
31 #include <memory>
32 
33 //------------------------------------------------------------------------------------------------------------------------------------------
34 // implementation follows
35 
36 namespace lar_cluster3d {
37 
38 class ClusterMergeAlg : virtual public IClusterModAlg
39 {
40 public:
46  explicit ClusterMergeAlg(const fhicl::ParameterSet&);
47 
52 
53  void configure(fhicl::ParameterSet const &pset) override;
54 
62 
69  void ModifyClusters(reco::ClusterParametersList&) const override;
70 
74  float getTimeToExecute() const override {return m_timeToProcess;}
75 
76 private:
77 
79 
81 
82  float closestApproach(const TVector3&, const TVector3&, const TVector3&, const TVector3&, TVector3&, TVector3&) const;
83 
88  double m_minCosAxisAng;
90  mutable float m_timeToProcess;
91 
92  geo::Geometry* m_geometry; //< pointer to the Geometry service
93 
94  PrincipalComponentsAlg m_pcaAlg; // For running Principal Components Analysis
95 };
96 
98  m_pcaAlg(pset.get<fhicl::ParameterSet>("PrincipalComponentsAlg"))
99 {
100  this->configure(pset);
101 }
102 
103 //------------------------------------------------------------------------------------------------------------------------------------------
104 
106 {
107 }
108 
109 //------------------------------------------------------------------------------------------------------------------------------------------
110 
112 {
113  m_enableMonitoring = pset.get<bool> ("EnableMonitoring", true );
114  m_minCosAxisAng = pset.get<double>("MinCosAxisAng", 0.975 );
115  m_minEigenToProcess = pset.get<double>("MinEigenToProcess", 2.0 );
116 
118 
119  m_geometry = &*geometry;
120 
121  m_timeToProcess = 0.;
122 
123  return;
124 }
125 
127 {
128  return;
129 }
130 
132 {
140  // Initial clustering is done, now trim the list and get output parameters
141  cet::cpu_timer theClockBuildClusters;
142 
143  // Start clocks if requested
144  if (m_enableMonitoring) theClockBuildClusters.start();
145 
146  // Resort by the largest first eigen value (so the "longest" cluster)
147  clusterParametersList.sort([](auto& left, auto& right){return left.getFullPCA().getEigenValues()[0] > right.getFullPCA().getEigenValues()[0];});
148 
149  reco::ClusterParametersList::iterator firstClusterItr = clusterParametersList.begin();
150 
151 // int clusCntr(0);
152 
153  while(firstClusterItr != clusterParametersList.end())
154  {
155  reco::ClusterParameters& firstClusterParams = *firstClusterItr;
156  reco::ClusterParametersList::iterator nextClusterItr = firstClusterItr;
157 
158  // Once you get down to the smallest clusters if they haven't already been absorbed there is no need to check them
159  if (firstClusterParams.getFullPCA().getEigenValues()[0] < m_minEigenToProcess) break;
160 
161 // std::cout << "+++++++++++++++++++++++++++++++ Checking PCA for cluster # " << clusCntr++ << " +++++++++++++++++++++++++++" << std::endl;
162 // std::cout << "+++++++ eigen values: " << firstClusterParams.getFullPCA().getEigenValues()[0] << "/" << firstClusterParams.getFullPCA().getEigenValues()[1] << "/" << firstClusterParams.getFullPCA().getEigenValues()[2] << " +++++++++" << std::endl;
163 
164  // want the next one...
165  nextClusterItr++;
166 
167  while(nextClusterItr != clusterParametersList.end())
168  {
169  reco::ClusterParameters& nextClusterParams = *nextClusterItr;
170 
171  // On any given loop through here it **might** be that the first cluster has been modified. So can't cache
172  // the parameters, need the curret ones
173  if (consistentClusters(firstClusterParams.getFullPCA(),nextClusterParams.getFullPCA()))
174  {
175  if (mergeClusters(firstClusterParams, nextClusterParams))
176  {
177  // Now remove the "next" cluster
178  nextClusterItr = clusterParametersList.erase(nextClusterItr);
179 
180  // Restart loop?
181  nextClusterItr = firstClusterItr;
182  }
183  }
184 
185  nextClusterItr++;
186  }
187 
188  firstClusterItr++;
189  }
190 
191 
192  if (m_enableMonitoring)
193  {
194  theClockBuildClusters.stop();
195 
196  m_timeToProcess = theClockBuildClusters.accumulated_real_time();
197  }
198 
199  mf::LogDebug("Cluster3D") << ">>>>> Merge clusters done, found " << clusterParametersList.size() << " clusters" << std::endl;
200 
201  return;
202 }
203 
205 {
206  // Assume failure
207  bool consistent(false);
208 
209  // Two types of conditions to decide between to start:
210  // 1) the cluster to merge lies along the trajectory of the primary cluster, in which case merge it
211  // - note that this should take care of relatively colinear trajectories
212  // 2) the trajectories of the two clusters are consistent
213  // - their doca is small
214  // - the vectors to their centers are not inconsistent with the trajectory of the first,
215  // - etc.
216  //
217  // Initial set up to check if the merge candidate is within the trajectory of the primary cluster
218 
219  // Recover the positions of the centers of the two clusters
220  TVector3 firstCenter(firstPCA.getAvePosition()[0],firstPCA.getAvePosition()[1],firstPCA.getAvePosition()[2]);
221  TVector3 nextCenter(nextPCA.getAvePosition()[0],nextPCA.getAvePosition()[1],nextPCA.getAvePosition()[2]);
222 
223  // And form a vector between the two centers
224  TVector3 firstPosToNextPos = nextCenter - firstCenter;
225 
226  // Now get the first PCA's primary axis and since we'll use them get all of them at once...
227  TVector3 firstAxis0(firstPCA.getEigenVectors()[0][0],firstPCA.getEigenVectors()[0][1],firstPCA.getEigenVectors()[0][2]);
228  TVector3 firstAxis1(firstPCA.getEigenVectors()[1][0],firstPCA.getEigenVectors()[1][1],firstPCA.getEigenVectors()[1][2]);
229  TVector3 firstAxis2(firstPCA.getEigenVectors()[2][0],firstPCA.getEigenVectors()[2][1],firstPCA.getEigenVectors()[2][2]);
230 
231  // Adopt the convention that the cluster axis is in same direction as vector from first to next centers
232  if (firstPosToNextPos.Dot(firstAxis0) < 0.) firstAxis0 = -firstAxis0;
233 
234  // Want the distance of closest approach of the next cluser's center to the primary, start by finding arc length
235  double arcLenToNextDoca = firstPosToNextPos.Dot(firstAxis0);
236 
237  // Position on the first cluster's axis of doca to next center
238  TVector3 firstAxisDocaPos = firstCenter + arcLenToNextDoca * firstAxis0;
239 
240  // Doca vector
241  TVector3 nextDocaVec = nextCenter - firstAxisDocaPos;
242 
243  // Need the projection of the doca vector onto the two transverse axes of the first cluster
244  double docaVecProj1 = std::fabs(firstAxis1.Dot(nextDocaVec));
245  double docaVecProj2 = std::fabs(firstAxis2.Dot(nextDocaVec));
246 
247  // We will now compare these to the eigen values of the first cluster, so recover all of them
248  TVector3 firstEigenVals(3. * sqrt(firstPCA.getEigenValues()[0]),
249  3. * sqrt(firstPCA.getEigenValues()[1]),
250  3. * sqrt(firstPCA.getEigenValues()[2]));
251 
252  // Use the angle between the vector between cluster centers and the first axis to moderate the selection cut
253  double firstToNextDist = firstPosToNextPos.Mag();
254  double cosAngFTNtoAxis0 = arcLenToNextDoca / firstToNextDist;
255  double docaVecProj1Cut = std::max( 1., (1. + 2. * cosAngFTNtoAxis0) * firstEigenVals[1]);
256  double docaVecProj2Cut = std::max(0.5, (1. + 2. * cosAngFTNtoAxis0) * firstEigenVals[2]);
257 
258 // std::cout << " ==> Check in tube, doca: " << nextDocaVec.Mag() << ", proj: " << docaVecProj1 << "/" << docaVecProj2 << ", cut: " << docaVecProj1Cut << "/" << docaVecProj2Cut << ", eigen: " << firstEigenVals[0] << "/" << firstEigenVals[1] << "/" << firstEigenVals[2] << ", arcLenToNextDoca: " << arcLenToNextDoca << ", cos(ang): " << cosAngFTNtoAxis0 << std::endl;
259 
260  // Ok, the first selection is that the cluster to merge lies within an (elliptical) tube of the first cluster's axis
261  if (docaVecProj1 < docaVecProj1Cut && docaVecProj2 < docaVecProj2Cut) consistent = true;
262 
263  // Otherwise we need to decide if the two clusters are consistent because they are "similar"...
264  else
265  {
266  // Set up to find the distance of closeset approach of the two primary axes.
267  // Results vectors
268  TVector3 firstPoca;
269  TVector3 nextPoca;
270 
271  // Get the primary axis for the next point
272  TVector3 nextAxis0(nextPCA.getEigenVectors()[0][0],nextPCA.getEigenVectors()[0][1],nextPCA.getEigenVectors()[0][2]);
273 
274  // Convention on axis direction applied again
275  if (firstPosToNextPos.Dot(nextAxis0) < 0.) nextAxis0 = -nextAxis0;
276 
277  // Recover the doca of the two axes and their points of closest approach
278  float lineDoca = closestApproach(firstCenter, firstAxis0, nextCenter, nextAxis0, firstPoca, nextPoca);
279 
280  // Determine the arc lengths to the two pocas
281  double arcLenToFirstPoca = (firstPoca - firstCenter).Dot(firstAxis0); // is this faster than "Mag"?
282  double arcLenToNextPoca = (nextPoca - nextCenter ).Dot(nextAxis0);
283 
284  // std::cout << " - arcLenToFirstPoca: " << arcLenToFirstPoca << ", arcLenToNextPoca: " << arcLenToNextPoca << ", first/Next dist: " << firstToNextDist << std::endl;
285 
286  // Require both of these to be less than length from first to next and to have the "right" sign where for the arc length
287  // to the first axis poca this will be positive and for the next poca it will be negative
288  // This prevents really long clusters that are not consistent from getting attached at their end points
289  if (arcLenToFirstPoca >= 0. && arcLenToFirstPoca < firstToNextDist && arcLenToNextPoca <= 0. && arcLenToNextPoca > -firstToNextDist)
290  {
291  // Don't let clusters that are really far apart get joined together and really try to suppress clusters which
292  // are not colinear but which have a small doca
293  double nextEigenVal0 = std::max(1.,3. * sqrt(nextPCA.getEigenValues()[0]));
294  double nextArcLenCut = (1. + 5. * cosAngFTNtoAxis0) * nextEigenVal0;
295 
296 // std::cout << " - linedoca: " << lineDoca << ", cosAngFTNtoAxis0: " << cosAngFTNtoAxis0 << ", nextEigenVal0: " << nextEigenVal0 << ", nextArcLenCut: " << nextArcLenCut << std::endl;
297 
298  // Check the actual doca with a simple cut on the first eigen value, make sure "in range"
299  if (lineDoca < firstEigenVals[1] && -arcLenToNextPoca < nextArcLenCut) consistent = true;
300  }
301  }
302 
303  return consistent;
304 
305  // hide all of this down here for now
306 /*
307  // We need the next cluster's principal eigenvalue
308  double nextEigenVal0 = 3. * std::sqrt(nextPCA.getEigenValues()[0]);
309 
310  // Now get position of endpoint of next cluster
311  TVector3 nextEndPoint = nextCenter - nextEigenVal0 * nextAxis0;
312 
313  // Ok, now reset the firstPosToNextPos vector
314  firstPosToNextPos = nextEndPoint - firstCenter;
315 
316  // Get the arclength to the point on the first axis which is the poca to the next point
317  double arcLenToNextDoca = firstPosToNextPos.Dot(firstAxis0);
318 
319  // And recover that point
320  TVector3 firstAxisPoca = firstCenter + arcLenToNextDoca * firstAxis0;
321 
322  // And then the vector for poca
323  TVector3 nextToFirstDocaVec = nextEndPoint - firstAxisPoca;
324 
325  // Projections of the doca vec on the first and second eigen axes
326  double docaVecProjMajor = std::fabs(nextToFirstDocaVec.Dot(firstAxis1));
327  double docaVecProjMinor = std::fabs(nextToFirstDocaVec.Dot(firstAxis2));
328 
329  // Get the eigen values
330 
331  // Get the ratio of the first eigen value to the arc length
332  double scaleFactor = std::max(3.,arcLenToNextDoca / firstEigenVals[0]);
333  double cutValueMajor = scaleFactor * firstEigenVals[1];
334  double cutValueMinor = scaleFactor * firstEigenVals[2];
335 
336 
337  // Ok, the first selection is that the doca of the next point to the first PCA primary axis is within
338  // the allowed maximum spread
339  if (docaVecProjMajor < cutValueMajor && docaVecProjMinor < cutValueMinor)
340  {
341 
342  // Effectively the same cut as above but now with the point of closest approach
343  float arcLenToPoca0 = (firstPoca - firstCenter).Dot(firstAxis0);
344  float docaCutValue = arcLenToPoca0 * firstEigenVals[1] / firstEigenVals[0];
345 
346  std::cout << " --> arcLenToPoca0: " << arcLenToPoca0 << ", firstToNext: " << firstPosToNextPos.Mag() << ", docaCutValue: " << docaCutValue << ", lineDoca: " << lineDoca << std::endl;
347 
348  // Require that the doca of the two lines be within range
349  if (lineDoca < 3. * docaCutValue)
350  {
351  // Final check, look at arc lengths to pocas
352  double arcLenToPocaFirst = (firstPoca - firstCenter).Dot(firstAxis0);
353  double arcLenToPocaNext = (nextPoca - nextEndPoint).Dot(nextAxis0);
354  double firstPosToNextPosDist = firstPosToNextPos.Mag();
355  double totalArcLen = arcLenToPocaFirst - arcLenToPocaNext; // arcLenToPocaNext should be negative
356 
357  std::cout << " >>> arcLenToPocaFirst: " << arcLenToPocaFirst << ", arcLenToPocaNext: " << arcLenToPocaNext << ", totalArcLen: " << totalArcLen << ", 2nd cut: " << firstPosToNextPos.Mag() / m_minCosAxisAng << std::endl;
358 
359  // It can be that that the lines are nearly parallel so the poca can be past the center of the two clusters
360  // If that is the case then it should be that a simple angle check is sufficient
361  if (arcLenToPocaFirst > firstPosToNextPosDist || arcLenToPocaNext > firstPosToNextPosDist)
362  {
363  if (firstPosToNextPos.Unit().Dot(firstAxis0) > m_minCosAxisAng) consistent = true;
364  }
365  // Otherwise the poca's are both "between" the cluster centers so require the sum of arc lengths to be within range
366  else if (2. * totalArcLen >firstPosToNextPos.Mag() && totalArcLen < firstPosToNextPos.Mag() / m_minCosAxisAng) consistent = true;
367  }
368  }
369 */
370 }
371 
372 bool ClusterMergeAlg::mergeClusters(reco::ClusterParameters& firstClusterParams, reco::ClusterParameters& nextClusterParams) const
373 {
374  bool merged(false);
375 
376  // Merge the next cluster into the first one
377  // Do this by making a local copy of the input cluster parameters
378  reco::ClusterParameters clusterParams = firstClusterParams;
379 
380  // Get the hits
381  reco::HitPairListPtr& hitPairListPtr = clusterParams.getHitPairListPtr();
382 
383  for(const auto* hit : nextClusterParams.getHitPairListPtr())
384  {
385  hitPairListPtr.push_back(hit);
386 
387  for(const auto* hit2D : hit->getHits())
388  if (hit2D) clusterParams.UpdateParameters(hit2D);
389  }
390 
391 
392  TVector3 origCenter(clusterParams.getFullPCA().getAvePosition()[0],clusterParams.getFullPCA().getAvePosition()[1],clusterParams.getFullPCA().getAvePosition()[2]);
393  TVector3 origAxis0(clusterParams.getFullPCA().getEigenVectors()[0][0],clusterParams.getFullPCA().getEigenVectors()[0][1],clusterParams.getFullPCA().getEigenVectors()[0][2]);
394  TVector3 origEigen(clusterParams.getFullPCA().getEigenValues()[0],clusterParams.getFullPCA().getEigenValues()[1],clusterParams.getFullPCA().getEigenValues()[2]);
395 
396 // std::cout << " **>> orig center: " << origCenter[0] << "/" << origCenter[1] << "/" << origCenter[2] << std::endl;
397 // std::cout << " orig vector: " << origAxis0[0] << "/" << origAxis0[1] << "/" << origAxis0[2] << std::endl;
398 // std::cout << " orig eigen: " << origEigen[0] << "/" << origEigen[1] << "/" << origEigen[2] << std::endl;
399 
400 
401  // Recalculate the PCA
402  m_pcaAlg.PCAAnalysis_3D(clusterParams.getHitPairListPtr(), clusterParams.getFullPCA());
403 
404 
405  TVector3 newCenter(clusterParams.getFullPCA().getAvePosition()[0],clusterParams.getFullPCA().getAvePosition()[1],clusterParams.getFullPCA().getAvePosition()[2]);
406  TVector3 newAxis0(clusterParams.getFullPCA().getEigenVectors()[0][0],clusterParams.getFullPCA().getEigenVectors()[0][1],clusterParams.getFullPCA().getEigenVectors()[0][2]);
407  TVector3 newEigen(clusterParams.getFullPCA().getEigenValues()[0],clusterParams.getFullPCA().getEigenValues()[1],clusterParams.getFullPCA().getEigenValues()[2]);
408 
409 // std::cout << " >>>> new center: " << newCenter[0] << "/" << newCenter[1] << "/" << newCenter[2] << std::endl;
410 // std::cout << " new vector: " << newAxis0[0] << "/" << newAxis0[1] << "/" << newAxis0[2] << std::endl;
411 // std::cout << " new eigen: " << newEigen[0] << "/" << newEigen[1] << "/" << newEigen[2] << ", cos orig to new: " << origAxis0.Dot(newAxis0) << std::endl;
412 
413 
414 // if (newEigen[0] > 0.8 * origEigen[0] && newEigen[1] * origEigen[0] < 5. * newEigen[0] * origEigen[1])
415  {
416  // Must have a valid pca
417  if (clusterParams.getFullPCA().getSvdOK())
418  {
419  // Finish out the merging here
420  reco::Hit3DToEdgeMap& firstEdgeMap = clusterParams.getHit3DToEdgeMap();
421  reco::Hit3DToEdgeMap& nextEdgeMap = nextClusterParams.getHit3DToEdgeMap();
422 
423  for(const auto& pair : nextEdgeMap) firstEdgeMap[pair.first] = pair.second;
424 
425  // Set the skeleton PCA to make sure it has some value
426  clusterParams.getSkeletonPCA() = clusterParams.getFullPCA();
427 
428  // Copy back to the input
429  firstClusterParams = clusterParams;
430 
431  merged = true;
432  }
433  }
434 
435  return merged;
436 }
437 
438 float ClusterMergeAlg::closestApproach(const TVector3& P0, const TVector3& u0,
439  const TVector3& P1, const TVector3& u1,
440  TVector3& poca0,
441  TVector3& poca1) const
442 {
443  // Technique is to compute the arclength to each point of closest approach
444  TVector3 w0 = P0 - P1;
445  float a(1.);
446  float b(u0.Dot(u1));
447  float c(1.);
448  float d(u0.Dot(w0));
449  float e(u1.Dot(w0));
450  float den(a * c - b * b);
451 
452  float arcLen0 = (b * e - c * d) / den;
453  float arcLen1 = (a * e - b * d) / den;
454 
455  poca0 = P0 + arcLen0 * u0;
456  poca1 = P1 + arcLen1 * u1;
457 
458  return (poca0 - poca1).Mag();
459 }
460 
462 } // namespace lar_cluster3d
void ModifyClusters(reco::ClusterParametersList &) const override
Scan an input collection of clusters and modify those according to the specific implementing algorith...
void configure(fhicl::ParameterSet const &pset) override
bool getSvdOK() const
Definition: Cluster3D.h:226
constexpr auto const & right(const_AssnsIter< L, R, D, Dir > const &a, const_AssnsIter< L, R, D, Dir > const &b)
Definition: AssnsIter.h:112
#define DEFINE_ART_CLASS_TOOL(tool)
Definition: ToolMacros.h:45
void PCAAnalysis_3D(const reco::HitPairListPtr &hitPairList, reco::PrincipalComponents &pca, bool skeletonOnly=false) const
float closestApproach(const TVector3 &, const TVector3 &, const TVector3 &, const TVector3 &, TVector3 &, TVector3 &) const
intermediate_table::iterator iterator
Float_t den
Definition: plot.C:37
void initializeHistograms(art::TFileDirectory &) override
Interface for initializing histograms if they are desired Note that the idea is to put hisgtograms in...
Declaration of signal hit object.
reco::PrincipalComponents & getSkeletonPCA()
Definition: Cluster3D.h:455
const float * getAvePosition() const
Definition: Cluster3D.h:230
reco::Hit3DToEdgeMap & getHit3DToEdgeMap()
Definition: Cluster3D.h:456
reco::HitPairListPtr & getHitPairListPtr()
Definition: Cluster3D.h:453
IClusterModAlg interface class definiton.
Int_t max
Definition: plot.C:27
bool mergeClusters(reco::ClusterParameters &, reco::ClusterParameters &) const
float getTimeToExecute() const override
If monitoring, recover the time to execute a particular function.
reco::PrincipalComponents & getFullPCA()
Definition: Cluster3D.h:454
parameter set interface
T get(std::string const &key) const
Definition: ParameterSet.h:231
ClusterMergeAlg(const fhicl::ParameterSet &)
Constructor.
Float_t d
Definition: plot.C:237
std::list< const reco::ClusterHit3D * > HitPairListPtr
Definition: Cluster3D.h:317
void UpdateParameters(const reco::ClusterHit2D *hit)
Definition: Cluster3D.h:429
The geometry of one entire detector, as served by art.
Definition: Geometry.h:110
Detector simulation of raw signals on wires.
This header file defines the interface to a principal components analysis designed to be used within ...
Encapsulate the geometry of a wire.
bool consistentClusters(const reco::PrincipalComponents &, const reco::PrincipalComponents &) const
constexpr auto const & left(const_AssnsIter< L, R, D, Dir > const &a, const_AssnsIter< L, R, D, Dir > const &b)
Definition: AssnsIter.h:104
Utility object to perform functions of association.
const float * getEigenValues() const
Definition: Cluster3D.h:228
Encapsulate the construction of a single detector plane.
bool m_enableMonitoring
Data members to follow.
MaybeLogger_< ELseverityLevel::ELsev_success, false > LogDebug
This provides an art tool interface definition for 3D Cluster algorithms.
double m_minEigenToProcess
Don&#39;t look anymore at clusters below this size.
std::unordered_map< const reco::ClusterHit3D *, reco::EdgeList > Hit3DToEdgeMap
Definition: Cluster3D.h:328
Float_t e
Definition: plot.C:34
art framework interface to geometry description
std::list< ClusterParameters > ClusterParametersList
Definition: Cluster3D.h:381
const EigenVectors & getEigenVectors() const
Definition: Cluster3D.h:229
double m_minCosAxisAng
minimum Cos(angle) cut value