Belle II Software development
__init__.py
1#!/usr/bin/python
2
3
10
11"""\
12Utilities for PXD calibration and performance study
13"""
14
15import ROOT
16from ROOT import Belle2
17import pandas as pd
18import numpy as np
19
20
21# Helper functions for Belle2.VxdID
22def get_name(self, separator="_"):
23 """
24 Get PXD module name with a specific format
25 Parameters:
26 self: Belle2.VxdID
27 separator (str): separator between different (layer/ladder/sensor) numbers.
28 Return:
29 PXD module name, e.g., 1_1_1
30 """
31 return f"{self.getLayerNumber()}{separator}{self.getLadderNumber()}{separator}{self.getSensorNumber()}"
32
33
34def get_pxdid(self):
35 """
36 Get PXD module id: layer_id * 1000 + ladder_id * 10 + sensor_id
37 Parameters:
38 self: Belle2.VxdID
39 Return:
40 PXD module id, e.g., 1011
41 """
42 return self.getLayerNumber() * 1000 + self.getLadderNumber() * 10 + self.getSensorNumber()
43
44
45# Helper function for TGraph
46def graph_append(self, x, y):
47 """
48 Append a point to TGraph
49 Parameters:
50 self: ROOT.TGraph
51 x (float): x coordinate of the point
52 y (float): y coordinate of the point
53 """
54 self.SetPoint(self.GetN(), x, y)
55
56
57# Extend Belle2.VxdID
58Belle2.VxdID.get_name = get_name
59Belle2.VxdID.get_pxdid = get_pxdid
60# Extend ROOT classes
61ROOT.TGraph.append = graph_append
62
63# Sensor id list
64sensor_labels = [
65 "1.1.1", "1.1.2",
66 "1.2.1", "1.2.2",
67 "1.3.1", "1.3.2",
68 "1.4.1", "1.4.2",
69 "1.5.1", "1.5.2",
70 "1.6.1", "1.6.2",
71 "1.7.1", "1.7.2",
72 "1.8.1", "1.8.2",
73 "2.4.1", "2.4.2",
74 "2.5.1", "2.5.2"]
75sensorID_list = tuple([Belle2.VxdID(label) for label in sensor_labels])
76nsensors = len(sensorID_list)
77
78# Sensor matrix
79nUCells = 250
80nVCells = 768
81nPixels = 192000
82
83# Colors and styles for ROOT
84colors = tuple([
85 ROOT.kRed + 1, ROOT.kOrange + 1, ROOT.kYellow - 3, ROOT.kSpring + 5, ROOT.kGreen + 3,
86 ROOT.kCyan - 6, ROOT.kAzure - 5, ROOT.kAzure - 6, ROOT.kBlue + 3, ROOT.kViolet - 1])
87
88plot_configs = {
89 "LineWidth": 4,
90 "yTitleOffset": 0.9
91}
92
93
94def get_sensor_graphs(ytitle=""):
95 """
96 Create TGraphs and related TLegends
97 Parameters:
98 ytitle (str): Title of the y-axis
99 Return:
100 A dictionary using sensorID as key and TGraph as value. A special key "TLegends"
101 is used for the list of TLegend objects for drawing.
102 """
103 graphs = {}
104 legs = []
105 # Create TLegends
106 xlegl = [0.60, 0.57, 0.78, 0.75]
107 xlegr = [0.72, 0.65, 0.90, 0.83]
108 for i in range(4):
109 """
110 Display format (content in a TLegend is bracketed):
111 (marker1 /) (marker2 label1/label2) (marker3 /) (markter4 label3/label4)
112 """
113 legs.append(ROOT.TLegend(xlegl[i], 0.62, xlegr[i], 0.85))
114 legs[i].SetFillStyle(0)
115 legs[i].SetBorderSize(0)
116 legs[i].SetTextFont(43)
117 legs[i].SetTextSize(18)
118 # Create TGraphs and set TLegend for each sensor
119 for i, sensorID in enumerate(sensorID_list):
120 agraph = ROOT.TGraph()
121 # agraph.SetTitle(sensorID.get_name())
122 agraph.SetName(sensorID.get_name())
123 agraph.GetXaxis().SetTitle('run #')
124 agraph.GetYaxis().SetTitle(ytitle)
125 agraph.GetYaxis().SetTitleOffset(0.9)
126 agraph.SetLineColor(colors[int(i / 2)])
127 agraph.SetLineWidth(4)
128 agraph.SetMarkerColor(colors[int(i / 2)])
129 agraph.SetMarkerStyle(26 if i % 2 else 24)
130 graphs[sensorID.getID()] = agraph
131 # Set TLegend
132 i1 = 0
133 i2 = 1
134 if (i > 9):
135 i1 = 2
136 i2 = 3
137 if (i % 2):
138 legs[i1].AddEntry(agraph, sensor_labels[i - 1] + ' / ' + sensor_labels[i], 'p')
139 else:
140 legs[i2].AddEntry(agraph, ' / ', 'p')
141 graphs["TLegends"] = legs
142
143 return graphs
144
145
146def get_sensor_maps(
147 name="MaskedPixels", title="Masked Pixels", ztitle="isMasked", run=0,
148 nUCells=nUCells, nVCells=nVCells, sensorID_list=sensorID_list
149):
150 """
151 Create TH2F objects for saving pixel map
152 Parameters:
153 name (str): Name of a histogram
154 title (str): Title
155 ztitle (str): Label for z-axis
156 run (int): run number of the map, to be appended into the title
157 sensorID_list (list): List of sensorID objects for which histograms are created.
158 Return:
159 A dictionary using sensorID as key and TH2F as value.
160 """
161 hists = {}
162 for sensorID in sensorID_list:
163 hname = name + f"_{sensorID.get_name()}_run_{run:d}"
164 htitle = title + f" Sensor={sensorID.get_pxdid():d} Run={run:d}"
165 h2 = ROOT.TH2F(hname, htitle, nUCells, 0, nUCells, nVCells, 0, nVCells)
166 h2.GetXaxis().SetTitle("uCell")
167 h2.GetYaxis().SetTitle("vCell")
168 h2.GetZaxis().SetTitle(ztitle)
169 h2.SetStats(0)
170 hists[sensorID.getID()] = h2
171 return hists
172
173
174def df_calculate_eff(df, num="nTrackClusters", den="nTrackPoints", output_var="eff"):
175 """
176 Efficiency calculation with asymmetric errors for pandas DataFrame
177 Parameters:
178 df: pandas.DataFrame
179 num (str): column used as the numerator
180 den (str): column used as the denominator
181 output_var (str): column for saving efficiency
182 """
183 nums = df[num].to_numpy()
184 dens = df[den].to_numpy()
185 from hist_utils import array2hist
186 nBins = len(nums)
187 assert len(nums) == len(dens), "len(numerators) != len(denominators)"
188 h_num = ROOT.TH1I("h_num", "h_num", nBins, 0, nBins)
189 h_den = ROOT.TH1I("h_den", "h_den", nBins, 0, nBins)
190 array2hist(nums, h_num)
191 array2hist(dens, h_den)
192 h_eff = ROOT.TEfficiency(h_num, h_den)
193 df[output_var] = df[num] / df[den]
194 df[output_var+"_err_low"] = [h_eff.GetEfficiencyErrorLow(i+1) for i in range(nBins)]
195 df[output_var+"_err_up"] = [h_eff.GetEfficiencyErrorUp(i+1) for i in range(nBins)]
196
197
198def getH1Sigma68(h1, fill_random=False):
199 """
200 Helper function to get sigma68 from TH1
201 Parameters:
202 h1 (ROOT.TH1): TH1 object from ROOT
203 fill_random (bool): Flag to call TH1.FillRandom
204 Return:
205 sigma68
206 """
207 qs = np.array([0., 0.])
208 probs = np.array([0.16, 0.84])
209 if fill_random:
210 h1tmp = h1.Clone()
211 h1tmp.Reset()
212 h1tmp.FillRandom(h1, int(h1.GetEffectiveEntries()))
213 h1tmp.GetQuantiles(2, qs, probs)
214 else:
215 h1.GetQuantiles(2, qs, probs)
216 return (qs[1]-qs[0])
217
218
219def getH1Sigma68WithError(h1, n=300):
220 """
221 Helper function to get sigma68 and its error using TH1.FillRandom
222 Parameters:
223 h1 (ROOT.TH1): TH1 object from ROOT
224 n (int): number of randomly generated histograms for the estimation
225 Return:
226 estimatd sigma68 and its standard deviation
227 """
228 results = np.array([getH1Sigma68(h1, fill_random=True) for i in range(n)])
229 # return results.mean(), results.std()
230 return getH1Sigma68(h1), results.std()
231
232
233def getSigma68(array):
234 """
235 Helper function to get sigma68 and its error using numpy.array
236 Parameters:
237 array (numpy.array): target array
238 Return:
239 estimatd sigma68
240 """
241 q1 = np.quantile(array, 0.16, axis=0)
242 q2 = np.quantile(array, 0.84, axis=0)
243 return (q2-q1) # /2*1e4 # cm to um
244
245
246def getSigma68WithError(array, n=300):
247 """
248 Helper function to get sigma68 and its error using numpy.array
249 Parameters:
250 array (numpy.array): target array
251 n (int): number of bootstrap drawings
252 Return:
253 estimatd sigma68 and its standard deviation
254 """
255 bs = np.random.choice(array, (array.shape[0], n)) # bootstrap resampling
256 results = getSigma68(bs)
257 # return results.mean(), results.std()
258 return getSigma68(array), results.std()
259
260
261# Extend pandas.DataFrame
262pd.DataFrame.calculate_eff = df_calculate_eff
263
264# latex
265latex_l = ROOT.TLatex() # left aligned
266latex_l.SetTextFont(43)
267latex_l.SetNDC()
268latex_l.SetTextSize(24)
269latex_ls = ROOT.TLatex() # left aligned small
270latex_ls.SetTextFont(43)
271latex_ls.SetNDC()
272latex_ls.SetTextSize(19)
273latex_r = ROOT.TLatex() # right aligned
274latex_r.SetTextFont(43)
275latex_r.SetNDC()
276latex_r.SetTextSize(24)
277latex_r.SetTextAlign(31)
Class to uniquely identify a any structure of the PXD and SVD.
Definition: VxdID.h:33