Belle II Software development
condition_checker.py
1#!/usr/bin/env python3
2
3
10
11"""\
12This module contains classes for plotting calibration constants.
13"""
14
15__all__ = ["ConditionCheckerBase", "PXDMaskedPixelsChecker", "PXDDeadPixelsChecker", "PXDOccupancyInfoChecker"]
16
17from abc import ABC, abstractmethod
18from ROOT import Belle2
19import ROOT
20import numpy as np
21from hist_utils import array2hist
22from pxd.utils import get_sensor_graphs, get_sensor_maps, sensorID_list
23from pxd.utils import latex_r, nPixels, nVCells, nUCells
24
25# import basf2
26
27# lookup dictionary for finding a checker based on objType
28__checker_dict__ = {
29 Belle2.PXDMaskedPixelPar: "PXDMaskedPixelsChecker",
30 Belle2.PXDDeadPixelPar: "PXDDeadPixelsChecker",
31 Belle2.PXDOccupancyInfoPar: "PXDOccupancyInfoChecker",
32 Belle2.PXDGainMapPar: "PXDGainMapChecker",
33}
34
35# plot categories and styles for PXDHotPixelMasking calibration
36type_list = ['hot', 'dead', 'hot_dead', 'occ_no_mask', 'occ_masked']
37# label_list = ['Hot', 'Dead', 'Hot/Dead', 'Occupancy (No Mask)', 'Occupancy (With Mask)']
38label_list = ['Hot pixels', 'Dead pixels', 'Hot/Dead pixels', 'No Mask', 'With Mask']
39color_list = [ROOT.kRed + 1, ROOT.kAzure - 5, ROOT.kGray + 2, ROOT.kOrange + 1, ROOT.kSpring + 5]
40max_list = [4., 4., 20., 3., 3.]
41plot_type_dict = {
42 _type: {'label': _label, 'color': _color, 'max': _max}
43 for (_type, _label, _color, _max) in zip(type_list, label_list, color_list, max_list)
44}
45# marker_style = 20
46
47
48# condition db object (payload) checkers
50 """Abstract base class describing interfaces to make plots from condition db."""
51
52 def __init__(self, name, objType, tfile, rundir="", use_hist=False):
53 """
54 """
55 super().__init__()
56
57 self.name = name
58
59 self._objType = objType
60
61 self.dbObj = Belle2.PyDBObj(self.name, self.objType.Class())
62
63 self.eventMetaData = Belle2.PyStoreObj("EventMetaData")
64
65 self.tfile = tfile
66
67 self.rundir = rundir # empty means skipping self.hists
68 if self.rundir != "":
69 self.tfile.cd()
70 self.tfile.mkdir(self.rundir)
71
72 self.graphs = {}
73
74 self.hists = {}
75
77
78 self.runstart = 999999 # a big number that will never be used
79
80 self.runend = -1
81
82 self.expstart = 999999
83
84 self.use_hist = use_hist
85
86 self.define_graphs()
87
88 @property
89 def objType(self):
90 """
91 DBObj type (read only)
92 """
93 return self._objType
94
95 @property
96 def run(self):
97 """
98 Run number
99 """
100 return self.eventMetaData.getRun()
101
102 @property
103 def exp(self):
104 """
105 Experiment number
106 """
107 return self.eventMetaData.getExperiment()
108
109 def beginRun(self):
110 """
111 Call functions at beginning of a run
112 """
113 self.define_hists()
114 self.fill_plots()
115
116 def define_graphs(self, ytitle=""):
117 """
118 Method to define TGraph
119 Parameters:
120 ytitle (str): Label for TGraph y-axis
121 """
122 self.tfile.cd()
123 self.graphs.update(get_sensor_graphs(ytitle))
124
125 def define_hists(self, name=None, title=None, ztitle=None, **kwargs):
126 """
127 Method to define TH2
128 Parameters:
129 name (str): name for TH2, to which sensor name will be attached
130 title (str): title for TH2
131 ztitle (str): title for z-axis (color bar)
132 kwargs: additional arguments
133 """
134 if self.rundir:
135 self.tfile.cd(self.rundir)
136 # \cond false positive doxygen warning
137 self.hists.update(get_sensor_maps(name, title, ztitle, self.run, **kwargs))
138 # \endcond
139
140 @abstractmethod
141 def get_db_content(self):
142 """
143 Abstract method to get content of a payload
144 Should return a Dictionary with sensorID.getID() as key and relaated calibration results as value
145 """
146
147 @abstractmethod
148 def get_graph_value(self, sensor_db_content):
149 """
150 Abstract method to get a value for each TGraph
151 Parameters:
152 sensor_db_content (Any): Calibration results of a module
153 """
154
155 def get_graph_value_from_hist(self, h2=None):
156 """
157 Method to get a value for each TGraph
158 Parameters:
159 h2 (TH2): If not none, get value from h2
160 """
161 return None
162
163 def set_hist_content(self, h2, sensor_db_content):
164 """
165 Method to set TH2 bins
166 Parameters:
167 h2 (TH2): TH2F object for handling values of a pixel matrix
168 sensor_db_content (Any): Calibration results of a module
169 """
170 pass
171
172 def fill_plots(self):
173 """
174 Method to fill plot objects
175 """
176 if self.exp < self.expstart:
177 self.expstart = self.exp
178 if self.run < self.runstart:
179 self.runstart = self.run
180 if self.run > self.runend:
181 self.runend = self.run
182
183 db_content = self.get_db_content()
184 values_dict = {}
185 pre_values_dict = {}
186 for sensorID in sensorID_list:
187 sensor_db_content = db_content[sensorID.getID()]
188 if self.graphs:
189 if not self.use_hist:
190 values_dict[sensorID.getID()] = self.get_graph_value(sensor_db_content)
191 gr = self.graphs[sensorID.getID()]
192 pre_values_dict[sensorID.getID()] = gr.GetPointY(gr.GetN() - 1)
193 # Saving graph points only for different values (sensor-wise)
194 # value = self.get_graph_value(sensor_db_content)
195 # pre_value = gr.GetPointY(gr.GetN()-1)
196 # if value != pre_value:
197 # gr.SetPoint(gr.GetN(), self.run, value)
198 # Saving masked map
199 if self.rundir != "" and self.hists:
200 h2 = self.hists[sensorID.getID()]
201 self.set_hist_content(h2, sensor_db_content)
202 # Update title with total count
203 h2.SetTitle(h2.GetTitle() + self.hist_title_suffix)
204 self.tfile.cd(self.rundir)
205 h2.Write()
206 if self.use_hist:
207 values_dict[sensorID.getID()] = self.get_graph_value_from_hist(h2)
208 # Saving graph points only for different conditions
209 if values_dict != pre_values_dict:
210 for sensorID in sensorID_list:
211 gr = self.graphs[sensorID.getID()]
212 # \cond false positive doxygen warning
213 gr.SetPoint(gr.GetN(), self.run, values_dict[sensorID.getID()])
214 # \endcond
215
216 def draw_plots(self, canvas=None, cname="", ymin=0., ymax=None, logy=False):
217 """
218 Method to draw plots on a TCanvas
219 Parameters:
220 canvas (TCanvas): ROOT TCanvas for plotting
221 canme (str): Name of the canvas
222 ymin (float): Minimum value of y-axis for plotting
223 ymax (float): Maximum of y-axis for plotting
224 logy (bool): Flag to use log scale for y-axis
225 """
226 if not canvas:
227 return
228 else:
229 canvas.Clear()
230 canvas.SetWindowSize(1000, 400)
231 canvas.SetLeftMargin(0.09)
232 canvas.SetRightMargin(0.05)
233 self.tfile.cd()
234 for i, sensorID in enumerate(sensorID_list):
235 sensor_id = sensorID.getID()
236 if i == 0:
237 if ymax is None:
238 ymax = np.array([np.array(gr.GetY()) for gr in list(self.graphs.values())
239 if isinstance(gr, ROOT.TGraph)]).max()
240 self.graphs[sensor_id].SetMaximum(ymax)
241 self.graphs[sensor_id].SetMinimum(ymin)
242 self.graphs[sensor_id].Draw("AP")
243 else:
244 self.graphs[sensor_id].Draw("P")
245 if self.graphs["TLegends"]:
246 for leg in self.graphs["TLegends"]:
247 leg.Draw()
248 self.save_canvas(canvas, cname, logy=logy)
249
250 def save_canvas(self, canvas, cname, logy=False):
251 """
252 Save TCanvas to png/pdf format and do not write it to the ROOT file by default
253 Parameters:
254 canvas (TCanvas): ROOT TCanvas for plotting
255 canme (str): Name of the canvas
256 logy (bool): Flag to use log scale for y-axis
257 """
258 if cname:
259 canvas.SetName(cname)
260 exp_run = f"e{self.expstart:05}_r{self.runstart:04}-{self.runend:04}"
261 # Draw Belle II label
262 latex_r.DrawLatex(0.95, 0.92, "Belle II Experiment: " + exp_run.replace("_", " "))
263 # Print and write TCanvas
264 if logy:
265 canvas.SetLogy(1)
266 canvas.Update()
267 canvas.Modified()
268 canvas.Print(f"{exp_run}_{cname}_vs_run_logy.png")
269 canvas.Print(f"{exp_run}_{cname}_vs_run_logy.pdf")
270 else:
271 canvas.SetLogy(0)
272 canvas.Print(f"{exp_run}_{cname}_vs_run.png")
273 canvas.Print(f"{exp_run}_{cname}_vs_run.pdf")
274 self.tfile.cd()
275
276
278 """
279 Checker for PXDOccupancyInfoPar
280 """
281
282 def __init__(self, name, tfile, rundir=""):
283 """
284 """
285 super().__init__(name, Belle2.PXDOccupancyInfoPar, tfile, "")
286
287 def define_graphs(self):
288 """
289 Method to define TGraph
290 """
291 super().define_graphs(ytitle="Occupancy (With Mask) [%]")
292
293 def get_db_content(self):
294 content_dic = {}
295 for sensorID in sensorID_list:
296 numID = sensorID.getID()
297 raw_occ = self.dbObj.getOccupancy(numID)
298 # The default value is -1. It seems 0 will not be updated into the payload.
299 content_dic[numID] = raw_occ if raw_occ >= 0 else 0
300
301 return content_dic
302
303 def get_graph_value(self, sensor_db_content):
304 return sensor_db_content * 100
305
306 def draw_plots(self, canvas=None, **kwargs):
307 """
308 Method to draw plots on a TCanvas
309 """
310 # We don't use raw occupanncy. They will be corrected after takig out dead pixels
311 pass
312 # super().draw_plots(canvas=canvas, cname="OccupancyWithMask", ymin=0., ymax=plot_type_dict["occ_masked"]["max"])
313
314
315class PXDMaskedPixelsChecker(ConditionCheckerBase):
316 """
317 Checker for PXDMaskedPixelPar
318 """
319
320 def __init__(self, name, tfile, rundir="maps"):
321 """
322 """
323 super().__init__(name, Belle2.PXDMaskedPixelPar, tfile, rundir)
324
325 def define_graphs(self):
326 """
327 Method to define TGraph
328 """
329 super().define_graphs(ytitle="Hot pixels [%]")
330
331 def define_hists(self):
332 """
333 Method to define TH2
334 """
335 super().define_hists(name="MaskedPixels", title="Masked Pixels", ztitle="isMasked")
336
337 def get_db_content(self):
338 return self.dbObj.getMaskedPixelMap()
339
340 def get_graph_value(self, sensor_db_content):
341 hotcount = len(sensor_db_content)
342
343 self.hist_title_suffix = f" ({hotcount} pixels)"
344 return hotcount * 100. / nPixels
345
346 def set_hist_content(self, h2, sensor_db_content):
347 # loop over all masked pixels
348 for pixelID in sensor_db_content:
349 uCell = int(pixelID / nVCells)
350 vCell = pixelID % nVCells
351 h2.SetBinContent(int(uCell + 1), int(vCell + 1), 1)
352
353 def draw_plots(self, canvas=None, cname="PXDHotPixel", ymin=0., ymax=plot_type_dict["hot"]["max"], **kwargs):
354 """
355 Method to draw plots on a TCanvas
356 """
357 super().draw_plots(canvas=canvas, cname=cname, ymin=ymin, ymax=ymax, **kwargs)
358
359
361 """
362 Checker for PXDDeadPixelPar
363 """
364
365 def __init__(self, name, tfile, rundir="maps", use_hist=True):
366 """
367 """
368 super().__init__(name, Belle2.PXDDeadPixelPar, tfile, rundir)
369
370 def define_graphs(self):
371 """
372 Method to define TGraph
373 """
374 super().define_graphs(ytitle="Dead pixels [%]")
375
376 def define_hists(self):
377 """
378 Method to define TH2
379 """
380 super().define_hists(name="DeadPixels", title="Dead Pixels", ztitle="isDead")
381
382 def get_db_content(self):
383 deadsensormap = self.dbObj.getDeadSensorMap()
384 deaddrainmap = self.dbObj.getDeadDrainMap()
385 deadrowmap = self.dbObj.getDeadRowMap()
386 deadsinglesmap = self.dbObj.getDeadSinglePixelMap()
387
388 content_dic = {}
389 for sensorID in sensorID_list:
390 numID = sensorID.getID()
391 content_dic[numID] = {}
392 if numID in deadsensormap:
393 content_dic[numID]["deadsensor"] = True
394 else:
395 content_dic[numID]["deadsensor"] = False
396 content_dic[numID]["deaddrains"] = deaddrainmap[numID]
397 content_dic[numID]["deadrows"] = deadrowmap[numID]
398 content_dic[numID]["deadsingles"] = deadsinglesmap[numID]
399 return content_dic
400
401 def get_graph_value(self, sensor_db_content):
402 deadcount = 0
403 if sensor_db_content["deadsensor"]:
404 deadcount = nPixels
405 else:
406 n_deaddrains = len(sensor_db_content["deaddrains"])
407 n_deadrows = len(sensor_db_content["deadrows"])
408 if n_deaddrains == 0 or n_deadrows == 0:
409 # Every dead drain counts for 192 dead pixels
410 deadcount = n_deaddrains * 192
411 # Every dead row counts for 250 dead pixels
412 # This can lead to double counting of dead pixel from dead drains
413 # The double counting can be avoided by using TH2.Integral().
414 deadcount += n_deadrows * 250
415 # Every dead single pixels
416 deadcount += len(sensor_db_content["deadsingles"])
417 else: # Using a histogram to avoid double counting
418 # Just create a temporary TH2
419 h2 = list(get_sensor_maps(sensorID_list=sensorID_list[0:1]).values())[0]
420 self.set_hist_content(h2, sensor_db_content)
421 deadcount = h2.Integral()
422
423 self.hist_title_suffix = f" ({deadcount} pixels)"
424 return min(deadcount, nPixels) * 100. / nPixels
425
427 return h2.Integral() * 100. / nPixels
428
429 def set_hist_content(self, h2, sensor_db_content):
430 # loop over all dead pixels
431 if sensor_db_content["deadsensor"]:
432 ones = np.ones(nPixels)
433 h2.SetContent(ones)
434 else:
435 for drainID in sensor_db_content["deaddrains"]:
436 for iGate in range(192):
437 uCell = drainID / 4
438 vCell = drainID % 4 + iGate * 4
439 h2.SetBinContent(int(uCell + 1), int(vCell + 1), 1)
440
441 for vCell in sensor_db_content["deadrows"]:
442 for uCell in range(nUCells):
443 h2.SetBinContent(int(uCell + 1), int(vCell + 1), 1)
444
445 for pixelID in sensor_db_content["deadsingles"]:
446 uCell = int(pixelID / nVCells)
447 vCell = pixelID % nVCells
448 h2.SetBinContent(int(uCell + 1), int(vCell + 1), 1)
449
450 def draw_plots(self, canvas=None, cname="PXDDeadPixel", ymin=0., ymax=plot_type_dict["dead"]["max"], **kwargs):
451 """
452 Method to draw plots on a TCanvas
453 """
454 super().draw_plots(canvas=canvas, cname=cname, ymin=ymin, ymax=ymax, **kwargs)
455
456
458 """
459 Checker for PXDGainMapPar
460 """
461
462 def __init__(self, name, tfile, rundir="maps"):
463 """
464 """
465 super().__init__(name, Belle2.PXDGainMapPar, tfile, rundir)
466
467 def define_graphs(self):
468 """
469 Method to define TGraph
470 """
471 super().define_graphs(ytitle="Gain / MC default")
472
473 def define_hists(self):
474 """
475 Method to define TH2
476 """
477 nU = self.dbObj.getBinsU()
478 nV = self.dbObj.getBinsV()
479 super().define_hists(name="GainMap", title="Gain / MC default", ztitle="Relative gain", nUCells=nU, nVCells=nV)
480
481 def get_db_content(self):
482 gainMap = self.dbObj.getCalibrationMap()
483 content_dic = {}
484 for sensorID in sensorID_list:
485 numID = sensorID.getID()
486 content_dic[numID] = np.array(list(gainMap[numID]))
487 return content_dic
488
489 def get_graph_value(self, sensor_db_content):
490 """
491 sensor_db_content (np.array): Array of gain factors of a module
492 """
493 return sensor_db_content.mean()
494
495 def set_hist_content(self, h2, sensor_db_content):
496 array2hist(sensor_db_content.reshape(h2.GetNbinsX(), h2.GetNbinsY()), h2)
497
498 def draw_plots(self, canvas=None, cname="PXDGain", ymin=0.5, ymax=2.5, **kwargs):
499 """
500 Method to draw plots on a TCanvas
501 """
502 super().draw_plots(canvas=canvas, cname=cname, ymin=ymin, ymax=ymax, **kwargs)
The payload telling which PXD pixel is dead (=Readout system does not receive signals)
The payload class for PXD gain corrections.
The payload telling which PXD pixel to mask (ignore)
The payload collecting some meta information from running the masking algorithm.
Class to access a DBObjPtr from Python.
Definition PyDBObj.h:50
a (simplified) python wrapper for StoreObjPtr.
Definition PyStoreObj.h:67
use_hist
Flag to get TGraph values from a histogram (TH2F)
rundir
Directory for writing histograms of each run.
dict hists
Dictionary of plots (TH1) to be saved for each run.
dict graphs
Dictionary of plots (TGraph) summarizing variables vs run.
draw_plots(self, canvas=None, cname="", ymin=0., ymax=None, logy=False)
__init__(self, name, objType, tfile, rundir="", use_hist=False)
define_hists(self, name=None, title=None, ztitle=None, **kwargs)
draw_plots(self, canvas=None, cname="PXDDeadPixel", ymin=0., ymax=plot_type_dict["dead"]["max"], **kwargs)
__init__(self, name, tfile, rundir="maps", use_hist=True)
draw_plots(self, canvas=None, cname="PXDGain", ymin=0.5, ymax=2.5, **kwargs)
draw_plots(self, canvas=None, cname="PXDHotPixel", ymin=0., ymax=plot_type_dict["hot"]["max"], **kwargs)