LArSoft  v10_06_00
Liquid Argon Software toolkit - https://larsoft.org/
EventClusterValidationAlgorithm.cc
Go to the documentation of this file.
1 
9 #include "Pandora/AlgorithmHeaders.h"
10 
12 
15 
16 #include <numeric>
17 
18 using namespace pandora;
19 
20 namespace lar_content
21 {
22 
23 EventClusterValidationAlgorithm::CaloHitParents::CaloHitParents() :
24  m_pMainMC{nullptr},
25  m_pCluster{nullptr}
26 {
27 }
28 
29 //------------------------------------------------------------------------------------------------------------------------------------------
30 
32  m_nRecoHits{std::vector<int>{}},
33  m_purities{std::vector<float>{}},
34  m_completenesses{std::vector<float>{}},
35  m_nHits{0},
36  m_nClusters{0},
37  m_nMainMCs{0}
38 {
39 }
40 
41 //------------------------------------------------------------------------------------------------------------------------------------------
42 
44  m_eventNumber{0},
45  m_caloHitListName{"CaloHitList2D"},
47 {
48 }
49 
50 //------------------------------------------------------------------------------------------------------------------------------------------
51 
53 {
54  PANDORA_MONITORING_API(SaveTree(this->GetPandora(), m_treeName, m_fileName, "UPDATE"));
55 
56  PANDORA_MONITORING_API(SetTreeVariable(this->GetPandora(), m_treeName + "_meta", "min_mc_hits_per_view", m_minMCHitsPerView));
57  PANDORA_MONITORING_API(FillTree(this->GetPandora(), m_treeName + "_meta"));
58  PANDORA_MONITORING_API(SaveTree(this->GetPandora(), m_treeName + "_meta", m_fileName, "UPDATE"));
59 }
60 
61 //------------------------------------------------------------------------------------------------------------------------------------------
62 
64 {
65  m_eventNumber++;
66 
67  // Gather hits by view
68  const CaloHitList *pFullCaloHitList{nullptr};
69  PANDORA_THROW_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::GetList(*this, m_caloHitListName, pFullCaloHitList));
70  std::map<HitType, CaloHitList> viewToCaloHits;
71  for (const CaloHit *const pCaloHit : *pFullCaloHitList)
72  viewToCaloHits[pCaloHit->GetHitType()].emplace_back(pCaloHit);
73 
74  // Gather clusters by view
75  std::map<HitType, ClusterList> viewToClusters;
76  for (std::string listName : m_clusterListNames)
77  {
78  const ClusterList *pClusterList{nullptr};
79  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, PandoraContentApi::GetList(*this, listName, pClusterList));
80  for (const Cluster *const pCluster : *pClusterList)
81  {
82  const HitType view{LArClusterHelper::GetClusterHitType(pCluster)};
83  viewToClusters[view].emplace_back(pCluster);
84  }
85  }
86 
87  const std::vector<ValidationType> valTypes{ValidationType::ALL, ValidationType::SHOWER, ValidationType::TRACK};
88  for (const auto &[view, caloHits] : viewToCaloHits)
89  {
90  const ClusterList &clusters{viewToClusters[view]};
91 
92  // For each hit, get the main MCParticle and the cluster it belongs to (if the truth matching is not missing)
93  // NOTE Using hitParents map to track which hits are being considered in metric calculations
94  std::map<const CaloHit *const, CaloHitParents> hitParents;
95  GetHitParents(caloHits, clusters, hitParents);
96 
97  // Drop any hits with a main MC particle with insufficient activity
98  if (m_minMCHitsPerView > 0)
99  ApplyMCParticleMinSumHits(hitParents);
100 
101  for (const ValidationType valType : valTypes)
102  {
103  // Apply the track/shower/all criteria
104  std::map<const CaloHit *const, CaloHitParents> hitParentsValid{ApplyPDGCut(hitParents, valType)};
105 
106  ClusterMetrics metrics;
107  GetMetrics(hitParentsValid, metrics);
108 
109  float adjustedRandI{CalcRandIndex(hitParentsValid)};
110 
111  std::string branchPrefix;
112  if (valType == ValidationType::ALL)
113  branchPrefix = "all_";
114  else if (valType == ValidationType::SHOWER)
115  branchPrefix = "shower_";
116  else if (valType == ValidationType::TRACK)
117  branchPrefix = "track_";
118  SetBranches(metrics, adjustedRandI, branchPrefix);
119  }
120 
121  PANDORA_MONITORING_API(SetTreeVariable(this->GetPandora(), m_treeName, "event", m_eventNumber - 1));
122  PANDORA_MONITORING_API(SetTreeVariable(this->GetPandora(), m_treeName, "view", static_cast<int>(view)));
123  PANDORA_MONITORING_API(FillTree(this->GetPandora(), m_treeName));
124  }
125 
126  return STATUS_CODE_SUCCESS;
127 }
128 
129 //------------------------------------------------------------------------------------------------------------------------------------------
130 
132  const CaloHitList &caloHits, const ClusterList &clusters, std::map<const CaloHit *const, CaloHitParents> &hitParents) const
133 {
134  for (const CaloHit *const pCaloHit : caloHits)
135  {
136  const MCParticle *pMainMC{nullptr};
137  const MCParticleWeightMap &weightMap{pCaloHit->GetMCParticleWeightMap()};
138  float maxWeight{std::numeric_limits<float>::lowest()};
139  for (const auto &[pMC, weight] : weightMap)
140  {
141  if (weight > maxWeight)
142  {
143  pMainMC = pMC;
144  maxWeight = weight;
145  }
146  }
147  if (pMainMC)
148  {
149  hitParents[pCaloHit] = CaloHitParents();
150  hitParents[pCaloHit].m_pMainMC = pMainMC;
151  }
152  }
153 
154  for (const Cluster *const pCluster : clusters)
155  {
156  const CaloHitList &isolatedHits{pCluster->GetIsolatedCaloHitList()};
157  CaloHitList clusterCaloHits;
158  pCluster->GetOrderedCaloHitList().FillCaloHitList(clusterCaloHits);
159  clusterCaloHits.insert(clusterCaloHits.end(), isolatedHits.begin(), isolatedHits.end());
160  for (const CaloHit *const pCaloHit : clusterCaloHits)
161  {
162  // Ignoring the calo hit if truth matching is missing
163  if (hitParents.find(pCaloHit) == hitParents.end())
164  continue;
165  hitParents[pCaloHit].m_pCluster = pCluster;
166  }
167  }
168 }
169 
170 //------------------------------------------------------------------------------------------------------------------------------------------
171 
172 void EventClusterValidationAlgorithm::ApplyMCParticleMinSumHits(std::map<const CaloHit *const, CaloHitParents> &hitParents) const
173 {
174  std::map<const MCParticle *const, int> mcSumHits;
175  for (const auto &[pCaloHit, parents] : hitParents)
176  {
177  const MCParticle *const pMainMC{parents.m_pMainMC};
178  if (mcSumHits.find(pMainMC) == mcSumHits.end())
179  mcSumHits[pMainMC] = 0;
180  mcSumHits.at(pMainMC)++;
181  }
182 
183  for (auto it = hitParents.begin(); it != hitParents.end();)
184  {
185  if (mcSumHits.at(it->second.m_pMainMC) < m_minMCHitsPerView)
186  it = hitParents.erase(it);
187  else
188  it++;
189  }
190 }
191 
192 //------------------------------------------------------------------------------------------------------------------------------------------
193 
194 std::map<const CaloHit *const, EventClusterValidationAlgorithm::CaloHitParents> EventClusterValidationAlgorithm::ApplyPDGCut(
195  std::map<const CaloHit *const, CaloHitParents> &hitParents, const ValidationType &valType) const
196 {
197  std::map<const CaloHit *const, CaloHitParents> hitParentsValid;
198  for (const auto &[pCaloHit, parents] : hitParents)
199  {
200  const MCParticle *const pMainMC{parents.m_pMainMC};
201  if (valType != ValidationType::ALL)
202  {
203  const int pdg{std::abs(pMainMC->GetParticleId())};
204  if ((valType == ValidationType::SHOWER && (pdg != PHOTON && pdg != E_MINUS)) ||
205  (valType == ValidationType::TRACK && (pdg == PHOTON || pdg == E_MINUS)))
206  continue;
207  }
208  hitParentsValid[pCaloHit] = parents;
209  }
210 
211  return hitParentsValid;
212 }
213 
214 //------------------------------------------------------------------------------------------------------------------------------------------
215 
216 void EventClusterValidationAlgorithm::GetMetrics(const std::map<const CaloHit *const, CaloHitParents> &hitParents, ClusterMetrics &metrics) const
217 {
218  // Adapted from Andy's code for calculating cluster purity and completeness (ClusterValidationAlgorithm)
219  std::set<const Cluster *> validClusters;
220  std::set<const MCParticle *> validMCParticles;
221  for (const auto &[pCaloHit, parents] : hitParents)
222  {
223  validClusters.insert(parents.m_pCluster);
224  validMCParticles.insert(parents.m_pMainMC);
225  }
226  metrics.m_nHits = hitParents.size();
227  metrics.m_nClusters = validClusters.size();
228  metrics.m_nMainMCs = validMCParticles.size();
229 
230  for (const Cluster *const pCluster : validClusters)
231  {
232  CaloHitList clusterCaloHits;
233  if (pCluster)
234  {
235  const CaloHitList &isolatedHits{pCluster->GetIsolatedCaloHitList()};
236  pCluster->GetOrderedCaloHitList().FillCaloHitList(clusterCaloHits);
237  clusterCaloHits.insert(clusterCaloHits.end(), isolatedHits.begin(), isolatedHits.end());
238  }
239  else // The hit has a null cluster ie. it is unclustered, treat all such hits as being clustered together
240  {
241  for (const auto &[pCaloHit, parents] : hitParents)
242  {
243  if (!parents.m_pCluster)
244  clusterCaloHits.emplace_back(pCaloHit);
245  }
246  }
247 
248  std::map<const MCParticle *const, int> mainMCParticleHits;
249  float totalHits{0};
250  for (const CaloHit *const pCaloHit : clusterCaloHits)
251  {
252  if (hitParents.find(pCaloHit) == hitParents.end())
253  continue;
254 
255  const MCParticle *const pMC = hitParents.at(pCaloHit).m_pMainMC;
256  if (mainMCParticleHits.find(pMC) == mainMCParticleHits.end())
257  mainMCParticleHits[pMC] = 0;
258  mainMCParticleHits.at(pMC)++;
259  totalHits++;
260  }
261  if (totalHits == 0) // Something went wrong and won't be able to get metrics from this cluster
262  continue;
263 
264  // Determine which MC particle contributes the most weight across the cluster
265  const MCParticle *pMainMC{nullptr};
266  int maxHits{0};
267  for (const auto &[pMC, nHits] : mainMCParticleHits)
268  {
269  if (nHits > maxHits)
270  {
271  pMainMC = pMC;
272  maxHits = nHits;
273  }
274  }
275  metrics.m_purities.emplace_back(maxHits / totalHits);
276  metrics.m_nRecoHits.emplace_back(totalHits);
277 
278  // Calculate cluster completeness
279  int nTotalMainMCHits{0};
280  for (const auto &[pCaloHit, parents] : hitParents)
281  {
282  if (pMainMC == parents.m_pMainMC)
283  nTotalMainMCHits++;
284  }
285  metrics.m_completenesses.emplace_back(maxHits / static_cast<float>(nTotalMainMCHits));
286  }
287 }
288 
289 //------------------------------------------------------------------------------------------------------------------------------------------
290 
291 float EventClusterValidationAlgorithm::CalcRandIndex(std::map<const CaloHit *const, CaloHitParents> &hitParents) const
292 {
293  // Fill contingency table
294  ContingencyTable cTable;
295  for (const auto &[pCaloHit, parents] : hitParents)
296  {
297  const MCParticle *const pMC = parents.m_pMainMC;
298  const Cluster *const pCluster = parents.m_pCluster;
299 
300  if (cTable.find(pCluster) == cTable.end() || cTable.at(pCluster).find(pMC) == cTable.at(pCluster).end())
301  cTable[pCluster][pMC] = 0;
302  cTable.at(pCluster).at(pMC)++;
303  }
304 
305  return LArMonitoringHelper::CalcRandIndex(cTable);
306 }
307 
308 //------------------------------------------------------------------------------------------------------------------------------------------
309 
311  [[maybe_unused]] ClusterMetrics &metrics, [[maybe_unused]] float randIndex, [[maybe_unused]] std::string branchPrefix) const
312 {
313  PANDORA_MONITORING_API(SetTreeVariable(this->GetPandora(), m_treeName, branchPrefix + "n_hits", metrics.m_nHits));
314  PANDORA_MONITORING_API(SetTreeVariable(this->GetPandora(), m_treeName, branchPrefix + "n_clusters", metrics.m_nClusters));
315  PANDORA_MONITORING_API(SetTreeVariable(this->GetPandora(), m_treeName, branchPrefix + "n_mainMCs", metrics.m_nMainMCs));
316 #ifdef MONITORING
317  double meanPurity{-1.0}, meanCompleteness{-1.0}, meanNRecoHits{-1.0};
318  if (!metrics.m_purities.empty() && !metrics.m_completenesses.empty() && !metrics.m_nRecoHits.empty())
319  {
320  meanPurity = std::accumulate(metrics.m_purities.begin(), metrics.m_purities.end(), 0.0) / metrics.m_purities.size();
321  meanCompleteness = std::accumulate(metrics.m_completenesses.begin(), metrics.m_completenesses.end(), 0.0) / metrics.m_completenesses.size();
322  meanNRecoHits = std::accumulate(metrics.m_nRecoHits.begin(), metrics.m_nRecoHits.end(), 0.0) / metrics.m_nRecoHits.size();
323  }
324 #endif
325  PANDORA_MONITORING_API(SetTreeVariable(this->GetPandora(), m_treeName, branchPrefix + "mean_purity", meanPurity));
326  PANDORA_MONITORING_API(SetTreeVariable(this->GetPandora(), m_treeName, branchPrefix + "mean_completeness", meanCompleteness));
327  PANDORA_MONITORING_API(SetTreeVariable(this->GetPandora(), m_treeName, branchPrefix + "mean_n_reco_hits", meanNRecoHits));
328  PANDORA_MONITORING_API(SetTreeVariable(this->GetPandora(), m_treeName, branchPrefix + "adjusted_rand_idx", randIndex));
329 }
330 
331 //------------------------------------------------------------------------------------------------------------------------------------------
332 
333 StatusCode EventClusterValidationAlgorithm::ReadSettings(const TiXmlHandle xmlHandle)
334 {
335  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, XmlHelper::ReadValue(xmlHandle, "FileName", m_fileName));
336  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, XmlHelper::ReadValue(xmlHandle, "TreeName", m_treeName));
337  PANDORA_RETURN_RESULT_IF_AND_IF(STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle, "CaloHitListName", m_caloHitListName));
338  PANDORA_RETURN_RESULT_IF(STATUS_CODE_SUCCESS, !=, XmlHelper::ReadVectorOfValues(xmlHandle, "ClusterListNames", m_clusterListNames));
339  PANDORA_RETURN_RESULT_IF_AND_IF(
340  STATUS_CODE_SUCCESS, STATUS_CODE_NOT_FOUND, !=, XmlHelper::ReadValue(xmlHandle, "MinMCHitsPerView", m_minMCHitsPerView));
341 
342  return STATUS_CODE_SUCCESS;
343 }
344 
345 } // namespace lar_content
pandora::StatusCode ReadSettings(const pandora::TiXmlHandle xmlHandle)
int m_eventNumber
To track the current event number.
std::map< const pandora::Cluster *const, std::map< const pandora::MCParticle *const, int > > ContingencyTable
float CalcRandIndex(std::map< const pandora::CaloHit *const, CaloHitParents > &hitParents) const
Calculate Rand Index for the clusters with the clusters of true MCParticles. (Ref. for Adjusted Rand Index: https://link.springer.com/article/10.1007/BF01908075)
constexpr auto abs(T v)
Returns the absolute value of the argument.
void GetHitParents(const pandora::CaloHitList &caloHits, const pandora::ClusterList &clusters, std::map< const pandora::CaloHit *const, CaloHitParents > &hitParents) const
Find the cluster and MCParticle each hit belongs to.
static pandora::HitType GetClusterHitType(const pandora::Cluster *const pCluster)
Get the hit type associated with a two dimensional cluster.
Header file for the lar monitoring helper helper class.
std::string m_caloHitListName
The name of the hit list containing all 2D hits.
Header file for the cluster helper class.
Header file of the event-level cluster validation.
void ApplyMCParticleMinSumHits(std::map< const pandora::CaloHit *const, CaloHitParents > &hitParents) const
Erase hits associated to an MCParticle that does meet a minimum number of hits in the view...
std::string m_fileName
The filename of the ROOT output file.
void GetMetrics(const std::map< const pandora::CaloHit *const, CaloHitParents > &hitParents, ClusterMetrics &metrics) const
Retrieve the metrics of every cluster in a view.
void SetBranches(ClusterMetrics &metrics, float randIdx, std::string branchPrefix) const
Update the branches of the TTree for this entry.
~EventClusterValidationAlgorithm()
Destructor saves the validation TTree along with making and saving a metadata TTree.
std::vector< std::string > m_clusterListNames
The names of the lists of 2D clusters to process.
double weight
Definition: plottest35.C:25
int m_minMCHitsPerView
Threshold on total main MCParticle hits in each view for consideration in metric calculations.
HitType
Definition: HitType.h:12
std::map< const pandora::CaloHit *const, CaloHitParents > ApplyPDGCut(std::map< const pandora::CaloHit *const, CaloHitParents > &hitParents, const ValidationType &valType) const
Erase hits not associated with an MCParticle PDG incompatible with track/shower/all.
static float CalcRandIndex(const std::map< const Ti, std::map< const Tj, int >> &cTable)
Calculate the adjusted Rand Index for a given contingency table that summarises two clusterings A and...