Belle II Software development
calibration_checker.py
1#!/usr/bin/env python3
2
3
10
11"""\
12This module contains classes for plotting calibration constants.
13"""
14
15from abc import ABC, abstractmethod
16from ROOT import Belle2
17from ROOT.Belle2 import PXDMaskedPixelPar, PXDDeadPixelPar, PXDOccupancyInfoPar
18import ROOT
19from copy import deepcopy
20
21from pxd.utils import get_sensor_graphs, sensorID_list
22# from pxd.utils import nPixels, nVCells, nUCells
23from pxd.calibration.condition_checker import plot_type_dict
24
25# import basf2
26
27# lookup dictrionary for finding a checker based on objType
28__cal_checker_dict__ = {
29 "PXDHotPixelMaskCalibrationChecker": {PXDMaskedPixelPar, PXDDeadPixelPar, PXDOccupancyInfoPar},
30}
31
32
33# Calibration checkers for summarizing info of multiple payloads
35 """
36 Abstract base class for generating plots from calibrations which save multiple payloads.
37 """
38
39 def __init__(self, objType_list=[]):
40 """
41 Parameters:
42 objType_list (list): a list of db objects used as keys to their checker.
44 """
45
47 for objType in objType_list:
48 self.condition_checkers[objType] = None
49
50 def initialize(self, checker_list=[]):
51 """
52 Initialize condition_checker according to condition objType
53 Parameters:
54 checker_list (list): list of objType checkers
55 """
56 checker = None
57 for checker in checker_list:
58 for objType in self.condition_checkers.keys():
59 if objType == checker.objType:
60 self.condition_checkers[objType] = checker
61 if self.valid:
62 checker.tfile.cd()
63 print(f"root file path: {checker.tfile}")
64 self.define_graphs()
65
66 @property
67 def valid(self):
68 """
69 valid flag
70 """
71 if None in self.condition_checkers.values():
72 # do nothing if any of the condition checkers is missing
73 return False
74 else:
75 return True
76
77 @abstractmethod
78 def define_graphs(self):
79 """
80 Define summary graphs
81 """
82
83 @abstractmethod
84 def fill_graphs(self):
85 """
86 Method to fill TGraphs
87 """
88
89 @abstractmethod
90 def draw_plots(self):
91 """
92 Generate summary plots from TGraphs of all condition checkers
93 """
94
95 def beginRun(self):
96 """
97 function to be executed at the beginning of a run
98 """
99 if self.valid:
100 self.fill_graphs()
101
102 def terminate(self):
103 """
104 Execute when terminating a basf2 module.
105 All required TGraphs should be ready at this stage.
106 """
107 if self.valid:
108 self.draw_plots()
109
110
112 """
113 Checker for PXDHotPixelMaskCalibration
114 """
115
116 def __init__(self):
117 """
118 Initial with related condition db object types
119 """
120 super().__init__(objType_list=[
124 ])
125
126 def define_graphs(self):
127 """
128 Define TGraphs for filling
129 """
130
131 self.hotdead_graphs = get_sensor_graphs("Hot/Dead pixels [%]")
132
133 self.occupancy_masked_graphs = get_sensor_graphs("Occupancy (With Mask) [%]")
134
135 self.occupancy_no_mask_graphs = get_sensor_graphs("Occupancy (No Mask) [%]")
136
137 self.sum_graphs = {}
138 # TLegend
139 legsum = ROOT.TLegend(0.75, 0.65, 0.90, 0.85)
140 legsum.SetFillStyle(0)
141 legsum.SetBorderSize(0)
142 legsum.SetTextFont(43)
143 legsum.SetTextSize(18)
144 legocc = deepcopy(legsum)
145 self.sum_graphs["TLegends"] = {"sum": legsum, "occ": legocc}
146
147 for category, style_dict in plot_type_dict.items():
148 agraph = ROOT.TGraph()
149 agraph.SetLineColor(style_dict["color"])
150 agraph.SetMarkerColor(style_dict["color"])
151 agraph.SetMarkerStyle(20)
152 if category == "hot_dead":
153 agraph.GetYaxis().SetTitle('Total Hot/Dead pixels [%]')
154 agraph.SetMaximum(style_dict["max"])
155 elif category == "occ_no_mask":
156 agraph.GetYaxis().SetTitle('Average occupancy [%]')
157 agraph.SetMaximum(style_dict["max"])
158 agraph.GetXaxis().SetTitle('run #')
159 agraph.GetYaxis().SetTitleOffset(0.9)
160 agraph.SetMinimum(0.0)
161 self.sum_graphs[category] = agraph
162
163 def fill_graphs(self):
164 """
165 Fill TGraphs, will be executed for each run
166 """
167 dead_checker = self.condition_checkers[PXDDeadPixelPar]
168 masked_graphs = self.condition_checkers[PXDMaskedPixelPar].graphs
169 dead_graphs = self.condition_checkers[PXDDeadPixelPar].graphs
170 occ_graphs = self.condition_checkers[PXDOccupancyInfoPar].graphs
171
172 current_run = dead_checker.run
173 saved_run = -1 # to be updated
174 saved_run_occ = -1 # to be updated
175 sum_hotfraction = 0.
176 sum_deadfraction = 0.
177 sum_hotdeadfraction = 0.
178 sum_occupancymasked = 0.
179 sum_occupancynomask = 0.
180 for sensorID in sensorID_list:
181 numID = sensorID.getID()
182 # This function is executed just after filling the condition checker graphs, so we don't need a python loop!
183 g1 = masked_graphs[numID]
184 g2 = dead_graphs[numID]
185 g3 = occ_graphs[numID]
186 # If two runs have the same constants, only results from the 1st run will be saved.
187 hotfraction = g1.GetPointY(g1.GetN() - 1)
188 deadfraction = g2.GetPointY(g2.GetN() - 1)
189 occupancymasked = g3.GetPointY(g3.GetN() - 1)
190 occupancynomask = 0.
191 # Masked hot pixels and dead pixels could be updated for different runs.
192 saved_run = max(g1.GetPointX(g1.GetN() - 1), g2.GetPointX(g2.GetN() - 1))
193 saved_run_occ = max(saved_run, g3.GetPointX(g3.GetN() - 1))
194 if current_run == saved_run:
195 self.hotdead_graphs[numID].append(current_run, hotfraction + deadfraction)
196 sum_hotfraction += hotfraction
197 sum_deadfraction += deadfraction
198 sum_hotdeadfraction += hotfraction + deadfraction
199 if current_run == saved_run_occ:
200 # occupancy correction (it's overestiamting occ_masked?)
201 # By defaut, both occ and fractions are in percentage.
202 if deadfraction != 100.:
203 occupancynomask = (occupancymasked + hotfraction) / (1. - deadfraction / 100)
204 occupancymasked = occupancymasked / (1. - deadfraction / 100)
205 self.occupancy_no_mask_graphs[numID].append(current_run, occupancynomask)
206 self.occupancy_masked_graphs[numID].append(current_run, occupancymasked)
207 sum_occupancymasked += occupancymasked
208 sum_occupancynomask += occupancynomask
209 # print(f"hotfraction:{hotfraction}, deadfraction: {deadfraction}")
210 # print(f"occ:{occupancymasked}, occ_no_mask:{occupancynomask}")
211 # rawOcc = self.condition_checkers[PXDOccupancyInfoPar].dbObj.getOccupancy(numID)
212 # print(f"Raw occ: {rawOcc}")
213 # print(f"sum_hotfraction:{sum_hotfraction}, sum_deadfraction: {sum_deadfraction}")
214 # print(f"sum_occ:{sum_occupancymasked}, sum_occ_no_mask:{sum_occupancynomask}")
215
216 nSensors = len(sensorID_list)
217 if current_run == saved_run:
218 self.sum_graphs["hot"].append(current_run, sum_hotfraction / nSensors)
219 self.sum_graphs["dead"].append(current_run, sum_deadfraction / nSensors)
220 self.sum_graphs["hot_dead"].append(current_run, sum_hotdeadfraction / nSensors)
221
222 nSensors = nSensors - len(dead_checker.dbObj.getDeadSensorMap())
223 if current_run == saved_run_occ and nSensors > 0:
224 self.sum_graphs["occ_masked"].append(current_run, sum_occupancymasked / nSensors)
225 self.sum_graphs["occ_no_mask"].append(current_run, sum_occupancynomask / nSensors)
226
227 def draw_plots(self):
228 """
229 Generate summary plots from TGraphs of all condition checkers
230 """
231
232 # Reuse masker checker and replace its graphs
233 # checker = deepcopy(self.condition_checkers[PXDMaskedPixelPar])
234 # checker.tfile = self.condition_checkers[PXDMaskedPixelPar].tfile
235 checker = self.condition_checkers[PXDMaskedPixelPar]
236
237 # Create a TCanvas under the file folder
238 checker.tfile.cd()
239 canvas = ROOT.TCanvas('c_pxd', 'c_pxd', 1000, 400)
240 # canvas.SetLeftMargin(0.09)
241 # canvas.SetRightMargin(0.05)
242
243 # Hot/dead pixel masking
244 checker.graphs = self.hotdead_graphs
245 checker.draw_plots(
246 canvas=canvas, cname="PXDHotnDeadPixel", ymin=0., ymax=plot_type_dict["hot_dead"]["max"])
247 checker.draw_plots(canvas=canvas, cname="PXDHotnDeadPixel", ymin=0., ymax=None, logy=True)
248 canvas.Write()
249 # Occupancy with/without Mask
250 checker.graphs = self.occupancy_masked_graphs
251 checker.draw_plots(
252 canvas=canvas, cname="PXDOccupancyWithMask", ymin=0., ymax=plot_type_dict["occ_masked"]["max"])
253 checker.draw_plots(canvas=canvas, cname="PXDOccupancyWithMask", ymin=0., ymax=None, logy=True)
254 canvas.Write()
255 checker.graphs = self.occupancy_no_mask_graphs
256 checker.draw_plots(
257 canvas=canvas, cname="PXDOccupancyNoMask", ymin=0., ymax=plot_type_dict["occ_no_mask"]["max"])
258 checker.draw_plots(canvas=canvas, cname="PXDOccupancyNoMask", ymin=0., ymax=None, logy=True)
259 canvas.Write()
260
261 # checker.save_canvas(canvas, "PXDHotnDeadPixel")
262
263 # Summary plots
264 # Total Masking plots
265 canvas.Clear()
266 g1 = self.sum_graphs["hot_dead"]
267 g2 = self.sum_graphs["hot"]
268 g3 = self.sum_graphs["dead"]
269 leg = self.sum_graphs["TLegends"]["sum"]
270 g1.Draw("AP")
271 g2.Draw("P same")
272 g3.Draw("P same")
273 leg.AddEntry(g1, 'Hot+Dead pixels', 'p')
274 leg.AddEntry(g2, 'Hot pixels', 'p')
275 leg.AddEntry(g3, 'Dead pixels', 'p')
276 leg.Draw()
277 checker.save_canvas(canvas, "PXDTotalHotnDeadPixel", logy=True)
278 canvas.Write()
279 checker.save_canvas(canvas, "PXDTotalHotnDeadPixel", logy=False)
280 # Average Occupancy plots
281 canvas.Clear()
282 g1 = self.sum_graphs["occ_no_mask"]
283 g2 = self.sum_graphs["occ_masked"]
284 leg = self.sum_graphs["TLegends"]["occ"]
285 g1.Draw("AP")
286 g2.Draw("P same")
287 leg.AddEntry(g1, 'No Mask', 'p')
288 leg.AddEntry(g2, 'With Mask', 'p')
289 leg.Draw()
290 checker.save_canvas(canvas, "PXDTotalOccupancy", logy=True)
291 canvas.Write()
292 checker.save_canvas(canvas, "PXDTotalOccupancy", logy=False)
The payload telling which PXD pixel is dead (=Readout system does not receive signals)
The payload telling which PXD pixel to mask (ignore)
The payload collecting some meta information from running the masking algorithm.
condition_checkers
condition checkers which are assoicated to this calibration.
occupancy_no_mask_graphs
Dictionary to save TGraph of module occupancy (without mask)
occupancy_masked_graphs
Dictionary to save TGraph of module occupancy (with mask)
hotdead_graphs
Dictionary to save TGraphs of hot/dead pixel fractions with sensor id as key.