source: fact/tools/pyscripts/sandbox/dneise/timecal.py@ 13408

Last change on this file since 13408 was 13404, checked in by neise, 12 years ago
testing version
File size: 8.5 KB
Line 
1#!/usr/bin/python -tti
2#
3# calculation of DRS Time Calibration constants
4# initial implementation by Remo Dietlicher and Werner Lustermann (ETH Zurich)
5# based on C++ classes by Oliver Grimm (ETH Zurich)
6#
7# this python implementation was coded by
8# D. Neise (TU Dortmund) April 2012
9#
10import numpy as np
11from pyfact import RawData
12from drs_spikes import DRSSpikes
13from extractors import ZeroXing
14class CalculateDrsTimeCalibrationConstants( object ):
15 """ basic analysis class for the calculation of DRS time aperture jitter calibration constants
16 """
17
18 def __init__(self, dpath, cpath):
19 """
20 *dpath*: data file path, file should contain a periodic signal in eahc 9th channel
21 *cpath*: std DRS amplitude calibration path
22 """
23 #dfname = '/data00/fact-construction/raw/2011/11/24/20111124_113.fits.gz'
24 #calfname = '/data00/fact-construction/raw/2011/11/24/20111124_111.drs.fits.gz'
25 self.data_path = dpath
26 self.calib_path = cpath
27 self.run = RawData(dpath, cpath, return_dict = True)
28
29 if self.run.nroi != 1024:
30 raise TypeError('Time Calibration Data must have ROI=1024, ROI of '+dpath+' is: '+str(self.run.nroi))
31
32 self.despike = DRSSpikes()
33 self.zero_crossing_finder = ZeroXing(slope = -1) # -1 means falling edge
34 self.fsampling = 2. # sampling frequency
35 self.freq = 250. # testfrequency
36 self.P_nom = 1000./freq # nominal Period due to testfrequency
37 self.DAMPING = 0.02 # Damping applied to correction
38
39 # this ndarray will contain the best estimate of the true width of each slice after a __call__
40 # but it is used to determine the current best estimate inside the __call__ as well,
41 # since the algorithm is iteratively implemented
42 #self.time_calib = np.linspace( 0. , 512, num=1024, endpoint=False).repeat(run.npix/9).reshape( (1024,run.npix/9) ).transpose()
43 self.time_calib = np.ones( (self.run.npix/9, 1024) )/self.fsampling
44 def __call__(self):
45 """ the class-call method performs the actual calculation based on the input data
46 returns: tuple of length 160 containing tuples of length 1024 containing the length of each cell in ns
47 the class contains self.result after the call.
48 """
49 # loop over events
50 for event in self.run:
51 # in the next() method, which is called during looping,
52 # the data is automatically amplitude calibrated.
53 # we just need every 9th pixel, so this is a place
54 # for optimization
55 data = event['acal_data']
56
57 # slice out every 9th channel
58 niners_ids = range(8,self.run.npix,9) # [8, 17, 26, ..., 1430, 1439]
59 data = data[niners_ids,:]
60 start_cell = event['start_cells'][niners_ids,:]
61
62 data = despike( data )
63
64 # shift data down by the mean # possible in one line
65 # calculate mean
66 mean = data.mean(1)
67 # make mean in the same shape as data, for elementwise subtraction
68 mean = mean.repeat(data.shape[1]).reshape(data.shape)
69 # subtraction
70 data = data - mean
71
72 # find zero crossings with falling edge
73 # the output is a list, of lists, of slices.
74 # each slice, shows a zero crossing. in fact, is is the slice where
75 # the data is still positive ... in the next slice it is negative, (or just zero).
76 # This was called 'MeanXing' before
77 all_crossings = self.zero_crossing_finder(data)
78
79 # now we have to calculate the exact time of the crossings
80 # but in units of nanoseconds therefor we use self.time_calib
81 # rolled appropriately, and summed up in order to have the integral timing calibration
82 #
83 #the list, 'time_of_all_crossings' will contain sublists,
84 # which in turn will contain the times, in ns, for each zero-crossing
85 time_of_all_crossings = []
86 # the list, 'float_slice_of_all_crossings'
87 # is similar to the list above, but i just contains the position of the crossings
88 # in the pipeline, but more precisely than 'all_crossings', the position is linear interpolated
89 float_slice_of_all_crossings = []
90
91 for chip_id,crossings in enumerate(all_crossings):
92 time_of_all_crossings.append([])
93 float_slice_of_all_crossings.append([])
94 time = np.roll(self.time_calib[chip_id], -start_cell[chip_id]).cumsum()
95 for crossing in crossings:
96 # performing linear interpolation of time of zero crossing
97 # what fraction of a slide is the interpolated crossing away from the integral slice id
98 fraction_of_slice = data[chip_id,crossing] / (data[chip_id,crossing]-data[chip_id,crossing+1])
99 float_slice_of_all_crossings[-1].append(fraction_of_slice + crossing)
100 slope = time[crossing+1] - time[crossing]
101 time_of_crossing = time[crossing] + slope * fraction_of_slice
102 time_of_all_crossings[-1].append(time_of_crossing)
103
104 # Now one can use those two lists, which were just created
105 # to calculate the number(float) of slices between two consecutive crossings
106 # as well as the *measured* time between two crossings.
107 # on the other hand, the period of the test signal is known to be self.P_nom
108 # so in case the measured time is longer or shorter than the known Period
109 # the associated time of all involved slices
110 # is assumed to be a bit longer or short than 1./self.fsampling
111
112 # first we make the sublists of the lists to be numpy.arrays
113 for i in len(float_slice_of_all_crossings): #both list have equal length
114 float_slice_of_all_crossings[i] = np.array(float_slice_of_all_crossings[i])
115 time_of_all_crossings[i] = np.array(time_of_all_crossings[i])
116
117 # now we can easily calculate the measured periods using np.diff()
118 all_measured_periods = []
119 for chip_times in time_of_all_crossings:
120 all_measured_periods.append(np.diff(chip_times))
121
122 for chid,chip_periods in enumerate(all_measured_periods):
123
124 for i,period in enumerate(chip_periods):
125 #shortcuts to the involved slices
126 start = all_crossings[chip][i]
127 end = all_crossings[chip][i+1]
128 interval = end-start
129 rest = 1024-interval
130
131 delta_period = (self.P_nom - period) * self.DAMPING
132 # nominal_number_of_slices
133 nom_num_slices = period * self.fsampling
134
135 # now the delta_period is distributed to all slices
136 # I do not exactly understand the +1 here ... error prone!!!
137 # the following explanation assumes delta_period is positive
138 # the slices between start and end get a positive correction value
139 # This correction value is given by delta_period / nom_num_slices
140 pos_fraction = delta_period / nom_num_slices
141 # the total positive correction sums up to
142 total_positive_correction = interval * pos_fraction
143 # the total negative correction must sum up to the sampe value
144 total_negative_correction = rest * (interval * (pos_fraction/rest))
145 # so we can call the product following 'rest' the 'neg_fraction
146 neg_fraction = -1 * interval/rest * important
147 assert total_positive_correction - total_negative_correction == 0
148
149 correction = np.zeros(1024)
150 correction[start+1:end+1] = pos_fraction
151 correction[:start+1] = neg_fraction
152 correction[end+1:] = neg_fraction
153 assert correction.sum() == 0
154
155 # now we have to add these correction values to self.time_calib
156 self.time_calib[chip,:] += np.roll(correction, start_cell[chip])
Note: See TracBrowser for help on using the repository browser.