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