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
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_suffixhist_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_contentset_hist_content(h2, sensor_db_content)
417 deadcount = h2.Integral()
418
419 self.hist_title_suffixhist_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.
Definition: PXDGainMapPar.h:43
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
graphs
Dictionary of plots (TGraph) summarizing variables vs run.
use_hist
Flag to get TGraph values from a histogram (TH2F)
rundir
Directory for writing histograms of each run.
def save_canvas(self, canvas, cname, logy=False)
def draw_plots(self, canvas=None, cname="", ymin=0., ymax=None, logy=False)
def define_hists(self, name=None, title=None, ztitle=None, **kwargs)
hists
Dictionary of plots (TH1) to be saved for each run.
def __init__(self, name, objType, tfile, rundir="", use_hist=False)
def draw_plots(self, canvas=None, cname="PXDDeadPixel", ymin=0., ymax=plot_type_dict["dead"]["max"], **kwargs)
def __init__(self, name, tfile, rundir="maps", use_hist=True)
def __init__(self, name, tfile, rundir="maps")
def draw_plots(self, canvas=None, cname="PXDGain", ymin=0.5, ymax=2.5, **kwargs)
def draw_plots(self, canvas=None, cname="PXDHotPixel", ymin=0., ymax=plot_type_dict["hot"]["max"], **kwargs)