Belle II Software prerelease-11-00-00a
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 plotTrackQualityIndicator=True):
64 """Constructor"""
65
66 TrackingValidationModule.__init__(
67 self,
68 name,
69 contact,
70 fit,
71 pulls,
72 resolution,
73 output_file_name,
74 track_filter_object,
75 plot_name_postfix,
76 plot_title_postfix,
77 exclude_profile_mc_parameter,
78 exclude_profile_pr_parameter,
79 use_expert_folder,
80 trackCandidatesColumnName,
81 mcTrackCandidatesColumnName)
82
83 ## cached name of the CDCHits StoreArray
84 self.cdcHitsColumnname = cdcHitsColumnName
85 ## cached value of the flag to write the validation figures of merit
86 self.write_tables = write_tables
87 ## draw validation plots of track quality indicator
88 self.plotTrackQualityIndicator = plotTrackQualityIndicator
89
90 def initialize(self):
91 """Receive signal at the start of event processing"""
92 TrackingValidationModule.initialize(self)
93
94 # Use dequeues in favour of lists to prevent repeated memory allocation of cost O(n)
95 ## number of all hits
96 self.number_of_total_hits = collections.deque()
97 ## number of hits on MC track
98 self.number_of_mc_hits = collections.deque()
99 ## number of hits on pattern reconstructed tracks
100 self.number_of_pr_hits = collections.deque()
101 ## list of flags for [not-]found hits
102 self.is_hit_found = collections.deque()
103 ## list of flags for [not-]matched hits
104 self.is_hit_matched = collections.deque()
105
106 # MC information
107 ## list of flags where MCRecoTrack is [not] missing MCTrackCand
108 self.mc_missing = collections.deque()
109 ## list of fraction of number of hits in MC track but not in PR track
110 self.ratio_hits_in_mc_tracks_and_not_in_pr_tracks = collections.deque()
111 ## list of fraction of number of hits in MC track and in PR track
112 self.ratio_hits_in_mc_tracks_and_in_pr_tracks = collections.deque()
113 ## list of fraction of number of hits in missing MC track and in PR track
114 self.ratio_hits_in_missing_mc_tracks_and_in_pr_tracks = collections.deque()
115 ## list of fraction of number of hits in MC track and in fake PR track
116 self.ratio_hits_in_mc_tracks_and_in_fake_pr_tracks = \
117 collections.deque()
118 ## list of fraction of number of hits in MC track and in good PR track
119 self.ratio_hits_in_mc_tracks_and_in_good_pr_tracks = \
120 collections.deque()
121 ## list of flags indicating that the MC track is [not] a primary MCParticle
122 self.mc_is_primary = collections.deque()
123 ## list of the number of MCTrackCandHits on the MC track
124 self.mc_number_of_hits = collections.deque()
125
126 # PT information
127 ## This is the number of mcTrackCands sharing a hit with the track cand.
128 self.number_of_connected_tracks = collections.deque()
129 ## This number gives information about the "badness" of the fake.
130 self.number_of_wrong_hits = collections.deque()
131 # It is calculated by going through all hits of the fake track and the connected mc track cands and counting the number.
132 # These numbers are than summed up and subtracted by the biggest number
133 # of hits this candidates shares with the mc track cands.
134 ## list of the number of pattern-reconstructed hits
135 self.pr_number_of_hits = collections.deque()
136 ## list of the number of pattern-reconstructed hits matched to MC track
137 self.pr_number_of_matched_hits = collections.deque()
138 ## list of the quality indicator of tracks
139 self.pr_track_QI = collections.deque()
140
141 def event(self):
142 """Event method"""
143
144 TrackingValidationModule.event(self)
145 self.examine_hits_in_event()
146
147 def examine_hits_in_event(self):
148 """Classify all of the hits in the event according to the parent track(s)"""
149
150 trackCands = Belle2.PyStoreArray(self.trackCandidatesColumnName)
151 mcTrackCands = Belle2.PyStoreArray(self.mcTrackCandidatesColumnName)
152 if self.cdcHitsColumnname not in Belle2.PyStoreArray.list():
153 # No CDC hits available, hit analysis incomplete, don't perform
154 # hit analysis
155 return
156 cdcHits = Belle2.PyStoreArray(self.cdcHitsColumnname)
157
158 # # CDC Hits in MC tracks
159 totalHitListMC = []
160 for mcTrackCand in mcTrackCands:
161 cdcHitIDs = [cdcHit.getArrayIndex() for cdcHit in getObjectList(mcTrackCand.getCDCHitList())] # Checked
162 # Working around a bug in ROOT where you should not access empty std::vectors
163 if len(cdcHitIDs) == 0:
164 cdcHitIDs = set()
165 else:
166 cdcHitIDs = set(cdcHitIDs)
167 totalHitListMC.extend(cdcHitIDs)
168
169 # Make the ids unique
170 totalHitListMC = set(totalHitListMC)
171
172 # # CDC Hits in PR tracks
173 totalHitListPR = []
174 totalHitListPRGood = []
175 totalHitListPRClone = []
176 totalHitListPRFake = []
177 for trackCand in trackCands:
178 if trackCand.getNumberOfTotalHits() == 0:
179 basf2.B2WARNING("Encountered a pattern recognition track with no hits")
180 continue
181
182 cdcHitIDs = [cdcHit.getArrayIndex() for cdcHit in getObjectList(trackCand.getCDCHitList())] # Checked
183 # Working around a bug in ROOT where you should not access empty std::vectors
184 if len(cdcHitIDs) == 0:
185 cdcHitIDs = set()
186 else:
187 cdcHitIDs = set(cdcHitIDs)
188
189 totalHitListPR.extend(cdcHitIDs)
190 if self.trackMatchLookUp.isAnyChargeMatchedPRRecoTrack(trackCand):
191 totalHitListPRGood.extend(cdcHitIDs)
192
193 if self.trackMatchLookUp.isAnyChargeClonePRRecoTrack(trackCand):
194 totalHitListPRClone.extend(cdcHitIDs)
195
196 if (self.trackMatchLookUp.isBackgroundPRRecoTrack(trackCand) or
197 self.trackMatchLookUp.isGhostPRRecoTrack(trackCand)):
198 totalHitListPRFake.extend(cdcHitIDs)
199
200 # Make the ids unique
201 totalHitListPR = set(totalHitListPR)
202 totalHitListPRGood = set(totalHitListPRGood)
203 totalHitListPRClone = set(totalHitListPRClone)
204 totalHitListPRFake = set(totalHitListPRFake)
205
206 # # All CDC Hits
207 totalHitList = {cdcHit.getArrayIndex() for cdcHit in cdcHits}
208
209 number_of_mc_hits = len(totalHitListMC)
210 number_of_pr_hits = len(totalHitListPR)
211 number_of_all_hits = len(totalHitList)
212
213 is_hit_matched = 0
214 is_hit_found = len(totalHitListMC & totalHitListPR)
215
216 for trackCand in trackCands:
217
218 is_matched = self.trackMatchLookUp.isAnyChargeMatchedPRRecoTrack(trackCand)
219 is_clone = self.trackMatchLookUp.isAnyChargeClonePRRecoTrack(trackCand)
220
221 trackCandHits = [cdcHit.getArrayIndex() for cdcHit in getObjectList(trackCand.getCDCHitList())]
222 # Working around a bug in ROOT where you should not access empty std::vectors
223 if len(trackCandHits) == 0:
224 trackCandHits = set()
225 else:
226 trackCandHits = set(trackCandHits)
227
228 # this is not very efficient...
229 list_of_connected_mc_tracks = set()
230 list_of_numbers_of_hits_for_connected_tracks = collections.deque()
231 # number_of_connected_tracks = 0
232 # number_of_wrong_hits = 0
233
234 for mcTrackCand in mcTrackCands:
235 mcTrackCandHits = [cdcHit.getArrayIndex() for cdcHit in getObjectList(mcTrackCand.getCDCHitList())]
236 # Working around a bug in ROOT where you should not access empty std::vectors
237 if len(mcTrackCandHits) == 0:
238 mcTrackCandHits = set()
239 else:
240 mcTrackCandHits = set(mcTrackCandHits)
241
242 length_of_intersection = len(mcTrackCandHits & trackCandHits)
243 if length_of_intersection > 0:
244 list_of_connected_mc_tracks.add(mcTrackCand)
245 list_of_numbers_of_hits_for_connected_tracks.append(length_of_intersection)
246
247 if len(list_of_numbers_of_hits_for_connected_tracks) == 0:
248 self.number_of_wrong_hits.append(0)
249 self.pr_number_of_matched_hits.append(0)
250 else:
251 maximum_intersection = \
252 max(list_of_numbers_of_hits_for_connected_tracks)
253 self.pr_number_of_matched_hits.append(sum(list_of_numbers_of_hits_for_connected_tracks))
254 self.number_of_wrong_hits.append(sum(list_of_numbers_of_hits_for_connected_tracks) -
255 maximum_intersection)
256
257 self.number_of_connected_tracks.append(len(list_of_connected_mc_tracks))
258
259 if is_matched or is_clone:
260 mcTrackCand = \
261 self.trackMatchLookUp.getRelatedMCRecoTrack(trackCand)
262 mcTrackCandHits = [cdcHit.getArrayIndex() for cdcHit in getObjectList(mcTrackCand.getCDCHitList())] # Checked
263 # Working around a bug in ROOT where you should not access empty std::vectors
264 if len(mcTrackCandHits) == 0:
265 mcTrackCandHits = set()
266 else:
267 mcTrackCandHits = set(mcTrackCandHits)
268
269 is_hit_matched += len(trackCandHits & mcTrackCandHits)
270
271 self.pr_number_of_hits.append(len(trackCandHits))
272
273 if self.plotTrackQualityIndicator:
274 self.pr_track_QI.append(trackCand.getQualityIndicator())
275
276 for mcTrackCand in mcTrackCands:
277 is_missing = \
278 self.trackMatchLookUp.isMissingMCRecoTrack(mcTrackCand)
279
280 mcTrackCandHits = [cdcHit.getArrayIndex() for cdcHit in getObjectList(mcTrackCand.getCDCHitList())] # Checked
281
282 # Working around a bug in ROOT where you should not access empty std::vectors
283 if len(mcTrackCandHits) == 0:
284 continue
285 else:
286 mcTrackCandHits = set(mcTrackCandHits)
287
288 ratio = 1.0 * len(mcTrackCandHits & totalHitListPR) / len(mcTrackCandHits)
289
290 self.ratio_hits_in_mc_tracks_and_not_in_pr_tracks.append(1.0 - ratio)
291 self.ratio_hits_in_mc_tracks_and_in_pr_tracks.append(ratio)
292 if is_missing:
293 self.ratio_hits_in_missing_mc_tracks_and_in_pr_tracks.append(ratio)
294 self.ratio_hits_in_mc_tracks_and_in_good_pr_tracks.append(
295 1.0 * len(mcTrackCandHits & totalHitListPRGood) / len(mcTrackCandHits))
296 self.ratio_hits_in_mc_tracks_and_in_fake_pr_tracks.append(
297 1.0 * len(mcTrackCandHits & totalHitListPRFake) / len(mcTrackCandHits))
298
299 mcParticle = \
300 self.trackMatchLookUp.getRelatedMCParticle(mcTrackCand)
301 is_primary = \
302 mcParticle.hasStatus(Belle2.MCParticle.c_PrimaryParticle)
303 self.mc_is_primary.append(is_primary)
304 self.mc_number_of_hits.append(len(mcTrackCandHits))
305
306 self.mc_missing.append(is_missing)
307
308 self.number_of_total_hits.append(number_of_all_hits)
309 self.number_of_mc_hits.append(number_of_mc_hits)
310 self.number_of_pr_hits.append(number_of_pr_hits)
311
312 self.is_hit_found.append(is_hit_found)
313 self.is_hit_matched.append(is_hit_matched)
314
315 def terminate(self):
316 """Receive signal at the end of event processing"""
317 TrackingValidationModule.terminate(self)
318
319 output_tfile = ROOT.TFile(self.output_file_name, 'update')
320
321 validation_plots = []
322
323 # Hit ratios #
324 ######################
325 all_tracks_plot = self.profiles_by_parameters_base(
326 xs=self.ratio_hits_in_mc_tracks_and_in_pr_tracks,
327 quantity_name="ratio of hits in MCTracks found by the track finder",
328 make_hist=True,
329 parameter_names=[],
330 profile_parameters={},
331 unit=None)
332
333 validation_plots.extend(all_tracks_plot)
334
335 missing_tracks_plot = self.profiles_by_parameters_base(
336 xs=self.ratio_hits_in_missing_mc_tracks_and_in_pr_tracks,
337 quantity_name="ratio of hits in missing MCTracks found by the track finder",
338 make_hist=True,
339 parameter_names=[],
340 profile_parameters={},
341 unit=None)
342
343 validation_plots.extend(missing_tracks_plot)
344
345 # Quality Indicator #
346 if self.plotTrackQualityIndicator:
347 track_QI_plot = self.profiles_by_parameters_base(
348 xs=self.pr_track_QI,
349 quantity_name="track quality indicator",
350 make_hist=True,
351 parameter_names=[],
352 profile_parameters={},
353 unit=None)
354
355 validation_plots.extend(track_QI_plot)
356
357 for validation_plot in validation_plots:
358 validation_plot.write()
359
360 if self.write_tables:
361 # MC Figures of merit
362 mc_figures_of_merit = \
363 ValidationManyFiguresOfMerit(f'{self.validation_name}_mc_figures_of_merit')
364
365 mc_figures_of_merit['mc_pts'] = self.mc_pts
366 mc_figures_of_merit['mc_d0s'] = self.mc_d0s
367 mc_figures_of_merit['mc_matches'] = self.mc_matches
368 mc_figures_of_merit['mc_hit_efficiencies'] = self.mc_hit_efficiencies
369 mc_figures_of_merit['mc_multiplicities'] = self.mc_multiplicities
370 mc_figures_of_merit['mc_phis'] = self.mc_phi
371 mc_figures_of_merit['mc_tan_lambdas'] = self.mc_tan_lambdas
372 mc_figures_of_merit['mc_missing'] = self.mc_missing
373 mc_figures_of_merit['mc_is_primary'] = self.mc_is_primary
374 mc_figures_of_merit['mc_number_of_hits'] = self.mc_number_of_hits
375 mc_figures_of_merit['ratio_hits_in_mc_tracks_and_in_good_pr_tracks'] = \
376 self.ratio_hits_in_mc_tracks_and_in_good_pr_tracks
377 mc_figures_of_merit['ratio_hits_in_mc_tracks_and_in_fake_pr_tracks'] = \
378 self.ratio_hits_in_mc_tracks_and_in_fake_pr_tracks
379 mc_figures_of_merit['ratio_hits_in_mc_tracks_and_not_in_pr_tracks'] = \
380 self.ratio_hits_in_mc_tracks_and_not_in_pr_tracks
381
382 mc_figures_of_merit.write()
383
384 # PR Figures of merit
385 pr_figures_of_merit = \
386 ValidationManyFiguresOfMerit(f'{self.validation_name}_pr_figures_of_merit')
387
388 pr_figures_of_merit['pr_clones_and_matches'] = \
389 self.pr_clones_and_matches
390 pr_figures_of_merit['pr_matches'] = self.pr_matches
391 pr_figures_of_merit['pr_fakes'] = self.pr_fakes
392 pr_figures_of_merit['pr_number_of_hits'] = self.pr_number_of_hits
393 pr_figures_of_merit['pr_number_of_matched_hits'] = \
394 self.pr_number_of_matched_hits
395 pr_figures_of_merit['pr_seed_tan_lambdas'] = self.pr_seed_tan_lambdas
396 pr_figures_of_merit['pr_seed_phi'] = self.pr_seed_phi
397
398 pr_figures_of_merit['number_of_connected_tracks'] = \
399 self.number_of_connected_tracks
400 pr_figures_of_merit['number_of_wrong_hits'] = self.number_of_wrong_hits
401
402 pr_figures_of_merit.write()
403
404 # Hit Figures of merit
405 hit_figures_of_merit = \
406 ValidationFiguresOfMerit(f'{self.validation_name}_hit_figures_of_merit')
407
408 hit_figures_of_merit['number_of_total_hits'] = \
409 np.sum(self.number_of_total_hits)
410 hit_figures_of_merit['number_of_mc_hits'] = \
411 np.sum(self.number_of_mc_hits)
412 hit_figures_of_merit['number_of_pr_hits'] = \
413 np.sum(self.number_of_pr_hits)
414 hit_figures_of_merit['is_hit_found'] = np.sum(self.is_hit_found)
415 hit_figures_of_merit['is_hit_matched'] = np.sum(self.is_hit_matched)
416
417 print(hit_figures_of_merit)
418 hit_figures_of_merit.write()
419
420 output_tfile.Close()
__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, plotTrackQualityIndicator=True)
Definition hit_module.py:63