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 self.hists.update(get_sensor_maps(name, title, ztitle, self.run, **kwargs))
137
138 @abstractmethod
139 def get_db_content(self):
140 """
141 Abstract method to get content of a payload
142 Should return a Dictionary with sensorID.getID() as key and relaated calibration results as value
143 """
144
145 @abstractmethod
146 def get_graph_value(self, sensor_db_content):
147 """
148 Abstract method to get a value for each TGraph
149 Parameters:
150 sensor_db_content (Any): Calibration results of a module
151 """
152
153 def get_graph_value_from_hist(self, h2=None):
154 """
155 Method to get a value for each TGraph
156 Parameters:
157 h2 (TH2): If not none, get value from h2
158 """
159 return None
160
161 def set_hist_content(self, h2, sensor_db_content):
162 """
163 Method to set TH2 bins
164 Parameters:
165 h2 (TH2): TH2F object for handling values of a pixel matrix
166 sensor_db_content (Any): Calibration results of a module
167 """
168 pass
169
170 def fill_plots(self):
171 """
172 Method to fill plot objects
173 """
174 if self.exp < self.expstart:
175 self.expstart = self.exp
176 if self.run < self.runstart:
177 self.runstart = self.run
178 if self.run > self.runend:
179 self.runend = self.run
180
181 db_content = self.get_db_content()
182 values_dict = {}
183 pre_values_dict = {}
184 for sensorID in sensorID_list:
185 sensor_db_content = db_content[sensorID.getID()]
186 if self.graphs:
187 if not self.use_hist:
188 values_dict[sensorID.getID()] = self.get_graph_value(sensor_db_content)
189 gr = self.graphs[sensorID.getID()]
190 pre_values_dict[sensorID.getID()] = gr.GetPointY(gr.GetN() - 1)
191 # Saving graph points only for different values (sensor-wise)
192 # value = self.get_graph_value(sensor_db_content)
193 # pre_value = gr.GetPointY(gr.GetN()-1)
194 # if value != pre_value:
195 # gr.SetPoint(gr.GetN(), self.run, value)
196 # Saving masked map
197 if self.rundir != "" and self.hists:
198 h2 = self.hists[sensorID.getID()]
199 self.set_hist_content(h2, sensor_db_content)
200 # Update title with total count
201 h2.SetTitle(h2.GetTitle() + self.hist_title_suffix)
202 self.tfile.cd(self.rundir)
203 h2.Write()
204 if self.use_hist:
205 values_dict[sensorID.getID()] = self.get_graph_value_from_hist(h2)
206 # Saving graph points only for different conditions
207 if values_dict != pre_values_dict:
208 for sensorID in sensorID_list:
209 gr = self.graphs[sensorID.getID()]
210 gr.SetPoint(gr.GetN(), self.run, values_dict[sensorID.getID()])
211
212 def draw_plots(self, canvas=None, cname="", ymin=0., ymax=None, logy=False):
213 """
214 Method to draw plots on a TCanvas
215 Parameters:
216 canvas (TCanvas): ROOT TCanvas for plotting
217 canme (str): Name of the canvas
218 ymin (float): Minimum value of y-axis for plotting
219 ymax (float): Maximum of y-axis for plotting
220 logy (bool): Flag to use log scale for y-axis
221 """
222 if not canvas:
223 return
224 else:
225 canvas.Clear()
226 canvas.SetWindowSize(1000, 400)
227 canvas.SetLeftMargin(0.09)
228 canvas.SetRightMargin(0.05)
229 self.tfile.cd()
230 for i, sensorID in enumerate(sensorID_list):
231 sensor_id = sensorID.getID()
232 if i == 0:
233 if ymax is None:
234 ymax = np.array([np.array(gr.GetY()) for gr in list(self.graphs.values())
235 if isinstance(gr, ROOT.TGraph)]).max()
236 self.graphs[sensor_id].SetMaximum(ymax)
237 self.graphs[sensor_id].SetMinimum(ymin)
238 self.graphs[sensor_id].Draw("AP")
239 else:
240 self.graphs[sensor_id].Draw("P")
241 if self.graphs["TLegends"]:
242 for leg in self.graphs["TLegends"]:
243 leg.Draw()
244 self.save_canvas(canvas, cname, logy=logy)
245
246 def save_canvas(self, canvas, cname, logy=False):
247 """
248 Save TCanvas to png/pdf format and do not write it to the ROOT file by default
249 Parameters:
250 canvas (TCanvas): ROOT TCanvas for plotting
251 canme (str): Name of the canvas
252 logy (bool): Flag to use log scale for y-axis
253 """
254 if cname:
255 canvas.SetName(cname)
256 exp_run = f"e{self.expstart:05}_r{self.runstart:04}-{self.runend:04}"
257 # Draw Belle II label
258 latex_r.DrawLatex(0.95, 0.92, "Belle II Experiment: " + exp_run.replace("_", " "))
259 # Print and write TCanvas
260 if logy:
261 canvas.SetLogy(1)
262 canvas.Update()
263 canvas.Modified()
264 canvas.Print(f"{exp_run}_{cname}_vs_run_logy.png")
265 canvas.Print(f"{exp_run}_{cname}_vs_run_logy.pdf")
266 else:
267 canvas.SetLogy(0)
268 canvas.Print(f"{exp_run}_{cname}_vs_run.png")
269 canvas.Print(f"{exp_run}_{cname}_vs_run.pdf")
270 self.tfile.cd()
271
272
274 """
275 Checker for PXDOccupancyInfoPar
276 """
277
278 def __init__(self, name, tfile, rundir=""):
279 """
280 """
281 super().__init__(name, Belle2.PXDOccupancyInfoPar, tfile, "")
282
283 def define_graphs(self):
284 """
285 Method to define TGraph
286 """
287 super().define_graphs(ytitle="Occupancy (With Mask) [%]")
288
289 def get_db_content(self):
290 content_dic = {}
291 for sensorID in sensorID_list:
292 numID = sensorID.getID()
293 raw_occ = self.dbObj.getOccupancy(numID)
294 # The default value is -1. It seems 0 will not be updated into the payload.
295 content_dic[numID] = raw_occ if raw_occ >= 0 else 0
296
297 return content_dic
298
299 def get_graph_value(self, sensor_db_content):
300 return sensor_db_content * 100
301
302 def draw_plots(self, canvas=None, **kwargs):
303 """
304 Method to draw plots on a TCanvas
305 """
306 # We don't use raw occupanncy. They will be corrected after takig out dead pixels
307 pass
308 # super().draw_plots(canvas=canvas, cname="OccupancyWithMask", ymin=0., ymax=plot_type_dict["occ_masked"]["max"])
309
310
311class PXDMaskedPixelsChecker(ConditionCheckerBase):
312 """
313 Checker for PXDMaskedPixelPar
314 """
315
316 def __init__(self, name, tfile, rundir="maps"):
317 """
318 """
319 super().__init__(name, Belle2.PXDMaskedPixelPar, tfile, rundir)
320
321 def define_graphs(self):
322 """
323 Method to define TGraph
324 """
325 super().define_graphs(ytitle="Hot pixels [%]")
326
327 def define_hists(self):
328 """
329 Method to define TH2
330 """
331 super().define_hists(name="MaskedPixels", title="Masked Pixels", ztitle="isMasked")
332
333 def get_db_content(self):
334 return self.dbObj.getMaskedPixelMap()
335
336 def get_graph_value(self, sensor_db_content):
337 hotcount = len(sensor_db_content)
338
339 self.hist_title_suffix = f" ({hotcount} pixels)"
340 return hotcount * 100. / nPixels
341
342 def set_hist_content(self, h2, sensor_db_content):
343 # loop over all masked pixels
344 for pixelID in sensor_db_content:
345 uCell = int(pixelID / nVCells)
346 vCell = pixelID % nVCells
347 h2.SetBinContent(int(uCell + 1), int(vCell + 1), 1)
348
349 def draw_plots(self, canvas=None, cname="PXDHotPixel", ymin=0., ymax=plot_type_dict["hot"]["max"], **kwargs):
350 """
351 Method to draw plots on a TCanvas
352 """
353 super().draw_plots(canvas=canvas, cname=cname, ymin=ymin, ymax=ymax, **kwargs)
354
355
357 """
358 Checker for PXDDeadPixelPar
359 """
360
361 def __init__(self, name, tfile, rundir="maps", use_hist=True):
362 """
363 """
364 super().__init__(name, Belle2.PXDDeadPixelPar, tfile, rundir)
365
366 def define_graphs(self):
367 """
368 Method to define TGraph
369 """
370 super().define_graphs(ytitle="Dead pixels [%]")
371
372 def define_hists(self):
373 """
374 Method to define TH2
375 """
376 super().define_hists(name="DeadPixels", title="Dead Pixels", ztitle="isDead")
377
378 def get_db_content(self):
379 deadsensormap = self.dbObj.getDeadSensorMap()
380 deaddrainmap = self.dbObj.getDeadDrainMap()
381 deadrowmap = self.dbObj.getDeadRowMap()
382 deadsinglesmap = self.dbObj.getDeadSinglePixelMap()
383
384 content_dic = {}
385 for sensorID in sensorID_list:
386 numID = sensorID.getID()
387 content_dic[numID] = {}
388 if numID in deadsensormap:
389 content_dic[numID]["deadsensor"] = True
390 else:
391 content_dic[numID]["deadsensor"] = False
392 content_dic[numID]["deaddrains"] = deaddrainmap[numID]
393 content_dic[numID]["deadrows"] = deadrowmap[numID]
394 content_dic[numID]["deadsingles"] = deadsinglesmap[numID]
395 return content_dic
396
397 def get_graph_value(self, sensor_db_content):
398 deadcount = 0
399 if sensor_db_content["deadsensor"]:
400 deadcount = nPixels
401 else:
402 n_deaddrains = len(sensor_db_content["deaddrains"])
403 n_deadrows = len(sensor_db_content["deadrows"])
404 if n_deaddrains == 0 or n_deadrows == 0:
405 # Every dead drain counts for 192 dead pixels
406 deadcount = n_deaddrains * 192
407 # Every dead row counts for 250 dead pixels
408 # This can lead to double counting of dead pixel from dead drains
409 # The double counting can be avoided by using TH2.Integral().
410 deadcount += n_deadrows * 250
411 # Every dead single pixels
412 deadcount += len(sensor_db_content["deadsingles"])
413 else: # Using a histogram to avoid double counting
414 # Just create a temporary TH2
415 h2 = list(get_sensor_maps(sensorID_list=sensorID_list[0:1]).values())[0]
416 self.set_hist_content(h2, sensor_db_content)
417 deadcount = h2.Integral()
418
419 self.hist_title_suffix = f" ({deadcount} pixels)"
420 return min(deadcount, nPixels) * 100. / nPixels
421
423 return h2.Integral() * 100. / nPixels
424
425 def set_hist_content(self, h2, sensor_db_content):
426 # loop over all dead pixels
427 if sensor_db_content["deadsensor"]:
428 ones = np.ones(nPixels)
429 h2.SetContent(ones)
430 else:
431 for drainID in sensor_db_content["deaddrains"]:
432 for iGate in range(192):
433 uCell = drainID / 4
434 vCell = drainID % 4 + iGate * 4
435 h2.SetBinContent(int(uCell + 1), int(vCell + 1), 1)
436
437 for vCell in sensor_db_content["deadrows"]:
438 for uCell in range(nUCells):
439 h2.SetBinContent(int(uCell + 1), int(vCell + 1), 1)
440
441 for pixelID in sensor_db_content["deadsingles"]:
442 uCell = int(pixelID / nVCells)
443 vCell = pixelID % nVCells
444 h2.SetBinContent(int(uCell + 1), int(vCell + 1), 1)
445
446 def draw_plots(self, canvas=None, cname="PXDDeadPixel", ymin=0., ymax=plot_type_dict["dead"]["max"], **kwargs):
447 """
448 Method to draw plots on a TCanvas
449 """
450 super().draw_plots(canvas=canvas, cname=cname, ymin=ymin, ymax=ymax, **kwargs)
451
452
454 """
455 Checker for PXDGainMapPar
456 """
457
458 def __init__(self, name, tfile, rundir="maps"):
459 """
460 """
461 super().__init__(name, Belle2.PXDGainMapPar, tfile, rundir)
462
463 def define_graphs(self):
464 """
465 Method to define TGraph
466 """
467 super().define_graphs(ytitle="Gain / MC default")
468
469 def define_hists(self):
470 """
471 Method to define TH2
472 """
473 nU = self.dbObj.getBinsU()
474 nV = self.dbObj.getBinsV()
475 super().define_hists(name="GainMap", title="Gain / MC default", ztitle="Relative gain", nUCells=nU, nVCells=nV)
476
477 def get_db_content(self):
478 gainMap = self.dbObj.getCalibrationMap()
479 content_dic = {}
480 for sensorID in sensorID_list:
481 numID = sensorID.getID()
482 content_dic[numID] = np.array(list(gainMap[numID]))
483 return content_dic
484
485 def get_graph_value(self, sensor_db_content):
486 """
487 sensor_db_content (np.array): Array of gain factors of a module
488 """
489 return sensor_db_content.mean()
490
491 def set_hist_content(self, h2, sensor_db_content):
492 array2hist(sensor_db_content.reshape(h2.GetNbinsX(), h2.GetNbinsY()), h2)
493
494 def draw_plots(self, canvas=None, cname="PXDGain", ymin=0.5, ymax=2.5, **kwargs):
495 """
496 Method to draw plots on a TCanvas
497 """
498 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)