LArSoft  v10_06_00
Liquid Argon Software toolkit - https://larsoft.org/
ThreeDMultiReclusteringAlgorithm.cc
Go to the documentation of this file.
1 
12 #include "Pandora/AlgorithmHeaders.h"
13 
16 
18 
19 using namespace pandora;
20 
21 namespace lar_content
22 {
23 
24 ThreeDMultiReclusteringAlgorithm::ThreeDMultiReclusteringAlgorithm() :
25  m_clusterListNames{{TPC_3D, {}}, {TPC_VIEW_U, "ClustersU"}, {TPC_VIEW_V, "ClustersV"}, {TPC_VIEW_W, "ClustersW"}},
26  m_mopUp2DCaloHits{true},
27  m_pFomAlgTool{nullptr}
28 {
29 }
30 
31 //------------------------------------------------------------------------------------------------------------------------------------------
32 
34 {
35  const PfoList *pPfos{nullptr};
36  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_INITIALIZED, !=, PandoraContentApi::GetList(*this, m_pfoListName, pPfos));
37  if (!pPfos || pPfos->empty())
38  return STATUS_CODE_SUCCESS;
39 
40  // Ask the FOM alg tool which pfos to recluster
41  PfoList pfosToRecluster;
42  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, m_pFomAlgTool->GetPfosToRecluster(pPfos, pfosToRecluster));
43  if (pfosToRecluster.empty())
44  return STATUS_CODE_SUCCESS;
45 
46  // Remove clusters from the pfos, keeping track of their original pfo
47  std::map<HitType, ClusterList> viewToFreeClusters = {{TPC_3D, {}}, {TPC_VIEW_U, {}}, {TPC_VIEW_V, {}}, {TPC_VIEW_W, {}}};
48  std::map<const Pfo *const, ClusterList> pfoToFreeClusters;
49  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, FreeClustersFromPfos(pfosToRecluster, viewToFreeClusters, pfoToFreeClusters));
50  if (viewToFreeClusters.at(TPC_3D).empty()) // No 3D clusters to work with, rebuild original pfos as if nothing happened...
51  return RelinkClustersToOriginalPfos(pfoToFreeClusters);
52 
53  // Run reclustering algs over the free 3D clusters to see if there is a superior 3D clustering
54  // NOTE Temporarily replacing the current list doesn't seem to work
55  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::ReplaceCurrentList<Cluster>(*this, m_clusterListNames.at(TPC_3D)));
56  std::string originalClusterListName;
57  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=,
58  PandoraContentApi::InitializeReclustering(*this, TrackList(), viewToFreeClusters.at(TPC_3D), originalClusterListName));
59 
60  float bestReclusterFom{std::numeric_limits<float>::lowest()};
61  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, m_pFomAlgTool->CalcClusteringFom(viewToFreeClusters.at(TPC_3D), bestReclusterFom));
62  std::string bestReclusterListName{originalClusterListName};
63  ClusterList bestReclusterList;
64 
65  for (const std::string &clusteringAlg : m_clusteringAlgs)
66  {
67  std::string reclusterListName;
68  const ClusterList *pReclusterList{nullptr};
69  PANDORA_RETURN_RESULT_IF(
70  STATUS_CODE_SUCCESS, !=, PandoraContentApi::RunClusteringAlgorithm(*this, clusteringAlg, pReclusterList, reclusterListName));
71  if (!pReclusterList || pReclusterList->empty())
72  continue;
73 
74  float reclusterFom{std::numeric_limits<float>::lowest()};
75  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, m_pFomAlgTool->CalcClusteringFom(*pReclusterList, reclusterFom));
76  if (reclusterFom > bestReclusterFom)
77  {
78  bestReclusterFom = reclusterFom;
79  bestReclusterListName = reclusterListName;
80  bestReclusterList = *pReclusterList;
81  }
82  }
83 
84  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::TemporarilyReplaceCurrentList<Cluster>(*this, bestReclusterListName));
85  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::EndReclustering(*this, bestReclusterListName));
86 
87  if (bestReclusterListName == originalClusterListName) // No clustering alg has better 3D clusters, rebuild original pfos as if nothing happened...
88  {
89  return RelinkClustersToOriginalPfos(pfoToFreeClusters);
90  }
91  else
92  {
93  // Delete the original pfos in preparation for making new ones
94  // ATTN This will break any existing particle hierarchy
95  for (const Pfo *const pPfo : pfosToRecluster)
96  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::Delete(*this, pPfo, m_pfoListName));
97  }
98 
99  // The 3D clusters are reclustered, now need to manually recluster the 2D clusters to be consistent with the 3D reclustering
100  // First, remove the original 2D clusters
101  std::map<HitType, CaloHitList> viewToFreeCaloHits2D = {{TPC_VIEW_U, {}}, {TPC_VIEW_V, {}}, {TPC_VIEW_W, {}}};
102  for (const auto &[view, freeClusters] : viewToFreeClusters)
103  {
104  if (view == TPC_3D)
105  continue;
106  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, FreeCaloHitsFromClusters(freeClusters, view, viewToFreeCaloHits2D.at(view)));
107  }
108 
109  // Now make new 2D clusters following the new 3D clusters, and make new corresponding pfos
110  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, BuildNewPfos(bestReclusterList, viewToFreeCaloHits2D));
111 
112  return STATUS_CODE_SUCCESS;
113 }
114 
115 //------------------------------------------------------------------------------------------------------------------------------------------
116 
117 StatusCode ThreeDMultiReclusteringAlgorithm::FreeClustersFromPfos(const PfoList &pfos, std::map<HitType, ClusterList> &viewToFreeClusters,
118  std::map<const Pfo *const, ClusterList> &pfoToFreeClusters) const
119 {
120  for (const Pfo *const pPfo : pfos)
121  {
122  ClusterList thisPfoClusters;
123  for (auto &[view, freeClusters] : viewToFreeClusters)
124  {
125  ClusterList clusters;
126  LArPfoHelper::GetClusters(pPfo, view, clusters);
127  for (const Cluster *const pCluster : clusters)
128  {
129  freeClusters.emplace_back(pCluster);
130  thisPfoClusters.emplace_back(pCluster);
131  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::RemoveFromPfo(*this, pPfo, pCluster));
132  }
133  }
134  pfoToFreeClusters.insert(std::make_pair(pPfo, thisPfoClusters));
135  }
136 
137  return STATUS_CODE_SUCCESS;
138 }
139 
140 //------------------------------------------------------------------------------------------------------------------------------------------
141 
142 StatusCode ThreeDMultiReclusteringAlgorithm::RelinkClustersToOriginalPfos(std::map<const Pfo *const, ClusterList> &pfoToClusters) const
143 {
144  for (const auto &[pPfo, clusters] : pfoToClusters)
145  for (const Cluster *const pCluster : clusters)
146  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::AddToPfo(*this, pPfo, pCluster));
147 
148  return STATUS_CODE_SUCCESS;
149 }
150 
151 //------------------------------------------------------------------------------------------------------------------------------------------
152 
153 StatusCode ThreeDMultiReclusteringAlgorithm::FreeCaloHitsFromClusters(const ClusterList &clusters, const HitType &view, CaloHitList &freeCaloHits) const
154 {
155  std::string clusterListName{m_clusterListNames.at(view)};
156  for (const Cluster *const pCluster : clusters)
157  {
158  CaloHitList caloHits;
159  pCluster->GetOrderedCaloHitList().FillCaloHitList(caloHits);
160  freeCaloHits.insert(freeCaloHits.end(), caloHits.begin(), caloHits.end());
161  freeCaloHits.insert(freeCaloHits.end(), pCluster->GetIsolatedCaloHitList().begin(), pCluster->GetIsolatedCaloHitList().end());
162  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::Delete(*this, pCluster, clusterListName));
163  }
164 
165  return STATUS_CODE_SUCCESS;
166 }
167 
168 //------------------------------------------------------------------------------------------------------------------------------------------
169 
170 StatusCode ThreeDMultiReclusteringAlgorithm::BuildNewPfos(const ClusterList &clusters, std::map<HitType, CaloHitList> &viewToFreeCaloHits2D) const
171 {
172  // Cluster any 2D hits directly associated with 3D hits in the same way as the 3D clusters
173  std::map<HitType, ClusterList> viewToNewClusters2D = {{TPC_VIEW_U, {}}, {TPC_VIEW_V, {}}, {TPC_VIEW_W, {}}};
174  std::vector<ClusterList> newPfoClusters;
175  for (const Cluster *const pCluster3D : clusters)
176  {
177  ClusterList newClusters;
178  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, Build2DClustersFrom3D(pCluster3D, viewToFreeCaloHits2D, newClusters));
179  for (const Cluster *const pCluster : newClusters)
180  viewToNewClusters2D.at(LArClusterHelper::GetClusterHitType(pCluster)).emplace_back(pCluster);
181  newClusters.emplace_back(pCluster3D);
182  newPfoClusters.emplace_back(newClusters);
183  }
184 
185  // Put any remaining 2D hits into the nearest new 2D cluster made in the last step
186  // NOTE Hits added to 2D clusters this way are added as isolated
187  if (m_mopUp2DCaloHits)
188  {
189  for (const auto &[view, caloHits2D] : viewToFreeCaloHits2D)
190  {
191  // If no 2D clusters for a view could be made in the previous step (happens rarely when no 3D hits get made from a cluster view),
192  // just mop up the remaining 2D hits into the old 2D clusters
193  if (viewToNewClusters2D.at(view).empty())
194  {
195  std::string clusterListName{m_clusterListNames.at(view)};
196  const ClusterList *pOldClusters2D{nullptr};
197  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::GetList(*this, clusterListName, pOldClusters2D));
198  // This means no hits in this view made any 3D hits, this happens very rarely for events with few hits.
199  // In this case, just leave these hits unclustered. Could also consider putting them in their own isolated hit-only cluster?
200  if (pOldClusters2D->empty())
201  continue;
202  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, MopUpCaloHits(caloHits2D, *pOldClusters2D, true));
203  continue;
204  }
205  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, MopUpCaloHits(caloHits2D, viewToNewClusters2D.at(view), true));
206  }
207  }
208 
209  // Create the new Pfos
210  // ATTN New pfos will: have no vertex, not be characterised as track/shower, not be part of any particle hierarchy.
211  // If desired, these these things will need to be added in succeeding algs.
212  const PfoList *pNewPfoList{nullptr};
213  std::string tempPfoListName;
214  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::CreateTemporaryListAndSetCurrent(*this, pNewPfoList, tempPfoListName));
215  for (const ClusterList &pfoClusters : newPfoClusters)
216  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, CreatePfoFromClusters(pfoClusters));
217  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::SaveList<Pfo>(*this, tempPfoListName, m_pfoListName));
218 
219  return STATUS_CODE_SUCCESS;
220 }
221 
222 //------------------------------------------------------------------------------------------------------------------------------------------
223 
225  const Cluster *const pCluster3D, std::map<HitType, CaloHitList> &viewToFreeCaloHits2D, ClusterList &newClusters2D) const
226 {
227  std::map<HitType, PandoraContentApi::Cluster::Parameters> viewToParamsCluster2D = {{TPC_VIEW_U, {}}, {TPC_VIEW_V, {}}, {TPC_VIEW_W, {}}};
228 
229  CaloHitList cluster3DCaloHits3D;
230  pCluster3D->GetOrderedCaloHitList().FillCaloHitList(cluster3DCaloHits3D);
231  for (const CaloHit *const pCaloHit3D : cluster3DCaloHits3D)
232  {
233  const CaloHit *const pCaloHit3DParent2D{static_cast<const CaloHit *>(pCaloHit3D->GetParentAddress())};
234  const HitType view{pCaloHit3DParent2D->GetHitType()};
235  if (std::find(viewToFreeCaloHits2D.at(view).begin(), viewToFreeCaloHits2D.at(view).end(), pCaloHit3DParent2D) ==
236  viewToFreeCaloHits2D.at(view).end())
237  return STATUS_CODE_FAILURE;
238  viewToParamsCluster2D.at(view).m_caloHitList.push_back(pCaloHit3DParent2D);
239  viewToFreeCaloHits2D.at(view).remove(pCaloHit3DParent2D);
240  }
241 
242  CaloHitList cluster3DIsoCaloHits3D{pCluster3D->GetIsolatedCaloHitList()};
243  for (const CaloHit *const pCaloHit3D : cluster3DIsoCaloHits3D)
244  {
245  const CaloHit *const pCaloHit3DParent2D{static_cast<const CaloHit *>(pCaloHit3D->GetParentAddress())};
246  const HitType view{pCaloHit3DParent2D->GetHitType()};
247  if (std::find(viewToFreeCaloHits2D.at(view).begin(), viewToFreeCaloHits2D.at(view).end(), pCaloHit3DParent2D) ==
248  viewToFreeCaloHits2D.at(view).end())
249  return STATUS_CODE_FAILURE;
250  viewToParamsCluster2D.at(view).m_isolatedCaloHitList.push_back(pCaloHit3DParent2D);
251  viewToFreeCaloHits2D.at(view).remove(pCaloHit3DParent2D);
252  }
253 
254  for (const auto &[view, params] : viewToParamsCluster2D)
255  {
256  if (params.m_caloHitList.empty() && params.m_isolatedCaloHitList.empty())
257  continue;
258 
259  std::string tempListName;
260  const ClusterList *pTempClusterList{nullptr};
261  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::CreateTemporaryListAndSetCurrent(*this, pTempClusterList, tempListName));
262  const Cluster *pNewCluster2D{nullptr};
263  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::Cluster::Create(*this, params, pNewCluster2D));
264  std::string cluster2DListName{m_clusterListNames.at(view)};
265  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::SaveList<Cluster>(*this, tempListName, cluster2DListName));
266  newClusters2D.push_back(pNewCluster2D);
267  }
268 
269  return STATUS_CODE_SUCCESS;
270 }
271 
272 //------------------------------------------------------------------------------------------------------------------------------------------
273 
274 StatusCode ThreeDMultiReclusteringAlgorithm::MopUpCaloHits(const CaloHitList &caloHits, const ClusterList &clusters, bool addAsIso) const
275 {
276  for (const CaloHit *const pCaloHit : caloHits)
277  {
278  const Cluster *pNearestCluster{nullptr};
279  double minimumDistance{std::numeric_limits<float>::max()};
280  for (const Cluster *const pCluster : clusters)
281  {
282  double dist = LArClusterHelper::GetClosestDistance(pCaloHit->GetPositionVector(), pCluster);
283  if (dist < minimumDistance)
284  {
285  minimumDistance = dist;
286  pNearestCluster = pCluster;
287  }
288  }
289  if (!pNearestCluster)
290  return STATUS_CODE_FAILURE;
291 
292  if (addAsIso)
293  {
294  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::AddIsolatedToCluster(*this, pNearestCluster, pCaloHit));
295  }
296  else
297  {
298  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::AddToCluster(*this, pNearestCluster, pCaloHit));
299  }
300  }
301 
302  return STATUS_CODE_SUCCESS;
303 }
304 
305 //------------------------------------------------------------------------------------------------------------------------------------------
306 
307 StatusCode ThreeDMultiReclusteringAlgorithm::CreatePfoFromClusters(const ClusterList &clusters) const
308 {
309  PandoraContentApi::ParticleFlowObject::Parameters params;
310  params.m_clusterList = clusters;
311  params.m_particleId = MU_MINUS; // Arbitrary choice to mark all new pfos as track-like
312  params.m_charge = PdgTable::GetParticleCharge(params.m_particleId.Get());
313  params.m_mass = PdgTable::GetParticleMass(params.m_particleId.Get());
314  params.m_energy = 0.f;
315  params.m_momentum = CartesianVector(0.f, 0.f, 0.f);
316  const Pfo *pPfo{nullptr};
317  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::ParticleFlowObject::Create(*this, params, pPfo));
318 
319  return STATUS_CODE_SUCCESS;
320 }
321 
322 //------------------------------------------------------------------------------------------------------------------------------------------
323 
324 StatusCode ThreeDMultiReclusteringAlgorithm::ReadSettings(const TiXmlHandle xmlHandle)
325 {
326  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, XmlHelper::ReadValue(xmlHandle, "PfoListName", m_pfoListName));
327  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, XmlHelper::ReadValue(xmlHandle, "Cluster3DListName", m_clusterListNames.at(TPC_3D)));
328  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=,
329  XmlHelper::ReadValue(xmlHandle, "ClusterUListName", m_clusterListNames.at(TPC_VIEW_U)));
330  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=,
331  XmlHelper::ReadValue(xmlHandle, "ClusterVListName", m_clusterListNames.at(TPC_VIEW_V)));
332  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=,
333  XmlHelper::ReadValue(xmlHandle, "ClusterWListName", m_clusterListNames.at(TPC_VIEW_W)));
334 
335  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle, "MopUp2DCaloHits", m_mopUp2DCaloHits));
336 
337  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, XmlHelper::ProcessAlgorithmList(*this, xmlHandle, "ClusteringAlgorithms", m_clusteringAlgs));
338 
339  AlgorithmTool *pAlgTool{nullptr};
340  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, XmlHelper::ProcessAlgorithmTool(*this, xmlHandle, "FomAlgorithmTool", pAlgTool));
341  m_pFomAlgTool = dynamic_cast<ThreeDReclusteringFigureOfMeritBaseTool *>(pAlgTool);
342  if (!m_pFomAlgTool)
343  return STATUS_CODE_INVALID_PARAMETER;
344 
345  return STATUS_CODE_SUCCESS;
346 }
347 
348 } // namespace lar_content
Header file for the pfo helper class.
ThreeDReclusteringFigureOfMeritBaseTool * m_pFomAlgTool
The address of the figure of merit algorithm tool to use.
pandora::StatusCode ReadSettings(const pandora::TiXmlHandle xmlHandle)
pandora::StatusCode FreeCaloHitsFromClusters(const pandora::ClusterList &clusters, const pandora::HitType &view, pandora::CaloHitList &freeCaloHits) const
Delete clusters of a single hit type and store the associated hits in a list.
static void GetClusters(const pandora::PfoList &pfoList, const pandora::HitType &hitType, pandora::ClusterList &clusterList)
Get a list of clusters of a particular hit type from a list of pfos.
pandora::StatusCode RelinkClustersToOriginalPfos(std::map< const pandora::Pfo *const, pandora::ClusterList > &pfoToClusters) const
Remake the original pfos by adding back their removed clusters.
pandora::StatusCode CreatePfoFromClusters(const pandora::ClusterList &clusters) const
Create a new pfo from a list of clusters.
std::string m_pfoListName
Name of list of pfos to consider for reclustering.
static pandora::HitType GetClusterHitType(const pandora::Cluster *const pCluster)
Get the hit type associated with a two dimensional cluster.
TFile f
Definition: plotHisto.C:6
Header file for the cluster helper class.
Header file for the reclustering algorithm class. Tries multiple 3D clustering algorithms and reclust...
pandora::StatusCode Build2DClustersFrom3D(const pandora::Cluster *const pCluster3D, std::map< pandora::HitType, pandora::CaloHitList > &viewToFreeCaloHits2D, pandora::ClusterList &newClusters2D) const
Create 2D clusters following a 3D cluster.
virtual pandora::StatusCode CalcClusteringFom(const pandora::ClusterList &clusters, float &fom)=0
Calculate a measure of the goodness of a clustering.
bool m_mopUp2DCaloHits
Add hits from reclustered 2D clusters without an associated 3D hit to the nearest new 2D cluster in t...
virtual pandora::StatusCode GetPfosToRecluster(const pandora::PfoList *pPfos, pandora::PfoList &pfosToRecluster)=0
Identify pfos for which an attempt at 3D reclustering should be made.
pandora::StatusCode MopUpCaloHits(const pandora::CaloHitList &caloHits, const pandora::ClusterList &clusters, bool addAsIso) const
Add hits to their nearest cluster.
std::map< pandora::HitType, std::string > m_clusterListNames
Map of list names for 3D clusters that comprise the pfos and 2D U, V, W clusters that may need reclus...
double dist(const TReal *x, const TReal *y, const unsigned int dimension)
HitType
Definition: HitType.h:12
std::vector< std::string > m_clusteringAlgs
The ordered list of clustering algorithms to use.
pandora::StatusCode BuildNewPfos(const pandora::ClusterList &clusters3D, std::map< pandora::HitType, pandora::CaloHitList > &viewToFreeCaloHits2D) const
Create new pfos from the reclustered 3D clusters and original 2D hits. The original 2D hits are put i...
pandora::StatusCode FreeClustersFromPfos(const pandora::PfoList &pfos, std::map< pandora::HitType, pandora::ClusterList > &viewToFreeClusters, std::map< const pandora::Pfo *const, pandora::ClusterList > &pfoToFreeClusters) const
Remove clusters from the pfos and store them them in maps.
static float GetClosestDistance(const pandora::ClusterList &clusterList1, const pandora::ClusterList &clusterList2)
Get closest distance between clusters in a pair of cluster lists.