All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
TwoDSlidingFitSplittingAndSplicingAlgorithm.cc
Go to the documentation of this file.
1 /**
2  * @file larpandoracontent/LArTwoDReco/ClusterSplitting/TwoDSlidingFitSplittingAndSplicingAlgorithm.cc
3  *
4  * @brief Implementation of the two dimensional sliding fit splitting and splicing algorithm class.
5  *
6  * $Log: $
7  */
8 
9 #include "Pandora/AlgorithmHeaders.h"
10 
13 
15 
16 using namespace pandora;
17 
18 namespace lar_content
19 {
20 
21 TwoDSlidingFitSplittingAndSplicingAlgorithm::TwoDSlidingFitSplittingAndSplicingAlgorithm() :
22  m_shortHalfWindowLayers(10),
23  m_longHalfWindowLayers(20),
24  m_minClusterLength(7.5f),
25  m_vetoDisplacement(1.5f),
26  m_runCosmicMode(false)
27 {
28 }
29 
30 //------------------------------------------------------------------------------------------------------------------------------------------
31 
33 {
34  const ClusterList *pClusterList = NULL;
35  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::GetCurrentList(*this, pClusterList));
36 
37  TwoDSlidingFitResultMap branchSlidingFitResultMap, replacementSlidingFitResultMap;
38 
39  unsigned int nIterations(0);
40 
41  while (++nIterations < 100) // Protect against flip-flopping between two answers
42  {
43  // Get ordered list of candidate clusters
44  ClusterVector clusterVector;
45  this->GetListOfCleanClusters(pClusterList, clusterVector);
46 
47  // Calculate sliding fit results for branch clusters (use a soft sliding fit for these)
48  this->BuildSlidingFitResultMap(clusterVector, m_shortHalfWindowLayers, branchSlidingFitResultMap);
49 
50  // Calculate sliding fit results for replacement clusters (use a hard linear fit for these)
51  this->BuildSlidingFitResultMap(clusterVector, m_longHalfWindowLayers, replacementSlidingFitResultMap);
52 
53  // Compile a list of possible splits
54  ClusterExtensionList splitList;
55 
56  if (m_runCosmicMode)
57  {
58  this->BuildClusterExtensionList(clusterVector, branchSlidingFitResultMap, replacementSlidingFitResultMap, splitList);
59  }
60  else
61  {
62  ClusterExtensionList intermediateList;
63  this->BuildClusterExtensionList(clusterVector, branchSlidingFitResultMap, replacementSlidingFitResultMap, intermediateList);
64  this->PruneClusterExtensionList(intermediateList, branchSlidingFitResultMap, replacementSlidingFitResultMap, splitList);
65  }
66 
67  // Run splitting and extension
68  if (STATUS_CODE_SUCCESS != this->RunSplitAndExtension(splitList, branchSlidingFitResultMap, replacementSlidingFitResultMap))
69  break;
70  }
71 
72  return STATUS_CODE_SUCCESS;
73 }
74 
75 //------------------------------------------------------------------------------------------------------------------------------------------
76 
77 void TwoDSlidingFitSplittingAndSplicingAlgorithm::GetListOfCleanClusters(const ClusterList *const pClusterList, ClusterVector &clusterVector) const
78 {
79  for (ClusterList::const_iterator iter = pClusterList->begin(), iterEnd = pClusterList->end(); iter != iterEnd; ++iter)
80  {
81  const Cluster *const pCluster = *iter;
82 
84  continue;
85 
86  clusterVector.push_back(pCluster);
87  }
88 
89  std::sort(clusterVector.begin(), clusterVector.end(), LArClusterHelper::SortByNHits);
90 }
91 
92 //------------------------------------------------------------------------------------------------------------------------------------------
93 
95  const ClusterVector &clusterVector, const unsigned int halfWindowLayers, TwoDSlidingFitResultMap &slidingFitResultMap) const
96 {
97  const float slidingFitPitch(LArGeometryHelper::GetWireZPitch(this->GetPandora()));
98 
99  for (ClusterVector::const_iterator iter = clusterVector.begin(), iterEnd = clusterVector.end(); iter != iterEnd; ++iter)
100  {
101  if (slidingFitResultMap.end() == slidingFitResultMap.find(*iter))
102  {
103  try
104  {
105  const TwoDSlidingFitResult slidingFitResult(*iter, halfWindowLayers, slidingFitPitch);
106 
107  if (!slidingFitResultMap.insert(TwoDSlidingFitResultMap::value_type(*iter, slidingFitResult)).second)
108  throw StatusCodeException(STATUS_CODE_FAILURE);
109  }
110  catch (StatusCodeException &statusCodeException)
111  {
112  if (STATUS_CODE_FAILURE == statusCodeException.GetStatusCode())
113  throw statusCodeException;
114  }
115  }
116  }
117 }
118 
119 //------------------------------------------------------------------------------------------------------------------------------------------
120 
122  const TwoDSlidingFitResultMap &branchSlidingFitResultMap, const TwoDSlidingFitResultMap &replacementSlidingFitResultMap,
123  ClusterExtensionList &clusterExtensionList) const
124 {
125  // Loop over each possible pair of clusters
126  for (ClusterVector::const_iterator iterI = clusterVector.begin(), iterEndI = clusterVector.end(); iterI != iterEndI; ++iterI)
127  {
128  const Cluster *const pClusterI = *iterI;
129 
130  for (ClusterVector::const_iterator iterJ = iterI, iterEndJ = clusterVector.end(); iterJ != iterEndJ; ++iterJ)
131  {
132  const Cluster *const pClusterJ = *iterJ;
133 
134  if (pClusterI == pClusterJ)
135  continue;
136 
137  // Get the branch and replacement sliding fits for this pair of clusters
138  TwoDSlidingFitResultMap::const_iterator iterBranchI = branchSlidingFitResultMap.find(*iterI);
139  TwoDSlidingFitResultMap::const_iterator iterBranchJ = branchSlidingFitResultMap.find(*iterJ);
140 
141  TwoDSlidingFitResultMap::const_iterator iterReplacementI = replacementSlidingFitResultMap.find(*iterI);
142  TwoDSlidingFitResultMap::const_iterator iterReplacementJ = replacementSlidingFitResultMap.find(*iterJ);
143 
144  if (branchSlidingFitResultMap.end() == iterBranchI || branchSlidingFitResultMap.end() == iterBranchJ ||
145  replacementSlidingFitResultMap.end() == iterReplacementI || replacementSlidingFitResultMap.end() == iterReplacementJ)
146  {
147  // TODO May want to raise an exception under certain conditions
148  continue;
149  }
150 
151  const TwoDSlidingFitResult &branchSlidingFitI(iterBranchI->second);
152  const TwoDSlidingFitResult &branchSlidingFitJ(iterBranchJ->second);
153 
154  const TwoDSlidingFitResult &replacementSlidingFitI(iterReplacementI->second);
155  const TwoDSlidingFitResult &replacementSlidingFitJ(iterReplacementJ->second);
156 
157  // Search for a split in clusterI
158  float branchChisqI(0.f);
159  CartesianVector branchSplitPositionI(0.f, 0.f, 0.f);
160  CartesianVector branchSplitDirectionI(0.f, 0.f, 0.f);
161  CartesianVector replacementStartPositionJ(0.f, 0.f, 0.f);
162 
163  try
164  {
165  this->FindBestSplitPosition(branchSlidingFitI, replacementSlidingFitJ, replacementStartPositionJ, branchSplitPositionI, branchSplitDirectionI);
166  branchChisqI = this->CalculateBranchChi2(pClusterI, branchSplitPositionI, branchSplitDirectionI);
167  }
168  catch (StatusCodeException &)
169  {
170  }
171 
172  // Search for a split in clusterJ
173  float branchChisqJ(0.f);
174  CartesianVector branchSplitPositionJ(0.f, 0.f, 0.f);
175  CartesianVector branchSplitDirectionJ(0.f, 0.f, 0.f);
176  CartesianVector replacementStartPositionI(0.f, 0.f, 0.f);
177 
178  try
179  {
180  this->FindBestSplitPosition(branchSlidingFitJ, replacementSlidingFitI, replacementStartPositionI, branchSplitPositionJ, branchSplitDirectionJ);
181  branchChisqJ = this->CalculateBranchChi2(pClusterJ, branchSplitPositionJ, branchSplitDirectionJ);
182  }
183  catch (StatusCodeException &)
184  {
185  }
186 
187  // Re-calculate chi2 values if both clusters have a split
188  if (branchChisqI > 0.f && branchChisqJ > 0.f)
189  {
190  const CartesianVector relativeDirection((branchSplitPositionJ - branchSplitPositionI).GetUnitVector());
191 
192  if (branchSplitDirectionI.GetDotProduct(relativeDirection) > 0.f && branchSplitDirectionJ.GetDotProduct(relativeDirection) < 0.f)
193  {
194  try
195  {
196  const float newBranchChisqI(this->CalculateBranchChi2(pClusterI, branchSplitPositionI, relativeDirection));
197  const float newBranchChisqJ(this->CalculateBranchChi2(pClusterJ, branchSplitPositionJ, relativeDirection * -1.f));
198  branchChisqI = newBranchChisqI;
199  branchChisqJ = newBranchChisqJ;
200  }
201  catch (StatusCodeException &)
202  {
203  }
204  }
205  }
206 
207  // Select the overall best split position
208  if (branchChisqI > branchChisqJ)
209  {
210  clusterExtensionList.push_back(
211  ClusterExtension(pClusterI, pClusterJ, replacementStartPositionJ, branchSplitPositionI, branchSplitDirectionI));
212  }
213 
214  else if (branchChisqJ > branchChisqI)
215  {
216  clusterExtensionList.push_back(
217  ClusterExtension(pClusterJ, pClusterI, replacementStartPositionI, branchSplitPositionJ, branchSplitDirectionJ));
218  }
219  }
220  }
221 }
222 
223 //------------------------------------------------------------------------------------------------------------------------------------------
224 
226  const TwoDSlidingFitResultMap &branchMap, const TwoDSlidingFitResultMap &replacementMap, ClusterExtensionList &outputList) const
227 {
228  ClusterList branchList;
229  for (const auto &mapEntry : branchMap)
230  branchList.push_back(mapEntry.first);
231  branchList.sort(LArClusterHelper::SortByNHits);
232 
233  ClusterList replacementList;
234  for (const auto &mapEntry : replacementMap)
235  replacementList.push_back(mapEntry.first);
236  replacementList.sort(LArClusterHelper::SortByNHits);
237 
238  for (const ClusterExtension &thisSplit : inputList)
239  {
240  const CartesianVector &branchVertex = thisSplit.GetBranchVertex();
241  const CartesianVector &replacementVertex = thisSplit.GetReplacementVertex();
242 
243  const float distanceSquared((branchVertex - replacementVertex).GetMagnitudeSquared());
244  const float vetoDistanceSquared(m_vetoDisplacement * m_vetoDisplacement);
245 
246  bool branchVeto(false), replacementVeto(false);
247 
248  // Veto the merge if another cluster is closer to the replacement vertex
249  for (const Cluster *const pBranchCluster : branchList)
250  {
251  const TwoDSlidingFitResult &slidingFit(branchMap.at(pBranchCluster));
252 
253  if (slidingFit.GetCluster() == thisSplit.GetReplacementCluster() || slidingFit.GetCluster() == thisSplit.GetBranchCluster())
254  continue;
255 
256  const float minDistanceSquared((replacementVertex - slidingFit.GetGlobalMinLayerPosition()).GetMagnitudeSquared());
257  const float maxDistanceSquared((replacementVertex - slidingFit.GetGlobalMaxLayerPosition()).GetMagnitudeSquared());
258 
259  if (std::min(minDistanceSquared, maxDistanceSquared) < std::max(distanceSquared, vetoDistanceSquared))
260  {
261  branchVeto = true;
262  break;
263  }
264  }
265 
266  // Veto the merge if another cluster is closer to the branch vertex
267  for (const Cluster *const pReplacementCluster : replacementList)
268  {
269  const TwoDSlidingFitResult &slidingFit(replacementMap.at(pReplacementCluster));
270 
271  if (slidingFit.GetCluster() == thisSplit.GetReplacementCluster() || slidingFit.GetCluster() == thisSplit.GetBranchCluster())
272  continue;
273 
274  const float minDistanceSquared((branchVertex - slidingFit.GetGlobalMinLayerPosition()).GetMagnitudeSquared());
275  const float maxDistanceSquared((branchVertex - slidingFit.GetGlobalMaxLayerPosition()).GetMagnitudeSquared());
276 
277  if (std::min(minDistanceSquared, maxDistanceSquared) < std::max(distanceSquared, vetoDistanceSquared))
278  {
279  replacementVeto = true;
280  break;
281  }
282  }
283 
284  if (branchVeto || replacementVeto)
285  continue;
286 
287  outputList.push_back(thisSplit);
288  }
289 }
290 
291 //------------------------------------------------------------------------------------------------------------------------------------------
292 
294  const Cluster *const pCluster, const CartesianVector &splitPosition, const CartesianVector &splitDirection) const
295 {
296  CaloHitList principalCaloHitList, branchCaloHitList;
297 
298  this->SplitBranchCluster(pCluster, splitPosition, splitDirection, principalCaloHitList, branchCaloHitList);
299 
300  float totalChi2(0.f);
301  float totalHits(0.f);
302 
303  for (CaloHitList::const_iterator iter = branchCaloHitList.begin(), iterEnd = branchCaloHitList.end(); iter != iterEnd; ++iter)
304  {
305  const CaloHit *const pCaloHit = *iter;
306 
307  const CartesianVector hitPosition(pCaloHit->GetPositionVector());
308  const CartesianVector projectedPosition(splitPosition + splitDirection * splitDirection.GetDotProduct(hitPosition - splitPosition));
309 
310  totalChi2 += (hitPosition - projectedPosition).GetMagnitudeSquared();
311  totalHits += 1.f;
312  }
313 
314  if (totalHits > 0.f)
315  return std::sqrt(totalChi2 / totalHits);
316 
317  throw StatusCodeException(STATUS_CODE_NOT_ALLOWED);
318 }
319 
320 //------------------------------------------------------------------------------------------------------------------------------------------
321 
322 void TwoDSlidingFitSplittingAndSplicingAlgorithm::SplitBranchCluster(const Cluster *const pCluster, const CartesianVector &splitPosition,
323  const CartesianVector &splitDirection, CaloHitList &principalCaloHitList, CaloHitList &branchCaloHitList) const
324 {
325  // Distribute hits in branch cluster between new principal and residual clusters
326  CaloHitList caloHitsToDistribute;
327  pCluster->GetOrderedCaloHitList().FillCaloHitList(caloHitsToDistribute);
328 
329  for (CaloHitList::const_iterator iter = caloHitsToDistribute.begin(), iterEnd = caloHitsToDistribute.end(); iter != iterEnd; ++iter)
330  {
331  const CaloHit *const pCaloHit = *iter;
332 
333  if (splitDirection.GetDotProduct((pCaloHit->GetPositionVector() - splitPosition)) > 0.f)
334  {
335  branchCaloHitList.push_back(pCaloHit);
336  }
337  else
338  {
339  principalCaloHitList.push_back(pCaloHit);
340  }
341  }
342 
343  if (branchCaloHitList.empty())
344  throw StatusCodeException(STATUS_CODE_NOT_ALLOWED);
345 }
346 
347 //------------------------------------------------------------------------------------------------------------------------------------------
348 
350  const ClusterExtensionList &splitList, TwoDSlidingFitResultMap &branchResultMap, TwoDSlidingFitResultMap &replacementResultMap) const
351 {
352  bool foundSplit(false);
353 
354  for (ClusterExtensionList::const_iterator iter = splitList.begin(), iterEnd = splitList.end(); iter != iterEnd; ++iter)
355  {
356  const ClusterExtension &thisSplit = *iter;
357 
358  const Cluster *const pBranchCluster = thisSplit.GetBranchCluster();
359  const Cluster *const pReplacementCluster = thisSplit.GetReplacementCluster();
360  const CartesianVector &branchSplitPosition = thisSplit.GetBranchVertex();
361  const CartesianVector &branchSplitDirection = thisSplit.GetBranchDirection();
362 
363  TwoDSlidingFitResultMap::iterator iterBranch1 = branchResultMap.find(pBranchCluster);
364  TwoDSlidingFitResultMap::iterator iterBranch2 = branchResultMap.find(pReplacementCluster);
365 
366  TwoDSlidingFitResultMap::iterator iterReplacement1 = replacementResultMap.find(pBranchCluster);
367  TwoDSlidingFitResultMap::iterator iterReplacement2 = replacementResultMap.find(pReplacementCluster);
368 
369  if (branchResultMap.end() == iterBranch1 || branchResultMap.end() == iterBranch2 ||
370  replacementResultMap.end() == iterReplacement1 || replacementResultMap.end() == iterReplacement2)
371  continue;
372 
373  PANDORA_RETURN_RESULT_IF(
374  STATUS_CODE_SUCCESS, !=, this->ReplaceBranch(pBranchCluster, pReplacementCluster, branchSplitPosition, branchSplitDirection));
375  branchResultMap.erase(iterBranch1);
376  branchResultMap.erase(iterBranch2);
377 
378  replacementResultMap.erase(iterReplacement1);
379  replacementResultMap.erase(iterReplacement2);
380 
381  foundSplit = true;
382  }
383 
384  if (foundSplit)
385  return STATUS_CODE_SUCCESS;
386 
387  return STATUS_CODE_NOT_FOUND;
388 }
389 
390 //------------------------------------------------------------------------------------------------------------------------------------------
391 
392 StatusCode TwoDSlidingFitSplittingAndSplicingAlgorithm::ReplaceBranch(const Cluster *const pBranchCluster,
393  const Cluster *const pReplacementCluster, const CartesianVector &branchSplitPosition, const CartesianVector &branchSplitDirection) const
394 {
395  ClusterList clusterList;
396  clusterList.push_back(pBranchCluster);
397  clusterList.push_back(pReplacementCluster);
398 
399  std::string clusterListToSaveName, clusterListToDeleteName;
400  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=,
401  PandoraContentApi::InitializeFragmentation(*this, clusterList, clusterListToDeleteName, clusterListToSaveName));
402 
403  // Entire replacement cluster goes into new principal cluster
404  PandoraContentApi::Cluster::Parameters principalParameters;
405  pReplacementCluster->GetOrderedCaloHitList().FillCaloHitList(principalParameters.m_caloHitList);
406 
407  // Distribute hits in branch cluster between new principal and residual clusters
408  PandoraContentApi::Cluster::Parameters residualParameters;
409  this->SplitBranchCluster(
410  pBranchCluster, branchSplitPosition, branchSplitDirection, principalParameters.m_caloHitList, residualParameters.m_caloHitList);
411 
412  const Cluster *pPrincipalCluster(NULL), *pResidualCluster(NULL);
413  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::Cluster::Create(*this, principalParameters, pPrincipalCluster));
414  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::Cluster::Create(*this, residualParameters, pResidualCluster));
415  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::EndFragmentation(*this, clusterListToSaveName, clusterListToDeleteName));
416 
417  return STATUS_CODE_SUCCESS;
418 }
419 
420 //------------------------------------------------------------------------------------------------------------------------------------------
421 
422 StatusCode TwoDSlidingFitSplittingAndSplicingAlgorithm::ReadSettings(const TiXmlHandle xmlHandle)
423 {
424  PANDORA_RETURN_RESULT_IF_AND_IF(
425  STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle, "ShortHalfWindow", m_shortHalfWindowLayers));
426 
427  PANDORA_RETURN_RESULT_IF_AND_IF(
428  STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle, "LongHalfWindow", m_longHalfWindowLayers));
429 
430  PANDORA_RETURN_RESULT_IF_AND_IF(
431  STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle, "MinClusterLength", m_minClusterLength));
432 
433  PANDORA_RETURN_RESULT_IF_AND_IF(
434  STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle, "VetoDisplacement", m_vetoDisplacement));
435 
436  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle, "CosmicMode", m_runCosmicMode));
437 
438  return STATUS_CODE_SUCCESS;
439 }
440 
441 } // namespace lar_content
static bool SortByNHits(const pandora::Cluster *const pLhs, const pandora::Cluster *const pRhs)
Sort clusters by number of hits, then layer span, then inner layer, then position, then pulse-height.
const pandora::Cluster * GetReplacementCluster() const
return the address of the replacement Cluster
void BuildSlidingFitResultMap(const pandora::ClusterVector &clusterVector, const unsigned int halfWindowLayers, TwoDSlidingFitResultMap &slidingFitResultMap) const
Build the map of sliding fit results.
void BuildClusterExtensionList(const pandora::ClusterVector &clusterVector, const TwoDSlidingFitResultMap &branchResultMap, const TwoDSlidingFitResultMap &replacementResultMap, ClusterExtensionList &clusterExtensionList) const
Build a list of candidate splits.
const pandora::Cluster * GetBranchCluster() const
return the address of the branch Cluster
const pandora::CartesianVector & GetBranchVertex() const
return the split position of the branch cluster
static float GetWireZPitch(const pandora::Pandora &pandora, const float maxWirePitchDiscrepancy=0.01)
Return the wire pitch.
const pandora::CartesianVector & GetBranchDirection() const
return the split direction of the branch cluster
Header file for the geometry helper class.
void GetListOfCleanClusters(const pandora::ClusterList *const pClusterList, pandora::ClusterVector &clusterVector) const
Populate cluster vector with subset of cluster list, containing clusters judged to be clean...
pandora::StatusCode RunSplitAndExtension(const ClusterExtensionList &splitList, TwoDSlidingFitResultMap &branchResultMap, TwoDSlidingFitResultMap &replacementResultMap) const
Run the machinary that performs the cluster splitting and extending.
virtual pandora::StatusCode ReadSettings(const pandora::TiXmlHandle xmlHandle)
pandora::StatusCode ReplaceBranch(const pandora::Cluster *const pBranchCluster, const pandora::Cluster *const pReplacementCluster, const pandora::CartesianVector &branchSplitPosition, const pandora::CartesianVector &branchSplitDirection) const
Remove a branch from a cluster and replace it with a second cluster.
std::unordered_map< const pandora::Cluster *, TwoDSlidingFitResult > TwoDSlidingFitResultMap
fhicl::Table< sbnd::crt::CRTDetSimParams > Parameters
required by fuzzyCluster table::sbnd_g4_services gaushitTruthMatch pandora
Definition: reco_sbnd.fcl:182
Header file for the two dimensional sliding fit splitting and splicing algorithm class.
virtual void FindBestSplitPosition(const TwoDSlidingFitResult &branchSlidingFit, const TwoDSlidingFitResult &replacementSlidingFit, pandora::CartesianVector &replacementStartPosition, pandora::CartesianVector &branchSplitPosition, pandora::CartesianVector &branchSplitDirection) const =0
Output the best split positions in branch and replacement clusters.
static float GetLengthSquared(const pandora::Cluster *const pCluster)
Get length squared of cluster.
void SplitBranchCluster(const pandora::Cluster *const pCluster, const pandora::CartesianVector &splitPosition, const pandora::CartesianVector &splitDirection, pandora::CaloHitList &principalCaloHitList, pandora::CaloHitList &branchCaloHitList) const
Separate cluster into the branch hits to be split from the primary cluster.
float CalculateBranchChi2(const pandora::Cluster *const pCluster, const pandora::CartesianVector &splitPosition, const pandora::CartesianVector &splitDirection) const
Calculate RMS deviation of branch hits relative to the split direction.
std::vector< art::Ptr< recob::Cluster > > ClusterVector
void PruneClusterExtensionList(const ClusterExtensionList &inputList, const TwoDSlidingFitResultMap &branchResultMap, const TwoDSlidingFitResultMap &replacementResultMap, ClusterExtensionList &outputList) const
Finalize the list of candidate splits.