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