Belle II Software development
cdst_timeResoNtuple.py
1#!/usr/bin/env python3
2
3
10
11# ---------------------------------------------------------------------------------------
12# Makes ntuple w/ variable size arrays to study single photon time resolution with di-muon data
13# by comparing photon times w.r.t leading PDF peak position.
14# Provides also full information of a PDF peak assocoated with a detected photon.
15# The following draw command must show a peak that represents an overall time resolution:
16# tree->Draw("time-t0>>h(100,-1,2)", "t0<10")
17#
18# Usage: basf2 cdst_timeResoNtuple.py -i <cdst_file-dimuon_skim.root>
19# ---------------------------------------------------------------------------------------
20
21import basf2 as b2
22from ROOT import Belle2
23from ROOT import TH1F, TH2F
24from ROOT import addressof, gROOT
25import math
26import ROOT
27
28# global tags
29# ******************************************************************************************************************
30# note: The patching global tags and their order are bucket number and basf2 version dependent.
31# Given below is what is needed for cdst files of bucket 16 calibration and January-2023 development version.
32# ******************************************************************************************************************
33b2.conditions.override_globaltags()
34b2.conditions.append_globaltag('patch_main_release-07_noTOP')
35b2.conditions.append_globaltag('data_reprocessing_proc13') # experiments 7 - 18
36# b2.conditions.append_globaltag('data_reprocessing_prompt') # experiments 20 - 26
37b2.conditions.append_globaltag('online')
38
39
40gROOT.ProcessLine('struct TreeStruct {\
41 int run; /* run number */ \
42 int itrk; /* track counter */ \
43 float offset; /* current bunch offset */ \
44 int usedTrk; /* number of tracks used for bunch reconstruction */ \
45 int slot; /* slot ID */ \
46 float p; /* extHit momentum */ \
47 float cth; /* extHit local cos(theta) (global must be pretty much the same) */ \
48 float phi; /* extHit local phi */ \
49 float z; /* extHit local z */ \
50 float x; /* extHit local x */ \
51 float tof; /* extHit time-of-flight */ \
52 int charge; /* particle charge */ \
53 float poca_x; /* POCA x */ \
54 float poca_y; /* POCA y */ \
55 float poca_z; /* POCA z */ \
56 int hitsCDC; /* number of CDC hits */ \
57 float Ecms; /* CMS energy of muon */ \
58 float rec_t0; /* t0 of this track determined with bunch finder */ \
59 int valid_t0; /* reconstruction status of t0 */ \
60 int nfot; /* number of photons */ \
61 int channel[1000]; /* channel */ \
62 int pixel[1000] ; /* pixel ID */ \
63 float time[1000]; /* photon time */ \
64 float timeErr[1000]; /* photon time uncertainty */ \
65 int pulseHeight[1000]; /* photon pulse height */ \
66 float pulseWidth[1000]; /* photon pulse width */ \
67 int sample[1000]; /* sample number modulo 256 */ \
68 int status[1000]; /* calibration status bits */ \
69 float t0[1000]; /* leading PDF peak: position */ \
70 float wid0[1000]; /* leading PDF peak: width w/o TTS */ \
71 float t1[1000]; /* next to leading PDF peak: position */ \
72 float t_pdf[1000]; /* associated pdf peak time */ \
73 float wid[1000]; /* associated pdf peak width */ \
74 int type[1000]; /* 0 bkg, 1 direct, 2 reflected */ \
75 int nx[1000]; /* total number of reflections in x */ \
76 int ny[1000]; /* total number of reflections in y */ \
77 int nxm[1000]; /* number of reflections in x before mirror */ \
78 int nym[1000]; /* number of reflections in y before mirror */ \
79 int nxe[1000]; /* number of reflections in x in prism */ \
80 int nye[1000]; /* number of reflections in y in prism */ \
81 int nys[1000]; /* number of reflections on slanted surface of prism */ \
82 float fic[1000]; /* Cherenkov azimuthal angle in degrees */ \
83 float xd[1000]; /* unfolded x coordinate of a pixel */ \
84 float yd[1000]; /* unfolded y coordinate of a pixel */ \
85 float xm[1000]; /* unfolded x coordinate of a reconstructed point on mirror */ \
86 float kx[1000]; /* reconstructed photon direction in x at emission */ \
87 float ky[1000]; /* reconstructed photon direction in y at emission */ \
88 float alpha[1000]; /* impact angle on photo-cathode [degrees] */ \
89};')
90
91int_arrays = ['channel', 'pixel', 'pulseHeight', 'sample', 'status', 'type', 'nx', 'ny',
92 'nxm', 'nym', 'nxe', 'nye', 'nys']
93float_arrays = ['time', 'timeErr', 'pulseWidth', 't0', 'wid0', 't1', 't_pdf', 'wid', 'alpha', 'fic',
94 'xd', 'yd', 'xm', 'kx', 'ky']
95
96from ROOT import TreeStruct # noqa
97
98
99class Ntuple(b2.Module):
100 ''' Makes a flat ntuple '''
101
102
103 nhisto = 0
104
105 def initialize(self):
106 ''' initialize: open root file, construct ntuple '''
107
108 evtMetaData = Belle2.PyStoreObj('EventMetaData')
109 expNo = evtMetaData.obj().getExperiment()
110 runNo = evtMetaData.obj().getRun()
111 exp_run = '-e' + f'{expNo:04d}' + '-r' + f'{runNo:05d}'
112 outName = 'timeResoNtuple' + exp_run + '.root'
113
114
116 self.trk_selector.setDeltaEcms(0.1)
117 self.trk_selector.setCutOnPOCA(2.0, 4.0)
118 self.trk_selector.setCutOnLocalZ(-130.0, 130.0)
119
120
121 self.file = ROOT.TFile(outName, 'recreate')
122
123
124 self.bunchOffset = TH1F("bunchOffset", "bunch offset", 100, -1.0, 1.0)
125 self.bunchOffset.SetXTitle("bunch offset [ns]")
126
127 self.h_cth_vs_p = TH2F("cth_vs_p", "local cos #theta vs. p", 100, 0.0, 10.0,
128 100, -1.0, 1.0)
129 self.h_cth_vs_p.SetXTitle("p [GeV/c]")
130 self.h_cth_vs_p.SetYTitle("cos #theta")
131
132 self.h_momentum = TH1F("momentum", "momentum", 100, 0.0, 10.0)
133 self.h_momentum.SetXTitle("p [GeV/c]")
134
135 self.h_cth = TH1F("cos_theta", "local cos #theta", 100, -1.0, 1.0)
136 self.h_cth.SetXTitle("cos #theta")
137
138 self.h_phi = TH1F("local_phi", "local phi", 100, -math.pi, math.pi)
139 self.h_phi.SetXTitle("local #phi")
140
141 self.h_z = TH1F("local_z", "local z", 100, -140.0, 140.0)
142 self.h_z.SetXTitle("local z [cm]")
143
144 self.h_x = TH1F("local_x", "local x", 100, -23.0, 23.0)
145 self.h_x.SetXTitle("local x [cm]")
146
147 self.h_charge = TH1F("charge", "charge", 3, -1.5, 1.5)
148 self.h_charge.SetXTitle("charge")
149
150 self.h_poca_xy = TH2F("poca_xy", "POCA distribution in x-y", 100, -1.0, 1.0,
151 100, -1.0, 1.0)
152 self.h_poca_xy.SetXTitle("x [cm]")
153 self.h_poca_xy.SetYTitle("y [cm]")
154
155 self.h_poca_z = TH1F("poca_z", "POCA distribution in z", 100, -2.0, 2.0)
156 self.h_poca_z.SetXTitle("z [cm]")
157
158 self.h_hitsCDC = TH1F("cdc_hits", "CDC hits", 100, 0.0, 100.0)
159 self.h_hitsCDC.SetXTitle("number of CDC hits")
160
161 self.h_Ecms = TH1F("Ecms", "c.m.s. energy of muon", 300, 4.5, 6.0)
162 self.h_Ecms.SetXTitle("E_{cm} [GeV/c]")
163
164
165 self.tree = ROOT.TTree('tree', 'time resolution')
166
167 self.data = TreeStruct()
168 self.data.itrk = 0
169
170 for key in TreeStruct.__dict__.keys():
171 if '__' not in key:
172 formstring = '/F'
173 if isinstance(self.data.__getattribute__(key), int):
174 formstring = '/I'
175 if key in int_arrays:
176 formstring = '[nfot]/I'
177 elif key in float_arrays:
178 formstring = '[nfot]/F'
179 self.tree.Branch(key, addressof(self.data, key), key + formstring)
180
181 def sortPeaks(self, unsortedPeaks):
182 ''' sort PDF peaks according to their positions '''
183
184 py_list = [x for x in unsortedPeaks]
185 return sorted(py_list, key=lambda x: (x.mean))
186
187 def pdfHistogram(self, pdf):
188 ''' make histogram of PDF peak positions for the first 20 tracks '''
189
190 self.nhisto += 1
191 if self.nhisto > 20:
192 return
193 h = TH2F('pdf' + str(self.nhisto), 'muon PDF, itrk = ' + str(self.data.itrk),
194 512, 0.0, 512.0, 1000, 0.0, 75.0)
195 h.SetXTitle('pixelID - 1')
196 h.SetYTitle('peak positions [ns]')
197 for x in range(512):
198 peaks = pdf[x]
199 for peak in peaks:
200 h.Fill(x, peak.mean)
201 h.Write()
202
203 def event(self):
204 ''' event processing '''
205
206 recBunch = Belle2.PyStoreObj('TOPRecBunch')
207 if not recBunch:
208 b2.B2ERROR('no TOPRecBunch')
209 return
210 if not recBunch.isReconstructed():
211 return
212 self.bunchOffset.Fill(recBunch.getCurrentOffset())
213
214 evtMetaData = Belle2.PyStoreObj('EventMetaData')
215 self.data.run = evtMetaData.getRun()
216 self.data.offset = recBunch.getCurrentOffset()
217 self.data.usedTrk = recBunch.getUsedTracks()
218
219 for track in Belle2.PyStoreArray('Tracks'):
220 trk = Belle2.TOP.TOPTrack(track)
221 if not trk.isValid():
222 continue
223 if not self.trk_selector.isSelected(trk):
224 continue
225 pdfs = track.getRelated('TOPPDFCollections')
226 if not pdfs:
227 continue
228 self.data.slot = pdfs.getModuleID()
229 momentum = pdfs.getAssociatedLocalMomentum()
230 position = pdfs.getAssociatedLocalHit()
231 self.data.p = momentum.R()
232 self.data.cth = math.cos(momentum.Theta())
233 self.data.phi = momentum.Phi()
234 self.data.z = position.Z()
235 self.data.x = position.X()
236 self.data.tof = trk.getTOF(Belle2.Const.muon)
237 try:
238 tfit = track.getTrackFitResultWithClosestMass(Belle2.Const.muon)
239 except BaseException:
240 b2.B2ERROR("No trackFitResult available")
241 continue
242 self.data.charge = tfit.getChargeSign()
243 pocaPosition = tfit.getPosition()
244 self.data.poca_x = pocaPosition.X()
245 self.data.poca_y = pocaPosition.Y()
246 self.data.poca_z = pocaPosition.Z()
247 self.data.hitsCDC = tfit.getHitPatternCDC().getNHits()
248 self.data.Ecms = self.trk_selector.getCMSEnergy()
249 try:
250 extHit = trk.getExtHit()
251 timeZero = extHit.getRelated('TOPTimeZeros')
252 self.data.rec_t0 = timeZero.getTime()
253 self.data.valid_t0 = timeZero.isValid()
254 except BaseException:
255 self.data.rec_t0 = 0
256 self.data.valid_t0 = 0
257
258 self.h_cth_vs_p.Fill(self.data.p, self.data.cth)
259 self.h_momentum.Fill(self.data.p)
260 self.h_cth.Fill(self.data.cth)
261 self.h_phi.Fill(self.data.phi)
262 self.h_z.Fill(self.data.z)
263 self.h_x.Fill(self.data.x)
264 self.h_charge.Fill(self.data.charge)
265 self.h_poca_xy.Fill(self.data.poca_x, self.data.poca_y)
266 self.h_poca_z.Fill(self.data.poca_z)
267 self.h_hitsCDC.Fill(self.data.hitsCDC)
268 self.h_Ecms.Fill(self.data.Ecms)
269 try:
270 pdf = pdfs.getHypothesisPDF(13)
271 except BaseException:
272 b2.B2ERROR("No PDF available for PDG = 13")
273 continue
274 self.data.itrk += 1
275 self.pdfHistogram(pdf)
276 emi = trk.getEmissionPoint()
277 x0 = emi.position.X()
278 z0 = emi.position.Y()
279 self.data.nfot = 0
280 for digit in Belle2.PyStoreArray('TOPDigits'):
281 if digit.getModuleID() == self.data.slot and digit.getHitQuality() == 1:
282 k = self.data.nfot
283 if k >= 1000:
284 continue
285 self.data.nfot += 1
286 self.data.channel[k] = digit.getChannel()
287 self.data.pixel[k] = digit.getPixelID()
288 self.data.time[k] = digit.getTime()
289 self.data.timeErr[k] = digit.getTimeError()
290 self.data.pulseHeight[k] = digit.getPulseHeight()
291 self.data.pulseWidth[k] = digit.getPulseWidth()
292 self.data.sample[k] = digit.getModulo256Sample()
293 self.data.status[k] = digit.getStatus()
294 peaks = pdf[digit.getPixelID() - 1]
295 if peaks.empty():
296 self.data.t0[k] = 0
297 self.data.wid0[k] = 0
298 self.data.t1[k] = 0
299 else:
300 sorted_peaks = self.sortPeaks(peaks)
301 self.data.t0[k] = sorted_peaks[0].mean
302 self.data.wid0[k] = sorted_peaks[0].width
303 self.data.t1[k] = self.data.t0[k] + 100
304 if peaks.size() > 1:
305 self.data.t1[k] = sorted_peaks[1].mean
306 self.data.t_pdf[k] = 0
307 self.data.wid[k] = 0
308 self.data.fic[k] = 0
309 self.data.type[k] = 0
310 self.data.nx[k] = 0
311 self.data.ny[k] = 0
312 self.data.nxm[k] = 0
313 self.data.nym[k] = 0
314 self.data.nxe[k] = 0
315 self.data.nye[k] = 0
316 self.data.nys[k] = 0
317 self.data.xd[k] = 0
318 self.data.yd[k] = 0
319 self.data.xm[k] = 0
320 self.data.kx[k] = 0
321 self.data.ky[k] = 0
322 self.data.alpha[k] = 0
323 assocPDF = digit.getRelated('TOPAssociatedPDFs')
324 if assocPDF:
325 pik = assocPDF.getSinglePeak()
326 if pik:
327 self.data.t_pdf[k] = pik.position
328 self.data.wid[k] = pik.width
329 self.data.fic[k] = math.degrees(pik.fic)
330 self.data.type[k] = pik.type
331 self.data.nx[k] = pik.nx
332 self.data.ny[k] = pik.ny
333 self.data.nxm[k] = pik.nxm
334 self.data.nym[k] = pik.nym
335 self.data.nxe[k] = pik.nxe
336 self.data.nye[k] = pik.nye
337 if pik.kyd > 0:
338 self.data.nys[k] = int(pik.nye / 2)
339 else:
340 self.data.nys[k] = int((pik.nye + 1) / 2)
341 self.data.xd[k] = pik.xd
342 self.data.yd[k] = pik.yd
343 if pik.kze > 0:
344 self.data.xm[k] = x0 + pik.kxe / pik.kze * (130.0 - z0)
345 self.data.kx[k] = pik.kxe
346 self.data.ky[k] = pik.kye
347 self.data.alpha[k] = math.degrees(math.acos(abs(pik.kzd)))
348
349 self.tree.Fill()
350
351 def terminate(self):
352 ''' terminate: close root file '''
353
354 self.file.cd()
355 self.file.Write()
356 self.file.Close()
357
358
359# Create path
360main = b2.create_path()
361
362# Input: cdst file(s), use -i option
363main.add_module('RootInput')
364
365# Reconstruction: TOP only
366main.add_module('Gearbox')
367main.add_module('Geometry')
368main.add_module('Ext')
369main.add_module('TOPUnpacker')
370main.add_module('TOPRawDigitConverter')
371main.add_module('TOPChannelMasker')
372main.add_module('TOPBunchFinder')
373
374# Make a muon PDF available at datastore
375main.add_module('TOPPDFDebugger', pdgCodes=[13]) # default
376
377# Write ntuple
378main.add_module(Ntuple())
379
380# Print progress
381progress = b2.register_module('Progress')
382main.add_module(progress)
383
384# Process events
385b2.process(main)
386
387# Print call statistics
388print(b2.statistics)
A (simplified) python wrapper for StoreArray.
Definition: PyStoreArray.h:72
a (simplified) python wrapper for StoreObjPtr.
Definition: PyStoreObj.h:67
Reconstructed track at TOP.
Definition: TOPTrack.h:39
Utility for the track selection - used in various calibration modules.
Definition: TrackSelector.h:27
trk_selector
track selector - selection of muons from ee->mumu events
def sortPeaks(self, unsortedPeaks)
h_cth
histogram of cos(theta)
h_cth_vs_p
histogram cos(theta) vs, momentum
h_phi
histogram of local phi
h_hitsCDC
histogram of number of hits in CDC
h_poca_xy
histogram of POCA x-y
h_momentum
histogram of momentum
bunchOffset
histogram of bunch offset