Belle II Software development
hit_module.py
1#!/usr/bin/env python3
2
3
10
11import collections
12import numpy as np
13
14from tracking.validation.fom import (
15 ValidationFiguresOfMerit,
16 ValidationManyFiguresOfMerit
17)
18
20 AlwaysPassFilter,
21 TrackingValidationModule
22)
23
24from tracking.validation.utilities import getObjectList
25
26import basf2
27
28import ROOT
29
30from ROOT import Belle2
31
32ROOT.gSystem.Load('libtracking')
33# FIXME: define hash function for TrackCand to be able to add it to set. This is
34# not really correct, it just checks for the address and normally if a==b also
35# hash(a) == hash(b) is required
36ROOT.genfit.TrackCand.__hash__ = lambda x: id(x)
37
38
40
41 """Module to collect more matching information about the found particles and to generate validation
42 plots and figures of merit on the performance of track finding. This module gives information on the
43 number of hits etc. """
44
46 self,
47 name,
48 contact,
49 fit=False,
50 pulls=False,
51 resolution=False,
52 output_file_name=None,
53 track_filter_object=AlwaysPassFilter(),
54 plot_name_postfix='',
55 plot_title_postfix='',
56 exclude_profile_mc_parameter='',
57 exclude_profile_pr_parameter='',
58 use_expert_folder=True,
59 trackCandidatesColumnName='RecoTracks',
60 mcTrackCandidatesColumnName='MCRecoTracks',
61 cdcHitsColumnName='CDCHits',
62 write_tables=False):
63 """Constructor"""
64
65 TrackingValidationModule.__init__(
66 self,
67 name,
68 contact,
69 fit,
70 pulls,
71 resolution,
72 output_file_name,
73 track_filter_object,
74 plot_name_postfix,
75 plot_title_postfix,
76 exclude_profile_mc_parameter,
77 exclude_profile_pr_parameter,
78 use_expert_folder,
79 trackCandidatesColumnName,
80 mcTrackCandidatesColumnName)
81
82
83 self.cdcHitsColumnname = cdcHitsColumnName
84
85 self.write_tables = write_tables
86
87 def initialize(self):
88 """Receive signal at the start of event processing"""
89 TrackingValidationModule.initialize(self)
90
91 # Use deques in favour of lists to prevent repeated memory allocation of cost O(n)
92
93 self.number_of_total_hits = collections.deque()
94
95 self.number_of_mc_hits = collections.deque()
96
97 self.number_of_pr_hits = collections.deque()
98
99 self.is_hit_found = collections.deque()
100
101 self.is_hit_matched = collections.deque()
102
103 # MC information
104
105 self.mc_missing = collections.deque()
106
108
110
112
114 collections.deque()
115
117 collections.deque()
118
119 self.mc_is_primary = collections.deque()
120
121 self.mc_number_of_hits = collections.deque()
122
123 # PT information
124
125 self.number_of_connected_tracks = collections.deque()
126
127 self.number_of_wrong_hits = collections.deque()
128 # It is calculated by going through all hits of the fake track and the connected mc track cands and counting the number.
129 # These numbers are than summed up and substracted by the biggest number
130 # of hits this candidates shares with the mc track cands.
131
132 self.pr_number_of_hits = collections.deque()
133
134 self.pr_number_of_matched_hits = collections.deque()
135
136 def event(self):
137 """Event method"""
138
139 TrackingValidationModule.event(self)
141
143 """Classify all of the hits in the event according to the parent track(s)"""
144
148 # No CDC hits available, hit analysis incomplete, don't perform
149 # hit analysis
150 return
152
153 # # CDC Hits in MC tracks
154 totalHitListMC = []
155 for mcTrackCand in mcTrackCands:
156 cdcHitIDs = [cdcHit.getArrayIndex() for cdcHit in getObjectList(mcTrackCand.getCDCHitList())] # Checked
157 # Working around a bug in ROOT where you should not access empty std::vectors
158 if len(cdcHitIDs) == 0:
159 cdcHitIDs = set()
160 else:
161 cdcHitIDs = set(cdcHitIDs)
162 totalHitListMC.extend(cdcHitIDs)
163
164 # Make the ids unique
165 totalHitListMC = set(totalHitListMC)
166
167 # # CDC Hits in PR tracks
168 totalHitListPR = []
169 totalHitListPRGood = []
170 totalHitListPRClone = []
171 totalHitListPRFake = []
172 for trackCand in trackCands:
173 if trackCand.getNumberOfTotalHits() == 0:
174 basf2.B2WARNING("Encountered a pattern recognition track with no hits")
175 continue
176
177 cdcHitIDs = [cdcHit.getArrayIndex() for cdcHit in getObjectList(trackCand.getCDCHitList())] # Checked
178 # Working around a bug in ROOT where you should not access empty std::vectors
179 if len(cdcHitIDs) == 0:
180 cdcHitIDs = set()
181 else:
182 cdcHitIDs = set(cdcHitIDs)
183
184 totalHitListPR.extend(cdcHitIDs)
185 if self.trackMatchLookUp.isAnyChargeMatchedPRRecoTrack(trackCand):
186 totalHitListPRGood.extend(cdcHitIDs)
187
188 if self.trackMatchLookUp.isAnyChargeClonePRRecoTrack(trackCand):
189 totalHitListPRClone.extend(cdcHitIDs)
190
191 if (self.trackMatchLookUp.isBackgroundPRRecoTrack(trackCand) or
192 self.trackMatchLookUp.isGhostPRRecoTrack(trackCand)):
193 totalHitListPRFake.extend(cdcHitIDs)
194
195 # Make the ids unique
196 totalHitListPR = set(totalHitListPR)
197 totalHitListPRGood = set(totalHitListPRGood)
198 totalHitListPRClone = set(totalHitListPRClone)
199 totalHitListPRFake = set(totalHitListPRFake)
200
201 # # All CDC Hits
202 totalHitList = {cdcHit.getArrayIndex() for cdcHit in cdcHits}
203
204 number_of_mc_hits = len(totalHitListMC)
205 number_of_pr_hits = len(totalHitListPR)
206 number_of_all_hits = len(totalHitList)
207
208 is_hit_matched = 0
209 is_hit_found = len(totalHitListMC & totalHitListPR)
210
211 for trackCand in trackCands:
212
213 is_matched = self.trackMatchLookUp.isAnyChargeMatchedPRRecoTrack(trackCand)
214 is_clone = self.trackMatchLookUp.isAnyChargeClonePRRecoTrack(trackCand)
215
216 trackCandHits = [cdcHit.getArrayIndex() for cdcHit in getObjectList(trackCand.getCDCHitList())]
217 # Working around a bug in ROOT where you should not access empty std::vectors
218 if len(trackCandHits) == 0:
219 trackCandHits = set()
220 else:
221 trackCandHits = set(trackCandHits)
222
223 # this is not very efficient...
224 list_of_connected_mc_tracks = set()
225 list_of_numbers_of_hits_for_connected_tracks = collections.deque()
226 # number_of_connected_tracks = 0
227 # number_of_wrong_hits = 0
228
229 for mcTrackCand in mcTrackCands:
230 mcTrackCandHits = [cdcHit.getArrayIndex() for cdcHit in getObjectList(mcTrackCand.getCDCHitList())]
231 # Working around a bug in ROOT where you should not access empty std::vectors
232 if len(mcTrackCandHits) == 0:
233 mcTrackCandHits = set()
234 else:
235 mcTrackCandHits = set(mcTrackCandHits)
236
237 length_of_intersection = len(mcTrackCandHits & trackCandHits)
238 if length_of_intersection > 0:
239 list_of_connected_mc_tracks.add(mcTrackCand)
240 list_of_numbers_of_hits_for_connected_tracks.append(length_of_intersection)
241
242 if len(list_of_numbers_of_hits_for_connected_tracks) == 0:
243 self.number_of_wrong_hits.append(0)
244 self.pr_number_of_matched_hits.append(0)
245 else:
246 maximum_intersection = \
247 max(list_of_numbers_of_hits_for_connected_tracks)
248 self.pr_number_of_matched_hits.append(sum(list_of_numbers_of_hits_for_connected_tracks))
249 self.number_of_wrong_hits.append(sum(list_of_numbers_of_hits_for_connected_tracks) -
250 maximum_intersection)
251
252 self.number_of_connected_tracks.append(len(list_of_connected_mc_tracks))
253
254 if is_matched or is_clone:
255 mcTrackCand = \
256 self.trackMatchLookUp.getRelatedMCRecoTrack(trackCand)
257 mcTrackCandHits = [cdcHit.getArrayIndex() for cdcHit in getObjectList(mcTrackCand.getCDCHitList())] # Checked
258 # Working around a bug in ROOT where you should not access empty std::vectors
259 if len(mcTrackCandHits) == 0:
260 mcTrackCandHits = set()
261 else:
262 mcTrackCandHits = set(mcTrackCandHits)
263
264 is_hit_matched += len(trackCandHits & mcTrackCandHits)
265
266 self.pr_number_of_hits.append(len(trackCandHits))
267
268 for mcTrackCand in mcTrackCands:
269 is_missing = \
270 self.trackMatchLookUp.isMissingMCRecoTrack(mcTrackCand)
271
272 mcTrackCandHits = [cdcHit.getArrayIndex() for cdcHit in getObjectList(mcTrackCand.getCDCHitList())] # Checked
273
274 # Working around a bug in ROOT where you should not access empty std::vectors
275 if len(mcTrackCandHits) == 0:
276 continue
277 else:
278 mcTrackCandHits = set(mcTrackCandHits)
279
280 ratio = 1.0 * len(mcTrackCandHits & totalHitListPR) / len(mcTrackCandHits)
281
282 self.ratio_hits_in_mc_tracks_and_not_in_pr_tracks.append(1.0 - ratio)
284 if is_missing:
287 1.0 * len(mcTrackCandHits & totalHitListPRGood) / len(mcTrackCandHits))
289 1.0 * len(mcTrackCandHits & totalHitListPRFake) / len(mcTrackCandHits))
290
291 mcParticle = \
292 self.trackMatchLookUp.getRelatedMCParticle(mcTrackCand)
293 is_primary = \
294 mcParticle.hasStatus(Belle2.MCParticle.c_PrimaryParticle)
295 self.mc_is_primary.append(is_primary)
296 self.mc_number_of_hits.append(len(mcTrackCandHits))
297
298 self.mc_missing.append(is_missing)
299
300 self.number_of_total_hits.append(number_of_all_hits)
301 self.number_of_mc_hits.append(number_of_mc_hits)
302 self.number_of_pr_hits.append(number_of_pr_hits)
303
304 self.is_hit_found.append(is_hit_found)
305 self.is_hit_matched.append(is_hit_matched)
306
307 def terminate(self):
308 """Receive signal at the end of event processing"""
309 TrackingValidationModule.terminate(self)
310
311 output_tfile = ROOT.TFile(self.output_file_name, 'update')
312
313 validation_plots = []
314
315 # Hit ratios #
316
317 all_tracks_plot = self.profiles_by_parameters_base(
319 quantity_name="ratio of hits in MCTracks found by the track finder",
320 make_hist=True,
321 parameter_names=[],
322 profile_parameters={},
323 unit=None)
324
325 validation_plots.extend(all_tracks_plot)
326
327 missing_tracks_plot = self.profiles_by_parameters_base(
329 quantity_name="ratio of hits in missing MCTracks found by the track finder",
330 make_hist=True,
331 parameter_names=[],
332 profile_parameters={},
333 unit=None)
334
335 validation_plots.extend(missing_tracks_plot)
336
337 for validation_plot in validation_plots:
338 validation_plot.write()
339
340 if self.write_tables:
341 # MC Figures of merit
342 mc_figures_of_merit = \
343 ValidationManyFiguresOfMerit(f'{self.validation_name}_mc_figures_of_merit')
344
345 mc_figures_of_merit['mc_pts'] = self.mc_pts
346 mc_figures_of_merit['mc_d0s'] = self.mc_d0s
347 mc_figures_of_merit['mc_matches'] = self.mc_matches
348 mc_figures_of_merit['mc_hit_efficiencies'] = self.mc_hit_efficiencies
349 mc_figures_of_merit['mc_multiplicities'] = self.mc_multiplicities
350 mc_figures_of_merit['mc_phis'] = self.mc_phi
351 mc_figures_of_merit['mc_tan_lambdas'] = self.mc_tan_lambdas
352 mc_figures_of_merit['mc_missing'] = self.mc_missing
353 mc_figures_of_merit['mc_is_primary'] = self.mc_is_primary
354 mc_figures_of_merit['mc_number_of_hits'] = self.mc_number_of_hits
355 mc_figures_of_merit['ratio_hits_in_mc_tracks_and_in_good_pr_tracks'] = \
357 mc_figures_of_merit['ratio_hits_in_mc_tracks_and_in_fake_pr_tracks'] = \
359 mc_figures_of_merit['ratio_hits_in_mc_tracks_and_not_in_pr_tracks'] = \
361
362 mc_figures_of_merit.write()
363
364 # PR Figures of merit
365 pr_figures_of_merit = \
366 ValidationManyFiguresOfMerit(f'{self.validation_name}_pr_figures_of_merit')
367
368 pr_figures_of_merit['pr_clones_and_matches'] = \
370 pr_figures_of_merit['pr_matches'] = self.pr_matches
371 pr_figures_of_merit['pr_fakes'] = self.pr_fakes
372 pr_figures_of_merit['pr_number_of_hits'] = self.pr_number_of_hits
373 pr_figures_of_merit['pr_number_of_matched_hits'] = \
375 pr_figures_of_merit['pr_seed_tan_lambdas'] = self.pr_seed_tan_lambdas
376 pr_figures_of_merit['pr_seed_phi'] = self.pr_seed_phi
377
378 pr_figures_of_merit['number_of_connected_tracks'] = \
380 pr_figures_of_merit['number_of_wrong_hits'] = self.number_of_wrong_hits
381
382 pr_figures_of_merit.write()
383
384 # Hit Figures of merit
385 hit_figures_of_merit = \
386 ValidationFiguresOfMerit(f'{self.validation_name}_hit_figures_of_merit')
387
388 hit_figures_of_merit['number_of_total_hits'] = \
389 np.sum(self.number_of_total_hits)
390 hit_figures_of_merit['number_of_mc_hits'] = \
391 np.sum(self.number_of_mc_hits)
392 hit_figures_of_merit['number_of_pr_hits'] = \
393 np.sum(self.number_of_pr_hits)
394 hit_figures_of_merit['is_hit_found'] = np.sum(self.is_hit_found)
395 hit_figures_of_merit['is_hit_matched'] = np.sum(self.is_hit_matched)
396
397 print(hit_figures_of_merit)
398 hit_figures_of_merit.write()
399
400 output_tfile.Close()
A (simplified) python wrapper for StoreArray.
Definition: PyStoreArray.h:72
static std::vector< std::string > list(DataStore::EDurability durability=DataStore::EDurability::c_Event)
Return list of available arrays for given durability.
Definition: PyStoreArray.cc:28
ratio_hits_in_mc_tracks_and_in_pr_tracks
list of fraction of number of hits in MC track and in PR track
Definition: hit_module.py:109
pr_number_of_hits
list of the number of pattern-reconstructed hits
Definition: hit_module.py:132
mc_is_primary
list of flags indicating that the MC track is [not] a primary MCParticle
Definition: hit_module.py:119
ratio_hits_in_missing_mc_tracks_and_in_pr_tracks
list of fraction of number of hits in missing MC track and in PR track
Definition: hit_module.py:111
ratio_hits_in_mc_tracks_and_in_good_pr_tracks
list of fraction of number of hits in MC track and in good PR track
Definition: hit_module.py:116
def __init__(self, name, contact, fit=False, pulls=False, resolution=False, output_file_name=None, track_filter_object=AlwaysPassFilter(), plot_name_postfix='', plot_title_postfix='', exclude_profile_mc_parameter='', exclude_profile_pr_parameter='', use_expert_folder=True, trackCandidatesColumnName='RecoTracks', mcTrackCandidatesColumnName='MCRecoTracks', cdcHitsColumnName='CDCHits', write_tables=False)
Definition: hit_module.py:62
mc_number_of_hits
list of the number of MCTrackCandHits on the MC track
Definition: hit_module.py:121
number_of_connected_tracks
This is the number of mcTrackCands sharing a hit with the track cand.
Definition: hit_module.py:125
ratio_hits_in_mc_tracks_and_not_in_pr_tracks
list of fraction of number of hits in MC track but not in PR track
Definition: hit_module.py:107
write_tables
cached value of the flag to write the validation figures of merit
Definition: hit_module.py:85
number_of_wrong_hits
This number gives information about the "badness" of the fake.
Definition: hit_module.py:127
number_of_pr_hits
number of hits on pattern reconstructed tracks
Definition: hit_module.py:97
cdcHitsColumnname
cached name of the CDCHits StoreArray
Definition: hit_module.py:83
pr_number_of_matched_hits
list of the number of pattern-reconstructed hits matched to MC track
Definition: hit_module.py:134
ratio_hits_in_mc_tracks_and_in_fake_pr_tracks
list of fraction of number of hits in MC track and in fake PR track
Definition: hit_module.py:113
is_hit_matched
list of flags for [not-]matched hits
Definition: hit_module.py:101
mc_missing
list of flags where MCRecoTrack is [not] missing MCTrackCand
Definition: hit_module.py:105
pr_seed_tan_lambdas
list of PR-track seed tan(lambda) values
Definition: module.py:195
trackCandidatesColumnName
cached name of the RecoTracks StoreArray
Definition: module.py:155
pr_seed_phi
list of PR-track seed phi values
Definition: module.py:197
output_file_name
cached value of the output ROOT TFile
Definition: module.py:140
trackMatchLookUp
Track-match object that examines relation information from MCMatcherTracksModule.
Definition: module.py:181
mc_multiplicities
list of MC-track multiplicities
Definition: module.py:254
pr_clones_and_matches
Use deques in favour of lists to prevent repeated memory allocation of cost O(n)
Definition: module.py:186
mcTrackCandidatesColumnName
cached name of the MCRecoTracks StoreArray
Definition: module.py:157
mc_tan_lambdas
list of MC-track tan(lambda) values
Definition: module.py:246
mc_hit_efficiencies
list of MC-track hit efficiencies
Definition: module.py:252
def profiles_by_parameters_base(self, xs, quantity_name, parameter_names, profile_parameters, unit, make_hist, weights=None, is_asymmetry=False)
Definition: module.py:790