pytesprocess 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytesprocess/__init__.py +9 -0
- pytesprocess/_version.py +2 -0
- pytesprocess/cli/__init__.py +1 -0
- pytesprocess/cli/commands/__init__.py +5 -0
- pytesprocess/cli/commands/event.py +66 -0
- pytesprocess/cli/commands/filter.py +17 -0
- pytesprocess/cli/commands/ivsweep.py +29 -0
- pytesprocess/cli/common.py +86 -0
- pytesprocess/cli/main.py +81 -0
- pytesprocess/config/__init__.py +4 -0
- pytesprocess/config/loader.py +94 -0
- pytesprocess/config/manager.py +297 -0
- pytesprocess/config/resolvers/__init__.py +5 -0
- pytesprocess/config/resolvers/common.py +56 -0
- pytesprocess/config/resolvers/feature.py +293 -0
- pytesprocess/config/resolvers/salting.py +86 -0
- pytesprocess/config/resolvers/trigger.py +84 -0
- pytesprocess/config/selectors.py +108 -0
- pytesprocess/config/validation.py +314 -0
- pytesprocess/config/warnings.py +2 -0
- pytesprocess/core/__init__.py +10 -0
- pytesprocess/core/algorithms.py +1455 -0
- pytesprocess/core/didv.py +1648 -0
- pytesprocess/core/eventbuilder.py +495 -0
- pytesprocess/core/filterbuilder.py +81 -0
- pytesprocess/core/filterdata.py +1849 -0
- pytesprocess/core/ivsweep.py +2072 -0
- pytesprocess/core/noise.py +923 -0
- pytesprocess/core/noisemodel.py +1408 -0
- pytesprocess/core/oftrigger.py +1035 -0
- pytesprocess/core/template.py +450 -0
- pytesprocess/process/__init__.py +6 -0
- pytesprocess/process/data_source.py +185 -0
- pytesprocess/process/event_context.py +35 -0
- pytesprocess/process/feature_plan.py +186 -0
- pytesprocess/process/feature_resources.py +267 -0
- pytesprocess/process/features.py +1024 -0
- pytesprocess/process/filterprocess.py +1176 -0
- pytesprocess/process/ivprocess.py +1380 -0
- pytesprocess/process/processing_data.py +967 -0
- pytesprocess/process/randoms.py +921 -0
- pytesprocess/process/triggers.py +1011 -0
- pytesprocess/salting/__init__.py +7 -0
- pytesprocess/salting/generator.py +364 -0
- pytesprocess/salting/injector.py +329 -0
- pytesprocess/salting/sampling.py +84 -0
- pytesprocess/utils/__init__.py +5 -0
- pytesprocess/utils/arg_utils.py +122 -0
- pytesprocess/utils/dataframe_output.py +120 -0
- pytesprocess/utils/filter_hdf5.py +594 -0
- pytesprocess/utils/utils.py +701 -0
- pytesprocess/workflows/__init__.py +3 -0
- pytesprocess/workflows/processing.py +317 -0
- pytesprocess/workflows/salting.py +133 -0
- pytesprocess-0.1.1.dist-info/METADATA +211 -0
- pytesprocess-0.1.1.dist-info/RECORD +60 -0
- pytesprocess-0.1.1.dist-info/WHEEL +5 -0
- pytesprocess-0.1.1.dist-info/entry_points.txt +2 -0
- pytesprocess-0.1.1.dist-info/licenses/LICENSE +21 -0
- pytesprocess-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1455 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import qetpy as qp
|
|
3
|
+
from scipy import signal
|
|
4
|
+
from scipy.optimize import curve_fit
|
|
5
|
+
|
|
6
|
+
from pytesprocess.utils import utils
|
|
7
|
+
|
|
8
|
+
# NumPy 2.0 introduced ``np.trapezoid`` as the replacement for
|
|
9
|
+
# ``np.trapz``; ``np.trapz`` was later removed. Keep compatibility
|
|
10
|
+
# with both NumPy 1.x and newer NumPy releases.
|
|
11
|
+
try:
|
|
12
|
+
_trapezoid = np.trapezoid
|
|
13
|
+
except AttributeError: # NumPy < 2.0
|
|
14
|
+
_trapezoid = np.trapz
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
'FeatureExtractors',
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FeatureExtractors:
|
|
23
|
+
"""
|
|
24
|
+
A class that contains all of the possible feature extractors
|
|
25
|
+
for a given trace, assuming processing on a single channel.
|
|
26
|
+
Each feature extraction function is a staticmethod for processing
|
|
27
|
+
convenience.
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def ofnxmx2(channel,
|
|
33
|
+
of_base,
|
|
34
|
+
available_channels=None,
|
|
35
|
+
feature_base_name='ofnxmx2',
|
|
36
|
+
template_tag=None,
|
|
37
|
+
amplitude_names=None,
|
|
38
|
+
fit_window = None,
|
|
39
|
+
**kwargs):
|
|
40
|
+
"""
|
|
41
|
+
Feature extraction for the nxmx2 Optimum Filter.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
channel : str
|
|
46
|
+
channel with format 'chan1|chan2|chan3'
|
|
47
|
+
(order matter)
|
|
48
|
+
|
|
49
|
+
of_base : OFBase object
|
|
50
|
+
OFBase QETpy object
|
|
51
|
+
|
|
52
|
+
lowchi2_fcutoff : float, optional
|
|
53
|
+
The frequency (in Hz) that we should cut off the chi^2 when
|
|
54
|
+
calculating the low frequency chi^2. Default is 10 kHz. (NOT IMPLEMENTED)
|
|
55
|
+
|
|
56
|
+
available_channels : list
|
|
57
|
+
list of available channels
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
retdict : dict
|
|
63
|
+
Dictionary containing the various extracted features.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# split channel name into list (same order)
|
|
69
|
+
channel_list, separator = utils.split_channel_name(
|
|
70
|
+
channel,
|
|
71
|
+
available_channels=available_channels,
|
|
72
|
+
separator='|')
|
|
73
|
+
|
|
74
|
+
nchans = len(channel_list)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if template_tag is None:
|
|
78
|
+
raise ValueError(f'ERROR: Missing "template_tag" argument '
|
|
79
|
+
f'for channel {channel}, '
|
|
80
|
+
f'algorithm "{feature_base_name}"')
|
|
81
|
+
|
|
82
|
+
# time constraints tag is the name of algorithm
|
|
83
|
+
time_constraints_tag = feature_base_name
|
|
84
|
+
|
|
85
|
+
# check template available
|
|
86
|
+
template = of_base.template(channel,
|
|
87
|
+
template_tag=template_tag)
|
|
88
|
+
if template is None:
|
|
89
|
+
raise ValueError(f'ERROR: Missing template '
|
|
90
|
+
f'for channel {channel}, '
|
|
91
|
+
f'tag "{template_tag}", '
|
|
92
|
+
f'algorithm "{feature_base_name}"')
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
ntmps = template.shape[1]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# amplitude names
|
|
99
|
+
if amplitude_names is None:
|
|
100
|
+
amplitude_names = []
|
|
101
|
+
for itmp in range(ntmps):
|
|
102
|
+
amplitude_names.append(f'amp{itmp+1}')
|
|
103
|
+
|
|
104
|
+
else:
|
|
105
|
+
|
|
106
|
+
if isinstance(amplitude_names, str):
|
|
107
|
+
amplitude_names = [amplitude_names]
|
|
108
|
+
|
|
109
|
+
if len(amplitude_names) != ntmps:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
f'ERROR: Wrong length for "amplitude_names" '
|
|
112
|
+
f'argument. Expecting {ntmps} name '
|
|
113
|
+
f'for channel {channel}, '
|
|
114
|
+
f'algorithm "{feature_base_name}"')
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# initalize dictionary
|
|
119
|
+
retdict = dict()
|
|
120
|
+
retdict[f'chi2_{feature_base_name}'] = -999999.0,
|
|
121
|
+
retdict[f'delta_t_{feature_base_name}'] = -999999.0,
|
|
122
|
+
for iamp, amp_name in enumerate(amplitude_names):
|
|
123
|
+
retdict[f'{amp_name}_{feature_base_name}'] = -999999.0
|
|
124
|
+
|
|
125
|
+
# check if signal stored
|
|
126
|
+
if not of_base.is_signal_stored(channel):
|
|
127
|
+
return retdict
|
|
128
|
+
|
|
129
|
+
# instantiate OF NxM
|
|
130
|
+
OF = qp.OFnxmx2(of_base=of_base,
|
|
131
|
+
channels=channel,
|
|
132
|
+
template_tag=template_tag,
|
|
133
|
+
time_constraints_tag=time_constraints_tag,
|
|
134
|
+
verbose=False)
|
|
135
|
+
|
|
136
|
+
# calc
|
|
137
|
+
OF.calc()
|
|
138
|
+
|
|
139
|
+
amps, deltat, chi2 = OF.get_fit()
|
|
140
|
+
|
|
141
|
+
retdict[f'chi2_{feature_base_name}'] = chi2
|
|
142
|
+
retdict[f'delta_t_{feature_base_name}'] = deltat
|
|
143
|
+
for iamp, amp_name in enumerate(amplitude_names):
|
|
144
|
+
retdict[f'{amp_name}_{feature_base_name}'] = amps[iamp]
|
|
145
|
+
|
|
146
|
+
return retdict
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def ofnxm(channel, of_base,
|
|
150
|
+
available_channels=None,
|
|
151
|
+
feature_base_name='ofnxm',
|
|
152
|
+
template_tag=None,
|
|
153
|
+
amplitude_names=None,
|
|
154
|
+
window_min_from_trig_usec=None,
|
|
155
|
+
window_max_from_trig_usec=None,
|
|
156
|
+
window_min_index=None,
|
|
157
|
+
window_max_index=None,
|
|
158
|
+
lgc_outside_window=False,
|
|
159
|
+
lowchi2_fcutoff=10000,
|
|
160
|
+
interpolate_t0=False,
|
|
161
|
+
**kwargs):
|
|
162
|
+
"""
|
|
163
|
+
Feature extraction for the NxM Optimum Filter.
|
|
164
|
+
Returns both constrained and nodelay paramaters.
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
channel : str
|
|
170
|
+
channel with format 'chan1|chan2|chan3'
|
|
171
|
+
(order matter)
|
|
172
|
+
|
|
173
|
+
of_base : OFBase object
|
|
174
|
+
OFBase QETpy object
|
|
175
|
+
|
|
176
|
+
lowchi2_fcutoff : float, optional
|
|
177
|
+
The frequency (in Hz) that we should cut off the chi^2 when
|
|
178
|
+
calculating the low frequency chi^2. Default is 10 kHz.
|
|
179
|
+
|
|
180
|
+
available_channels : list
|
|
181
|
+
list of available channels
|
|
182
|
+
|
|
183
|
+
feature_base_name : str, option
|
|
184
|
+
output feature base name
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
retdict : dict
|
|
190
|
+
Dictionary containing the various extracted features.
|
|
191
|
+
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
debug = False
|
|
195
|
+
|
|
196
|
+
# split channel name into list (same order)
|
|
197
|
+
channel_list, separator = utils.split_channel_name(
|
|
198
|
+
channel,
|
|
199
|
+
available_channels=available_channels,
|
|
200
|
+
separator='|')
|
|
201
|
+
|
|
202
|
+
nchans = len(channel_list)
|
|
203
|
+
|
|
204
|
+
# check data
|
|
205
|
+
if template_tag is None:
|
|
206
|
+
raise ValueError(f'ERROR: Missing "template_tag" argument '
|
|
207
|
+
f'for channel {channel}, '
|
|
208
|
+
f'algorithm "{feature_base_name}"')
|
|
209
|
+
|
|
210
|
+
template = of_base.template(channel,
|
|
211
|
+
template_tag=template_tag)
|
|
212
|
+
if template is None:
|
|
213
|
+
raise ValueError(f'ERROR: Missing template '
|
|
214
|
+
f'for channel {channel}, '
|
|
215
|
+
f'tag "{template_tag}", '
|
|
216
|
+
f'algorithm "{feature_base_name}"')
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
ntmps = template.shape[1]
|
|
220
|
+
|
|
221
|
+
if amplitude_names is None:
|
|
222
|
+
amplitude_names = []
|
|
223
|
+
for itmp in range(ntmps):
|
|
224
|
+
amplitude_names.append(f'amp{itmp+1}')
|
|
225
|
+
else:
|
|
226
|
+
|
|
227
|
+
if isinstance(amplitude_names, str):
|
|
228
|
+
amplitude_names = [amplitude_names]
|
|
229
|
+
|
|
230
|
+
if len(amplitude_names) != ntmps:
|
|
231
|
+
raise ValueError(
|
|
232
|
+
f'ERROR: Wrong length for "amplitude_names" '
|
|
233
|
+
f'argument. Expecting {ntmps} name '
|
|
234
|
+
f'for channel {channel}, '
|
|
235
|
+
f'algorithm "{feature_base_name}"')
|
|
236
|
+
|
|
237
|
+
# Initialize output
|
|
238
|
+
retdict = dict()
|
|
239
|
+
retdict[f'chi2_{feature_base_name}_constrained'] = -999999.0
|
|
240
|
+
retdict[f't0_{feature_base_name}_constrained'] = -999999.0
|
|
241
|
+
for iamp, amp_name in enumerate(amplitude_names):
|
|
242
|
+
retdict[f'{amp_name}_{feature_base_name}_constrained'] = -999999.0
|
|
243
|
+
retdict[f'chi2_{feature_base_name}_nodelay'] = -999999.0
|
|
244
|
+
for iamp, amp_name in enumerate(amplitude_names):
|
|
245
|
+
retdict[f'{amp_name}_{feature_base_name}_nodelay'] = -999999.0
|
|
246
|
+
|
|
247
|
+
# check if signal stored
|
|
248
|
+
if not of_base.is_signal_stored(channel):
|
|
249
|
+
return retdict
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# instantiate OF NxM
|
|
253
|
+
OF = qp.OFnxm(of_base=of_base,
|
|
254
|
+
channels=channel,
|
|
255
|
+
template_tag=template_tag,
|
|
256
|
+
verbose=False)
|
|
257
|
+
|
|
258
|
+
# calc
|
|
259
|
+
OF.calc()
|
|
260
|
+
|
|
261
|
+
# get data
|
|
262
|
+
amps_constrained, t0_constrained, chi2_constrained = OF.get_fit_withdelay(
|
|
263
|
+
window_min_from_trig_usec=window_min_from_trig_usec,
|
|
264
|
+
window_max_from_trig_usec=window_max_from_trig_usec,
|
|
265
|
+
window_min_index=window_min_index,
|
|
266
|
+
window_max_index=window_max_index,
|
|
267
|
+
interpolate_t0=interpolate_t0,
|
|
268
|
+
lgc_outside_window=lgc_outside_window
|
|
269
|
+
)
|
|
270
|
+
amps_nodelay, t0_nodelay, chi2_nodelay = OF.get_fit_nodelay()
|
|
271
|
+
|
|
272
|
+
# store
|
|
273
|
+
retdict[f'chi2_{feature_base_name}_constrained'] = chi2_constrained
|
|
274
|
+
retdict[f't0_{feature_base_name}_constrained'] = t0_constrained
|
|
275
|
+
for iamp, amp_name in enumerate(amplitude_names):
|
|
276
|
+
retdict[f'{amp_name}_{feature_base_name}_constrained'] = amps_constrained[iamp]
|
|
277
|
+
|
|
278
|
+
retdict[f'chi2_{feature_base_name}_nodelay'] = chi2_nodelay
|
|
279
|
+
for iamp, amp_name in enumerate(amplitude_names):
|
|
280
|
+
retdict[f'{amp_name}_{feature_base_name}_nodelay'] = amps_nodelay[iamp]
|
|
281
|
+
|
|
282
|
+
return retdict
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@staticmethod
|
|
286
|
+
def of1x1_nodelay(channel, of_base,
|
|
287
|
+
template_tag=None,
|
|
288
|
+
lowchi2_fcutoff=10000,
|
|
289
|
+
feature_base_name='of1x1_nodelay',
|
|
290
|
+
**kwargs):
|
|
291
|
+
"""
|
|
292
|
+
Feature extraction for the no delay Optimum Filter.
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
Parameters
|
|
296
|
+
----------
|
|
297
|
+
channel : str
|
|
298
|
+
channel name
|
|
299
|
+
|
|
300
|
+
of_base : OFBase object
|
|
301
|
+
OFBase QETpy object
|
|
302
|
+
|
|
303
|
+
template_tag : str
|
|
304
|
+
tag of the template to be used for OF calculation,
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
lowchi2_fcutoff : float, optional
|
|
308
|
+
The frequency (in Hz) that we should cut off the chi^2 when
|
|
309
|
+
calculating the low frequency chi^2. Default is 10 kHz.
|
|
310
|
+
|
|
311
|
+
feature_base_name : str, option
|
|
312
|
+
output feature base name
|
|
313
|
+
|
|
314
|
+
Returns
|
|
315
|
+
-------
|
|
316
|
+
retdict : dict
|
|
317
|
+
Dictionary containing the various extracted features.
|
|
318
|
+
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
# check tag
|
|
322
|
+
if template_tag is None:
|
|
323
|
+
raise ValueError('ERROR: Template tag required for OF 1x1')
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# intialize variables
|
|
327
|
+
retdict = {
|
|
328
|
+
('amp_' + feature_base_name): -999999.0,
|
|
329
|
+
('chi2_' + feature_base_name): -999999.0,
|
|
330
|
+
('lowchi2_' + feature_base_name): -999999.0
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# check if signal stored
|
|
334
|
+
if not of_base.is_signal_stored(channel):
|
|
335
|
+
return retdict
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# instantiate OF 1x1
|
|
339
|
+
OF = qp.OF1x1(of_base=of_base,
|
|
340
|
+
channel=channel,
|
|
341
|
+
template_tag=template_tag)
|
|
342
|
+
|
|
343
|
+
# calc
|
|
344
|
+
OF.calc(lgc_fit_withdelay=False,
|
|
345
|
+
lgc_fit_nodelay=True,
|
|
346
|
+
lowchi2_fcutoff=lowchi2_fcutoff)
|
|
347
|
+
|
|
348
|
+
# get results
|
|
349
|
+
amp, t0, chi2, lowchi2 = OF.get_result_nodelay()
|
|
350
|
+
|
|
351
|
+
# store features
|
|
352
|
+
retdict = {
|
|
353
|
+
('amp_' + feature_base_name): amp,
|
|
354
|
+
('chi2_' + feature_base_name): chi2,
|
|
355
|
+
('lowchi2_' + feature_base_name): lowchi2
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return retdict
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@staticmethod
|
|
363
|
+
def of1x1_unconstrained(channel, of_base,
|
|
364
|
+
template_tag='default',
|
|
365
|
+
interpolate=False,
|
|
366
|
+
lowchi2_fcutoff=10000,
|
|
367
|
+
feature_base_name='of1x1_unconstrained',
|
|
368
|
+
**kwargs):
|
|
369
|
+
"""
|
|
370
|
+
Feature extraction for the unconstrained Optimum Filter.
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
Parameters
|
|
374
|
+
----------
|
|
375
|
+
channel : str
|
|
376
|
+
channel name
|
|
377
|
+
|
|
378
|
+
of_base : OFBase object
|
|
379
|
+
OFBase
|
|
380
|
+
|
|
381
|
+
template_tag : str, option
|
|
382
|
+
tag of the template to be used for OF calculation,
|
|
383
|
+
Default: 'default'
|
|
384
|
+
|
|
385
|
+
interpolate : bool, optional
|
|
386
|
+
if True, do delay interpolation
|
|
387
|
+
default: False
|
|
388
|
+
|
|
389
|
+
lowchi2_fcutoff : float, optional
|
|
390
|
+
The frequency (in Hz) that we should cut off the chi^2 when
|
|
391
|
+
calculating the low frequency chi^2. Default is 10 kHz.
|
|
392
|
+
|
|
393
|
+
feature_base_name : str, option
|
|
394
|
+
output feature base name
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
Returns
|
|
399
|
+
-------
|
|
400
|
+
retdict : dict
|
|
401
|
+
Dictionary containing the various extracted features.
|
|
402
|
+
|
|
403
|
+
"""
|
|
404
|
+
|
|
405
|
+
# intialize features dict
|
|
406
|
+
retdict = {
|
|
407
|
+
('amp_' + feature_base_name): -999999.0,
|
|
408
|
+
('t0_' + feature_base_name): -999999.0,
|
|
409
|
+
('chi2_' + feature_base_name): -999999.0,
|
|
410
|
+
('lowchi2_' + feature_base_name): -999999.0
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
# check if signal stored
|
|
414
|
+
if not of_base.is_signal_stored(channel):
|
|
415
|
+
return retdict
|
|
416
|
+
|
|
417
|
+
# instantiate OF1x1
|
|
418
|
+
OF = qp.OF1x1(of_base=of_base,
|
|
419
|
+
channel=channel,
|
|
420
|
+
template_tag=template_tag)
|
|
421
|
+
# calc
|
|
422
|
+
OF.calc(lowchi2_fcutoff=lowchi2_fcutoff,
|
|
423
|
+
interpolate_t0=interpolate,
|
|
424
|
+
lgc_fit_withdelay=True,
|
|
425
|
+
lgc_fit_nodelay=False,
|
|
426
|
+
lgc_plot=False)
|
|
427
|
+
|
|
428
|
+
# get results
|
|
429
|
+
amp, t0, chi2, lowchi2 = OF.get_result_withdelay()
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
# store features
|
|
433
|
+
retdict = {
|
|
434
|
+
('amp_' + feature_base_name): amp,
|
|
435
|
+
('t0_' + feature_base_name): t0,
|
|
436
|
+
('chi2_' + feature_base_name): chi2,
|
|
437
|
+
('lowchi2_' + feature_base_name): lowchi2
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return retdict
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@staticmethod
|
|
444
|
+
def of1x1_constrained(channel, of_base,
|
|
445
|
+
template_tag='default',
|
|
446
|
+
window_min_from_trig_usec=None,
|
|
447
|
+
window_max_from_trig_usec=None,
|
|
448
|
+
window_min_index=None,
|
|
449
|
+
window_max_index=None,
|
|
450
|
+
lgc_outside_window=False,
|
|
451
|
+
interpolate=False,
|
|
452
|
+
lowchi2_fcutoff=10000,
|
|
453
|
+
feature_base_name='of1x1_constrained',
|
|
454
|
+
**kwargs):
|
|
455
|
+
"""
|
|
456
|
+
Feature extraction for the constrained Optimum Filter.
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
Parameters
|
|
460
|
+
----------
|
|
461
|
+
channel : str
|
|
462
|
+
channel name
|
|
463
|
+
|
|
464
|
+
of_base : OFBase object
|
|
465
|
+
OFBase QETpy object
|
|
466
|
+
|
|
467
|
+
template_tag : str, optional
|
|
468
|
+
tag of the template to be used for OF calculation,
|
|
469
|
+
Default: 'default'
|
|
470
|
+
|
|
471
|
+
window_min_from_trig_usec : float, optional
|
|
472
|
+
OF filter window start in micro seconds from
|
|
473
|
+
pre-trigger (can be negative if prior pre-trigger)
|
|
474
|
+
Default: use "window_min_index" or
|
|
475
|
+
or set to 0 if both parameters are None
|
|
476
|
+
|
|
477
|
+
window_max_from_trig_usec : float, optional
|
|
478
|
+
OF filter window end in micro seconds from
|
|
479
|
+
pre-trigger (can be negative if prior pre-trigger)
|
|
480
|
+
Default: use "window_max_index" or set to end trace
|
|
481
|
+
if "window_max_index" also None
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
window_min_index : int, optional
|
|
485
|
+
ADC index OF filter window start (alternative
|
|
486
|
+
to "window_min_from_trig_usec")
|
|
487
|
+
|
|
488
|
+
Default: use "window_min_from_trig_usec" or
|
|
489
|
+
set to 0 if both parameters are None
|
|
490
|
+
|
|
491
|
+
window_max_index : int, optional
|
|
492
|
+
ADC index OF filter window end (alternative
|
|
493
|
+
to "window_min_from_trig_usec")
|
|
494
|
+
|
|
495
|
+
Default: use "window_max_from_trig_usec" or
|
|
496
|
+
set to end of trace if both parameters
|
|
497
|
+
are None
|
|
498
|
+
|
|
499
|
+
lgc_outside_window : bool, optional
|
|
500
|
+
If True, define window to be outside [min:max]
|
|
501
|
+
Default: False
|
|
502
|
+
|
|
503
|
+
interpolate : bool, optional
|
|
504
|
+
if True, do delay interpolation
|
|
505
|
+
default: False
|
|
506
|
+
|
|
507
|
+
lowchi2_fcutoff : float, optional
|
|
508
|
+
The frequency (in Hz) that we should cut off the chi^2 when
|
|
509
|
+
calculating the low frequency chi^2. Default is 10 kHz.
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
feature_base_name : str, optional
|
|
513
|
+
output feature base name
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
Returns
|
|
518
|
+
-------
|
|
519
|
+
retdict : dict
|
|
520
|
+
Dictionary containing the various extracted features.
|
|
521
|
+
|
|
522
|
+
"""
|
|
523
|
+
|
|
524
|
+
# intialize dictionary
|
|
525
|
+
retdict = {
|
|
526
|
+
('amp_' + feature_base_name): -999999.0,
|
|
527
|
+
('t0_' + feature_base_name): -999999.0,
|
|
528
|
+
('chi2_' + feature_base_name): -999999.0,
|
|
529
|
+
('lowchi2_' + feature_base_name): -999999.0,
|
|
530
|
+
('chi2nopulse_' + feature_base_name): -999999.0,
|
|
531
|
+
('ampres_' + feature_base_name): -999999.0,
|
|
532
|
+
('timeres_' + feature_base_name): -999999.0
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
# check if signal stored
|
|
536
|
+
if not of_base.is_signal_stored(channel):
|
|
537
|
+
return retdict
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
# instantiate OF1x1
|
|
541
|
+
OF = qp.OF1x1(of_base=of_base,
|
|
542
|
+
channel=channel,
|
|
543
|
+
template_tag=template_tag)
|
|
544
|
+
|
|
545
|
+
# calc (signal needs to be None if set already)
|
|
546
|
+
OF.calc(window_min_from_trig_usec=window_min_from_trig_usec,
|
|
547
|
+
window_max_from_trig_usec=window_max_from_trig_usec,
|
|
548
|
+
window_min_index=window_min_index,
|
|
549
|
+
window_max_index=window_max_index,
|
|
550
|
+
lowchi2_fcutoff=lowchi2_fcutoff,
|
|
551
|
+
interpolate_t0=interpolate,
|
|
552
|
+
lgc_outside_window=lgc_outside_window,
|
|
553
|
+
lgc_fit_withdelay=True,
|
|
554
|
+
lgc_fit_nodelay=False,
|
|
555
|
+
lgc_plot=False)
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
# get results
|
|
559
|
+
amp, t0, chi2, lowchi2 = OF.get_result_withdelay()
|
|
560
|
+
|
|
561
|
+
# get chi2 no pulse
|
|
562
|
+
chi2_nopulse = OF.get_chisq_nopulse()
|
|
563
|
+
|
|
564
|
+
# get OF resolution
|
|
565
|
+
ampres = OF.get_energy_resolution()
|
|
566
|
+
timeres = OF.get_time_resolution()
|
|
567
|
+
|
|
568
|
+
retdict = {
|
|
569
|
+
('amp_' + feature_base_name): amp,
|
|
570
|
+
('t0_' + feature_base_name): t0,
|
|
571
|
+
('chi2_' + feature_base_name): chi2,
|
|
572
|
+
('lowchi2_' + feature_base_name): lowchi2,
|
|
573
|
+
('chi2nopulse_' + feature_base_name): chi2_nopulse,
|
|
574
|
+
('ampres_' + feature_base_name): ampres,
|
|
575
|
+
('timeres_' + feature_base_name): timeres,
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return retdict
|
|
579
|
+
|
|
580
|
+
@staticmethod
|
|
581
|
+
def of1x2x2(channel, of_base,
|
|
582
|
+
template_tag_1='Scintillation',
|
|
583
|
+
template_tag_2='Evaporation',
|
|
584
|
+
feature_base_name='of1x2x2',
|
|
585
|
+
**kwargs):
|
|
586
|
+
"""
|
|
587
|
+
Feature extraction for the one channel, two template Optimum Filter.
|
|
588
|
+
|
|
589
|
+
Parameters
|
|
590
|
+
----------
|
|
591
|
+
of_base : OFBase object, optional
|
|
592
|
+
OFBase if it has been instantiated independently
|
|
593
|
+
|
|
594
|
+
template_tag_1: str, option
|
|
595
|
+
tag of the template to be used for OF calculation of the scintillation part; please use Scintilation,
|
|
596
|
+
Default: 'Scintillation'
|
|
597
|
+
|
|
598
|
+
template_tag_2 : str, option
|
|
599
|
+
tag of the template to be used for OF calculation of the evaporation part; please use Evaporation,
|
|
600
|
+
Default: 'Evaporation'
|
|
601
|
+
|
|
602
|
+
feature_base_name : str, option
|
|
603
|
+
output feature base name
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
Returns
|
|
608
|
+
-------
|
|
609
|
+
retdict : dict
|
|
610
|
+
Dictionary containing the various extracted features.
|
|
611
|
+
|
|
612
|
+
"""
|
|
613
|
+
|
|
614
|
+
# intialize dictionary
|
|
615
|
+
retdict = {
|
|
616
|
+
('scintillation_amp_' + feature_base_name): -999999.0,
|
|
617
|
+
('evaporation_amp_' + feature_base_name): -999999.0,
|
|
618
|
+
('time_diff_' + feature_base_name): -999999.0,
|
|
619
|
+
('scintillation_time_index' + feature_base_name): -999999.0,
|
|
620
|
+
('evaporation_time_index' + feature_base_name): -999999.0
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
# check if signal stored
|
|
624
|
+
if not of_base.is_signal_stored(channel):
|
|
625
|
+
return retdict
|
|
626
|
+
|
|
627
|
+
# instantiate OF1x2
|
|
628
|
+
OF = qp.OF1x2(
|
|
629
|
+
of_base=of_base,
|
|
630
|
+
template_1_tag=template_tag_1,
|
|
631
|
+
template_2_tag=template_tag_1,
|
|
632
|
+
channel_name= channel,
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
# calc (signal needs to be None if set already)
|
|
636
|
+
OF.calc(lgc_plot=False)
|
|
637
|
+
|
|
638
|
+
# get results
|
|
639
|
+
scintillation_amp = OF._amplitude[OF._template_1_tag]
|
|
640
|
+
evaporation_amp = OF._amplitude[OF._template_2_tag]
|
|
641
|
+
time_diff= OF._time_diff_two_Pulses
|
|
642
|
+
Starting_time_first_pulse = OF._time_first_pulse
|
|
643
|
+
Starting_time_second_pulse = OF._time_second_pulse
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
# store features
|
|
647
|
+
retdict = {
|
|
648
|
+
('scintillation_amp_' + feature_base_name): scintillation_amp,
|
|
649
|
+
('evaporation_amp_' + feature_base_name): evaporation_amp,
|
|
650
|
+
('time_diff_' + feature_base_name): time_diff,
|
|
651
|
+
('scintillation_time_index' + feature_base_name): Starting_time_first_pulse ,
|
|
652
|
+
('evaporation_time_index' + feature_base_name): Starting_time_second_pulse
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return retdict
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
@staticmethod
|
|
659
|
+
def baseline(trace,
|
|
660
|
+
window_min_index=None, window_max_index=None,
|
|
661
|
+
feature_base_name='baseline',
|
|
662
|
+
**kwargs):
|
|
663
|
+
"""
|
|
664
|
+
Feature extraction for the trace baseline.
|
|
665
|
+
|
|
666
|
+
Parameters
|
|
667
|
+
----------
|
|
668
|
+
trace : ndarray
|
|
669
|
+
An ndarray containing the raw data to extract the feature
|
|
670
|
+
from.
|
|
671
|
+
|
|
672
|
+
window_min_index : int, optional
|
|
673
|
+
The minium index of the window used to average the trace.
|
|
674
|
+
Default: 0
|
|
675
|
+
|
|
676
|
+
window_max_index : int, optional
|
|
677
|
+
The maximum index of the window used to average the trace.
|
|
678
|
+
Default: end of trace
|
|
679
|
+
|
|
680
|
+
feature_base_name : str, optional
|
|
681
|
+
output feature base name
|
|
682
|
+
|
|
683
|
+
Returns
|
|
684
|
+
-------
|
|
685
|
+
retdict : dict
|
|
686
|
+
Dictionary containing the various extracted features.
|
|
687
|
+
|
|
688
|
+
"""
|
|
689
|
+
|
|
690
|
+
# check if trace is empty or None
|
|
691
|
+
if (trace is None or trace.size==0):
|
|
692
|
+
retdict = {
|
|
693
|
+
feature_base_name: -999999.0,
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return retdict
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
if window_min_index is None:
|
|
700
|
+
window_min_index = 0
|
|
701
|
+
|
|
702
|
+
if window_max_index is None:
|
|
703
|
+
window_max_index = trace.shape[-1] - 1
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
baseline = np.mean(trace[window_min_index:window_max_index])
|
|
707
|
+
|
|
708
|
+
retdict = {
|
|
709
|
+
feature_base_name: baseline,
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return retdict
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@staticmethod
|
|
717
|
+
def integral(trace, fs,
|
|
718
|
+
window_min_index=None, window_max_index=None,
|
|
719
|
+
feature_base_name='integral',
|
|
720
|
+
**kwargs):
|
|
721
|
+
"""
|
|
722
|
+
Feature extraction for the pulse integral.
|
|
723
|
+
|
|
724
|
+
Parameters
|
|
725
|
+
----------
|
|
726
|
+
trace : ndarray
|
|
727
|
+
An ndarray containing the raw data to extract the feature
|
|
728
|
+
from.
|
|
729
|
+
|
|
730
|
+
fs : float
|
|
731
|
+
The digitization rate of the data in trace.
|
|
732
|
+
|
|
733
|
+
window_min_index : int, optional
|
|
734
|
+
The minium index of the window used to integrate the trace.
|
|
735
|
+
Default: 0
|
|
736
|
+
|
|
737
|
+
window_max_index : int, optional
|
|
738
|
+
The maximum index of the window used to integrate the trace.
|
|
739
|
+
Default: end of trace
|
|
740
|
+
|
|
741
|
+
feature_base_name : str, optional
|
|
742
|
+
output feature base name
|
|
743
|
+
|
|
744
|
+
Returns
|
|
745
|
+
-------
|
|
746
|
+
retdict : dict
|
|
747
|
+
Dictionary containing the various extracted features.
|
|
748
|
+
|
|
749
|
+
"""
|
|
750
|
+
|
|
751
|
+
# check if trace is empty or None
|
|
752
|
+
if (trace is None or trace.size==0):
|
|
753
|
+
retdict = {
|
|
754
|
+
feature_base_name: -999999.0,
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return retdict
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
if window_min_index is None:
|
|
761
|
+
window_min_index = 0
|
|
762
|
+
|
|
763
|
+
if window_max_index is None:
|
|
764
|
+
window_max_index = trace.shape[-1] - 1
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
integral = _trapezoid(trace[window_min_index:window_max_index]) / fs
|
|
768
|
+
|
|
769
|
+
retdict = {
|
|
770
|
+
feature_base_name: integral,
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
return retdict
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
@staticmethod
|
|
779
|
+
def maximum(trace,
|
|
780
|
+
window_min_index=None, window_max_index=None,
|
|
781
|
+
feature_base_name='maximum',
|
|
782
|
+
**kwargs):
|
|
783
|
+
"""
|
|
784
|
+
Feature extraction for the maximum pulse value.
|
|
785
|
+
|
|
786
|
+
Parameters
|
|
787
|
+
----------
|
|
788
|
+
trace : ndarray
|
|
789
|
+
An ndarray containing the raw data to extract the feature
|
|
790
|
+
from.
|
|
791
|
+
|
|
792
|
+
window_min_index : int, optional
|
|
793
|
+
The minium index of the window used to find maximum
|
|
794
|
+
Default: 0
|
|
795
|
+
|
|
796
|
+
window_max_index : int, optional
|
|
797
|
+
The maximum index of the window used to find maximum
|
|
798
|
+
Default: end of trace
|
|
799
|
+
|
|
800
|
+
feature_base_name : str, optional
|
|
801
|
+
output feature base name
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
Returns
|
|
805
|
+
-------
|
|
806
|
+
retdict : dict
|
|
807
|
+
Dictionary containing the various extracted features.
|
|
808
|
+
|
|
809
|
+
"""
|
|
810
|
+
|
|
811
|
+
# check if trace is empty or None
|
|
812
|
+
if (trace is None or trace.size==0):
|
|
813
|
+
retdict = {
|
|
814
|
+
feature_base_name: -999999.0,
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
return retdict
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
if window_min_index is None:
|
|
821
|
+
window_min_index = 0
|
|
822
|
+
|
|
823
|
+
if window_max_index is None:
|
|
824
|
+
window_max_index = trace.shape[-1] - 1
|
|
825
|
+
|
|
826
|
+
max_trace = np.amax(trace[window_min_index:window_max_index])
|
|
827
|
+
|
|
828
|
+
retdict = {
|
|
829
|
+
feature_base_name: max_trace,
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return retdict
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
@staticmethod
|
|
838
|
+
def minimum(trace,
|
|
839
|
+
window_min_index=None, window_max_index=None,
|
|
840
|
+
feature_base_name='minimum',
|
|
841
|
+
**kwargs):
|
|
842
|
+
"""
|
|
843
|
+
Feature extraction for the minimum pulse value.
|
|
844
|
+
|
|
845
|
+
Parameters
|
|
846
|
+
----------
|
|
847
|
+
trace : ndarray
|
|
848
|
+
An ndarray containing the raw data to extract the feature
|
|
849
|
+
from.
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
window_min_index : int, optional
|
|
853
|
+
The minium index of the window used to find minimum
|
|
854
|
+
Default: 0
|
|
855
|
+
|
|
856
|
+
window_max_index : int, optional
|
|
857
|
+
The maximum index of the window used to find minimum
|
|
858
|
+
Default: end of trace
|
|
859
|
+
|
|
860
|
+
feature_base_name : str, optional
|
|
861
|
+
output feature base name
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
Returns
|
|
865
|
+
-------
|
|
866
|
+
retdict : dict
|
|
867
|
+
Dictionary containing the various extracted features.
|
|
868
|
+
|
|
869
|
+
"""
|
|
870
|
+
|
|
871
|
+
# check if trace is empty or None
|
|
872
|
+
if (trace is None or trace.size==0):
|
|
873
|
+
retdict = {
|
|
874
|
+
feature_base_name: -999999.0,
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
return retdict
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
if window_min_index is None:
|
|
881
|
+
window_min_index = 0
|
|
882
|
+
|
|
883
|
+
if window_max_index is None:
|
|
884
|
+
window_max_index = trace.shape[-1] - 1
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
min_trace = np.amin(trace[window_min_index:window_max_index])
|
|
888
|
+
|
|
889
|
+
retdict = {
|
|
890
|
+
feature_base_name: min_trace,
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return retdict
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
@staticmethod
|
|
898
|
+
def energyabsorbed(trace,
|
|
899
|
+
fs, vb, i0, rl,
|
|
900
|
+
window_min_index=None, window_max_index=None,
|
|
901
|
+
feature_base_name='energyabsorbed',
|
|
902
|
+
**kwargs):
|
|
903
|
+
"""
|
|
904
|
+
Feature extraction for the minimum pulse value.
|
|
905
|
+
|
|
906
|
+
Parameters
|
|
907
|
+
----------
|
|
908
|
+
trace : ndarray
|
|
909
|
+
An ndarray containing the raw data to extract the feature
|
|
910
|
+
from.
|
|
911
|
+
fs : float
|
|
912
|
+
The digitization rate of the data in trace.
|
|
913
|
+
vb : float
|
|
914
|
+
Bias voltage applied to the TES.
|
|
915
|
+
i0 : float
|
|
916
|
+
Quiescent operating current of the TES.
|
|
917
|
+
rl : float
|
|
918
|
+
Load resistance in the TES circuit.
|
|
919
|
+
|
|
920
|
+
window_min_index : int, optional
|
|
921
|
+
The index of the trace to start the integration.
|
|
922
|
+
Default: 0
|
|
923
|
+
window_max_index : int, optional
|
|
924
|
+
The index of the trace to end the integration.
|
|
925
|
+
Default: end of trace
|
|
926
|
+
|
|
927
|
+
feature_base_name : str, optional
|
|
928
|
+
output feature base name
|
|
929
|
+
|
|
930
|
+
Returns
|
|
931
|
+
-------
|
|
932
|
+
retdict : dict
|
|
933
|
+
Dictionary containing the various extracted features.
|
|
934
|
+
|
|
935
|
+
"""
|
|
936
|
+
|
|
937
|
+
# check if trace is empty or None
|
|
938
|
+
if (trace is None or trace.size==0):
|
|
939
|
+
retdict = {
|
|
940
|
+
feature_base_name: -999999.0,
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
return retdict
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
baseline = trace[:window_min_index].mean()
|
|
947
|
+
i_trace = trace[window_min_index:window_max_index] - baseline
|
|
948
|
+
|
|
949
|
+
p0 = i_trace * (vb - 2*i0*rl) - i_trace**2 * rl
|
|
950
|
+
|
|
951
|
+
en_abs = _trapezoid(p0, dx=1/fs, axis=-1)
|
|
952
|
+
|
|
953
|
+
retdict = {
|
|
954
|
+
feature_base_name: en_abs,
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return retdict
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
@staticmethod
|
|
961
|
+
def psd_amp(channel, fft_data=None,
|
|
962
|
+
of_base=None,
|
|
963
|
+
f_lims=[],
|
|
964
|
+
feature_base_name='psd_amp',
|
|
965
|
+
**kwargs):
|
|
966
|
+
"""
|
|
967
|
+
Feature extraction for measuring the average amplitude of a
|
|
968
|
+
ffted trace in a range of frequencies. The FFT is supplied by the per-event FFT cache so multiple FFT-based
|
|
969
|
+
features can reuse the same transform.
|
|
970
|
+
The arguments "trace", "template", "psd"
|
|
971
|
+
(and associated parameters "fs", "nb_samples_pretrigger")
|
|
972
|
+
should only be used if not already added in OF base object.
|
|
973
|
+
Otherwise keep as None
|
|
974
|
+
|
|
975
|
+
Parameters
|
|
976
|
+
----------
|
|
977
|
+
fft_data : EventFFTData-like object
|
|
978
|
+
Cached signal FFT/frequency view. ``of_base`` remains accepted as a
|
|
979
|
+
compatibility keyword for direct callers.
|
|
980
|
+
|
|
981
|
+
f_lims : list of list of floats
|
|
982
|
+
A list of [f_low, f_high]s between which the averaged PSD is
|
|
983
|
+
calculated. For example, [[45.0, 65.0], [120.0, 130.0]]
|
|
984
|
+
|
|
985
|
+
feature_base_name : str, option
|
|
986
|
+
output feature base name
|
|
987
|
+
|
|
988
|
+
Returns
|
|
989
|
+
-------
|
|
990
|
+
retdict : dict
|
|
991
|
+
Dictionary containing the various extracted features.
|
|
992
|
+
|
|
993
|
+
"""
|
|
994
|
+
|
|
995
|
+
# ``of_base`` is retained as a compatibility keyword for direct callers.
|
|
996
|
+
# FeatureProcessing now passes a lightweight EventFFTData object.
|
|
997
|
+
if fft_data is None:
|
|
998
|
+
fft_data = of_base
|
|
999
|
+
|
|
1000
|
+
# check f_lims and define feature name
|
|
1001
|
+
if not f_lims:
|
|
1002
|
+
raise ValueError('ERROR: "f_lims" required for algorithm psd_amps')
|
|
1003
|
+
|
|
1004
|
+
freq_ranges, range_names = utils.cleanup_freq_ranges(f_lims)
|
|
1005
|
+
|
|
1006
|
+
# initialize output
|
|
1007
|
+
retdict = {}
|
|
1008
|
+
for var_base in range_names:
|
|
1009
|
+
var_name = f'{feature_base_name}_{var_base}'
|
|
1010
|
+
retdict[var_name] = -999999.0
|
|
1011
|
+
|
|
1012
|
+
# check if signal available
|
|
1013
|
+
if not fft_data.is_signal_stored(channel):
|
|
1014
|
+
return retdict
|
|
1015
|
+
|
|
1016
|
+
# get fft
|
|
1017
|
+
trace_fft = fft_data.signal_fft(channel, squeeze_array=True)
|
|
1018
|
+
trace_fft = trace_fft.copy()
|
|
1019
|
+
freqs = fft_data.fft_freqs().copy()
|
|
1020
|
+
nbins = fft_data.nb_samples()
|
|
1021
|
+
|
|
1022
|
+
# sample rate
|
|
1023
|
+
fs = utils.estimate_sampling_rate(freqs)
|
|
1024
|
+
if 'fs' in kwargs:
|
|
1025
|
+
fs = kwargs['fs']
|
|
1026
|
+
|
|
1027
|
+
# calculate psd
|
|
1028
|
+
psd = (np.abs(trace_fft)**2.0)*nbins/fs
|
|
1029
|
+
|
|
1030
|
+
# fold
|
|
1031
|
+
freqs_fold, psd_fold = qp.utils.fold_spectrum(psd, fs)
|
|
1032
|
+
|
|
1033
|
+
# remove DC
|
|
1034
|
+
psd_fold = psd_fold[1:]
|
|
1035
|
+
psd_fold = np.sqrt(psd_fold)
|
|
1036
|
+
freqs_fold = freqs_fold[1:]
|
|
1037
|
+
|
|
1038
|
+
retdict = {}
|
|
1039
|
+
ind_ranges = utils.get_ind_freq_ranges(freq_ranges, freqs_fold)
|
|
1040
|
+
for it, ind_range in enumerate(ind_ranges):
|
|
1041
|
+
|
|
1042
|
+
ind_low = ind_range[0]
|
|
1043
|
+
ind_high = ind_range[1]
|
|
1044
|
+
|
|
1045
|
+
# take median
|
|
1046
|
+
psd_chunk = psd_fold[ind_low:ind_high]
|
|
1047
|
+
|
|
1048
|
+
# smooth + max ?
|
|
1049
|
+
# nb_samples = len(psd_chunk)
|
|
1050
|
+
#psd_chunk_smooth = np.convolve(psd_chunk, np.ones(3)/3,
|
|
1051
|
+
# mode='valid')
|
|
1052
|
+
#
|
|
1053
|
+
psd_avg = np.average(psd_chunk)
|
|
1054
|
+
|
|
1055
|
+
# parameter name
|
|
1056
|
+
psd_amp_name = f'{feature_base_name}_{range_names[it]}'
|
|
1057
|
+
retdict[psd_amp_name] = psd_avg
|
|
1058
|
+
|
|
1059
|
+
return retdict
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
@staticmethod
|
|
1063
|
+
def psd_peaks(channel, fft_data=None,
|
|
1064
|
+
of_base=None,
|
|
1065
|
+
f_lims=[],
|
|
1066
|
+
npeaks=1,
|
|
1067
|
+
min_separation_hz=0.0,
|
|
1068
|
+
average_range=False,
|
|
1069
|
+
feature_base_name='psd_peaks',
|
|
1070
|
+
**kwargs):
|
|
1071
|
+
"""
|
|
1072
|
+
Feature extraction for finding peaks of a psd
|
|
1073
|
+
in a range of frequencies. The FFT is supplied by the per-event FFT cache so multiple FFT-based
|
|
1074
|
+
features can reuse the same transform.
|
|
1075
|
+
|
|
1076
|
+
Parameters
|
|
1077
|
+
----------
|
|
1078
|
+
fft_data : EventFFTData-like object
|
|
1079
|
+
Cached signal FFT/frequency view. ``of_base`` remains accepted as a
|
|
1080
|
+
compatibility keyword for direct callers.
|
|
1081
|
+
|
|
1082
|
+
f_lims : list of list of floats
|
|
1083
|
+
A list of [f_low, f_high]s between which the PSD peak is
|
|
1084
|
+
calculated. For example, [[45.0, 65.0], [120.0, 130.0]]
|
|
1085
|
+
|
|
1086
|
+
npeaks : number of peaks to search. Default=1
|
|
1087
|
+
|
|
1088
|
+
min_separation_hz : minimum separation between peaks
|
|
1089
|
+
|
|
1090
|
+
feature_base_name : str, option
|
|
1091
|
+
output feature base name
|
|
1092
|
+
|
|
1093
|
+
Returns
|
|
1094
|
+
-------
|
|
1095
|
+
retdict : dict
|
|
1096
|
+
Dictionary containing the various extracted features.
|
|
1097
|
+
|
|
1098
|
+
"""
|
|
1099
|
+
if fft_data is None:
|
|
1100
|
+
fft_data = of_base
|
|
1101
|
+
|
|
1102
|
+
# check f_lims and define feature name
|
|
1103
|
+
if not f_lims:
|
|
1104
|
+
raise ValueError('ERROR: "f_lims" required for algorithm psd_amps')
|
|
1105
|
+
|
|
1106
|
+
freq_ranges, range_names = utils.cleanup_freq_ranges(f_lims)
|
|
1107
|
+
|
|
1108
|
+
# initialize output
|
|
1109
|
+
retdict = {}
|
|
1110
|
+
for i in range(1, npeaks + 1):
|
|
1111
|
+
for var_base in range_names:
|
|
1112
|
+
var_name_amp = f'{feature_base_name}_{var_base}_amp_{i}'
|
|
1113
|
+
var_name_freq = f'{feature_base_name}_{var_base}_freq_{i}'
|
|
1114
|
+
retdict[var_name_amp] = -999999.0
|
|
1115
|
+
retdict[var_name_freq] = -999999.0
|
|
1116
|
+
# DC
|
|
1117
|
+
retdict[f'{feature_base_name}_dc_amp'] = -999999.0
|
|
1118
|
+
|
|
1119
|
+
# check if signal stored
|
|
1120
|
+
if not fft_data.is_signal_stored(channel):
|
|
1121
|
+
return retdict
|
|
1122
|
+
|
|
1123
|
+
trace_fft = fft_data.signal_fft(channel, squeeze_array=True)
|
|
1124
|
+
trace_fft = trace_fft.copy()
|
|
1125
|
+
freqs = fft_data.fft_freqs().copy()
|
|
1126
|
+
nbins = fft_data.nb_samples()
|
|
1127
|
+
|
|
1128
|
+
if trace_fft.ndim != 1:
|
|
1129
|
+
# multi-channels, not implemented
|
|
1130
|
+
raise ValueError(f'ERROR: "psd_peaks" not implemented for '
|
|
1131
|
+
f'multi-channel. Remove algorithm for '
|
|
1132
|
+
f'channel {channel}!')
|
|
1133
|
+
|
|
1134
|
+
# sample rate
|
|
1135
|
+
fs = utils.estimate_sampling_rate(freqs)
|
|
1136
|
+
if 'fs' in kwargs:
|
|
1137
|
+
fs = kwargs['fs']
|
|
1138
|
+
|
|
1139
|
+
# calculate psd
|
|
1140
|
+
psd = (np.abs(trace_fft)**2.0)*nbins/fs
|
|
1141
|
+
|
|
1142
|
+
# fold
|
|
1143
|
+
freqs_fold, psd_fold = qp.utils.fold_spectrum(psd, fs)
|
|
1144
|
+
|
|
1145
|
+
# store DC amp
|
|
1146
|
+
retdict[f'{feature_base_name}_dc_amp'] = np.sqrt(psd_fold[0])
|
|
1147
|
+
|
|
1148
|
+
# remove DC
|
|
1149
|
+
psd_fold = psd_fold[1:]
|
|
1150
|
+
psd_fold = np.sqrt(psd_fold)
|
|
1151
|
+
freqs_fold = freqs_fold[1:]
|
|
1152
|
+
|
|
1153
|
+
# loop range and find peaks
|
|
1154
|
+
ind_ranges = utils.get_ind_freq_ranges(freq_ranges, freqs_fold)
|
|
1155
|
+
for it, freq_range in enumerate(freq_ranges):
|
|
1156
|
+
|
|
1157
|
+
ind_range = ind_ranges[it]
|
|
1158
|
+
var_base = range_names[it]
|
|
1159
|
+
|
|
1160
|
+
# case single frequency or just take average range
|
|
1161
|
+
if ((ind_range[1] == ind_range[0]+1)
|
|
1162
|
+
or average_range):
|
|
1163
|
+
|
|
1164
|
+
ind_low = ind_range[0]
|
|
1165
|
+
ind_high = ind_range[1]
|
|
1166
|
+
psd_chunk = psd_fold[ind_low:ind_high]
|
|
1167
|
+
psd_avg = np.average(psd_chunk)
|
|
1168
|
+
freq_avg = np.average(freqs_fold[ind_low:ind_high])
|
|
1169
|
+
var_name_amp = f'{feature_base_name}_{var_base}_amp_1'
|
|
1170
|
+
var_name_freq = f'{feature_base_name}_{var_base}_freq_1'
|
|
1171
|
+
|
|
1172
|
+
retdict[var_name_amp] = psd_avg
|
|
1173
|
+
retdict[var_name_freq] = freq_avg
|
|
1174
|
+
|
|
1175
|
+
else:
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
# find peaks within interval
|
|
1179
|
+
result_list = utils.find_psd_peaks(
|
|
1180
|
+
freqs_fold, psd_fold,
|
|
1181
|
+
fmin=freq_range[0], fmax=freq_range[1],
|
|
1182
|
+
npeaks=npeaks,
|
|
1183
|
+
min_separation_hz=min_separation_hz,
|
|
1184
|
+
min_prominence=None)
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
# loop peaks
|
|
1188
|
+
for i in range(npeaks):
|
|
1189
|
+
var_name_amp = f'{feature_base_name}_{var_base}_amp_{i+1}'
|
|
1190
|
+
var_name_freq = f'{feature_base_name}_{var_base}_freq_{i+1}'
|
|
1191
|
+
if i < len(result_list):
|
|
1192
|
+
result = result_list[i]
|
|
1193
|
+
retdict[var_name_amp] = result['amplitude']
|
|
1194
|
+
retdict[var_name_freq] = result['freq']
|
|
1195
|
+
else:
|
|
1196
|
+
retdict[var_name_amp] = -999999.
|
|
1197
|
+
retdict[var_name_freq] = -999999.
|
|
1198
|
+
|
|
1199
|
+
# done
|
|
1200
|
+
return retdict
|
|
1201
|
+
|
|
1202
|
+
@staticmethod
|
|
1203
|
+
def phase(channel, fft_data=None,
|
|
1204
|
+
of_base=None,
|
|
1205
|
+
f_lims=[],
|
|
1206
|
+
npeaks=1,
|
|
1207
|
+
min_separation_hz=0.0,
|
|
1208
|
+
threshold_factor=1e-3,
|
|
1209
|
+
feature_base_name='phase',
|
|
1210
|
+
**kwargs):
|
|
1211
|
+
"""
|
|
1212
|
+
Feature extraction for finding the phase signal for peaks of the psd
|
|
1213
|
+
in a range of frequencies. The FFT is supplied by the per-event FFT cache so multiple FFT-based
|
|
1214
|
+
features can reuse the same transform.
|
|
1215
|
+
Phase is returned in units of radians.
|
|
1216
|
+
|
|
1217
|
+
Parameters
|
|
1218
|
+
----------
|
|
1219
|
+
fft_data : EventFFTData-like object
|
|
1220
|
+
Cached signal FFT/frequency view. ``of_base`` remains accepted as a
|
|
1221
|
+
compatibility keyword for direct callers.
|
|
1222
|
+
|
|
1223
|
+
f_lims : list of list of floats
|
|
1224
|
+
A list of [f_low, f_high]s that will be used to search for peaks in the PSD.
|
|
1225
|
+
The phase will be calculated and returned for frequencies
|
|
1226
|
+
associated with those peaks.
|
|
1227
|
+
Example: [[45.0, 65.0], [120.0, 130.0]].
|
|
1228
|
+
|
|
1229
|
+
npeaks : number of peaks to search. Default=1
|
|
1230
|
+
|
|
1231
|
+
min_separation_hz : minimum separation between peaks
|
|
1232
|
+
|
|
1233
|
+
threshold_factor : a threshold factor that will be applied in FFT units to avoid calculating the phase for noisy garbage.
|
|
1234
|
+
|
|
1235
|
+
feature_base_name : str, option
|
|
1236
|
+
output feature base name
|
|
1237
|
+
|
|
1238
|
+
Returns
|
|
1239
|
+
-------
|
|
1240
|
+
retdict : dict
|
|
1241
|
+
Dictionary containing the various extracted features.
|
|
1242
|
+
|
|
1243
|
+
Phase will be returned in radians.
|
|
1244
|
+
"""
|
|
1245
|
+
|
|
1246
|
+
if fft_data is None:
|
|
1247
|
+
fft_data = of_base
|
|
1248
|
+
|
|
1249
|
+
# check f_lims and define feature name
|
|
1250
|
+
if not f_lims:
|
|
1251
|
+
raise ValueError('ERROR: "f_lims" required for algorithm psd_amps')
|
|
1252
|
+
|
|
1253
|
+
freq_ranges, range_names = utils.cleanup_freq_ranges(f_lims)
|
|
1254
|
+
|
|
1255
|
+
# initialize output
|
|
1256
|
+
retdict = {}
|
|
1257
|
+
for i in range(1, npeaks + 1):
|
|
1258
|
+
for var_base in range_names:
|
|
1259
|
+
var_name_amp = f'{feature_base_name}_{var_base}_phase_{i}'
|
|
1260
|
+
var_name_freq = f'{feature_base_name}_{var_base}_freq_{i}'
|
|
1261
|
+
retdict[var_name_amp] = -999999.0
|
|
1262
|
+
retdict[var_name_freq] = -999999.0
|
|
1263
|
+
|
|
1264
|
+
|
|
1265
|
+
# check if signal stored
|
|
1266
|
+
if not fft_data.is_signal_stored(channel):
|
|
1267
|
+
return retdict
|
|
1268
|
+
|
|
1269
|
+
trace_fft = fft_data.signal_fft(channel, squeeze_array=True)
|
|
1270
|
+
trace_fft = trace_fft.copy()
|
|
1271
|
+
freqs = fft_data.fft_freqs().copy()
|
|
1272
|
+
nbins = fft_data.nb_samples()
|
|
1273
|
+
|
|
1274
|
+
if trace_fft.ndim != 1:
|
|
1275
|
+
# multi-channels, not implemented
|
|
1276
|
+
raise ValueError(f'ERROR: "phase" not implemented for '
|
|
1277
|
+
f'multi-channel. Remove algorithm for '
|
|
1278
|
+
f'channel {channel}!')
|
|
1279
|
+
|
|
1280
|
+
# sample rate
|
|
1281
|
+
fs = utils.estimate_sampling_rate(freqs)
|
|
1282
|
+
if 'fs' in kwargs:
|
|
1283
|
+
fs = kwargs['fs']
|
|
1284
|
+
|
|
1285
|
+
# calculate psd
|
|
1286
|
+
psd = (np.abs(trace_fft)**2.0)*nbins/fs
|
|
1287
|
+
|
|
1288
|
+
# fold
|
|
1289
|
+
freqs_fold, psd_fold = qp.utils.fold_spectrum(psd, fs)
|
|
1290
|
+
|
|
1291
|
+
# remove DC
|
|
1292
|
+
psd_fold = psd_fold[1:]
|
|
1293
|
+
psd_fold = np.sqrt(psd_fold)
|
|
1294
|
+
freqs_fold = freqs_fold[1:]
|
|
1295
|
+
|
|
1296
|
+
fft_cpy = np.array(trace_fft, copy=True)
|
|
1297
|
+
mag = np.abs(fft_cpy)
|
|
1298
|
+
|
|
1299
|
+
# Phase reference shift for pretrigger
|
|
1300
|
+
nb_samples_pretrigger = kwargs.get('nb_samples_pretrigger', 0)
|
|
1301
|
+
t0 = nb_samples_pretrigger / fs # seconds
|
|
1302
|
+
fft_cpy *= np.exp(1j * 2.0 * np.pi * freqs * t0)
|
|
1303
|
+
|
|
1304
|
+
# Mask tiny-magnitude bins to avoid noisy phase
|
|
1305
|
+
thr = mag.max() * float(kwargs.get('threshold_factor', 0.0))
|
|
1306
|
+
phase = np.angle(fft_cpy)
|
|
1307
|
+
if thr > 0:
|
|
1308
|
+
phase = np.where(mag >= thr, phase, -999999.0)
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
|
+
# Keep only positive frequencies (includes DC and Nyquist if N even)
|
|
1312
|
+
N = phase.shape[-1]
|
|
1313
|
+
pos_stop = N // 2 + 1
|
|
1314
|
+
phase_fold = phase[:pos_stop]
|
|
1315
|
+
freqs_fold = freqs[:pos_stop]
|
|
1316
|
+
|
|
1317
|
+
# Fix Nyquist if negative (happens for even N)
|
|
1318
|
+
if freqs_fold[-1] < 0:
|
|
1319
|
+
freqs_fold[-1] = abs(freqs_fold[-1])
|
|
1320
|
+
|
|
1321
|
+
# Remove DC
|
|
1322
|
+
phase_fold = phase_fold[1:]
|
|
1323
|
+
freqs_fold = freqs_fold[1:]
|
|
1324
|
+
|
|
1325
|
+
# loop ranges
|
|
1326
|
+
ind_ranges = utils.get_ind_freq_ranges(freq_ranges, freqs_fold)
|
|
1327
|
+
for it, freq_range in enumerate(freq_ranges):
|
|
1328
|
+
|
|
1329
|
+
ind_range = ind_ranges[it]
|
|
1330
|
+
var_base = range_names[it]
|
|
1331
|
+
|
|
1332
|
+
# case single frequency or just take average range
|
|
1333
|
+
if (ind_range[1] == ind_range[0]+1):
|
|
1334
|
+
ind_low = ind_range[0]
|
|
1335
|
+
ind_high = ind_range[1]
|
|
1336
|
+
var_name_phase = f'{feature_base_name}_{var_base}_phase_1'
|
|
1337
|
+
var_name_freq = f'{feature_base_name}_{var_base}_freq_1'
|
|
1338
|
+
|
|
1339
|
+
freq_val = float(freqs_fold[ind_low:ind_high].item())
|
|
1340
|
+
phase_val = float(phase_fold[ind_low:ind_high].item())
|
|
1341
|
+
|
|
1342
|
+
retdict[var_name_freq] = freq_val
|
|
1343
|
+
retdict[var_name_phase] = phase_val
|
|
1344
|
+
|
|
1345
|
+
else:
|
|
1346
|
+
|
|
1347
|
+
# find peaks within interval
|
|
1348
|
+
result_list = utils.find_psd_peaks(
|
|
1349
|
+
freqs_fold, psd_fold,
|
|
1350
|
+
fmin=freq_range[0], fmax=freq_range[1],
|
|
1351
|
+
npeaks=npeaks,
|
|
1352
|
+
min_separation_hz=min_separation_hz,
|
|
1353
|
+
min_prominence=None)
|
|
1354
|
+
|
|
1355
|
+
# loop peaks
|
|
1356
|
+
for i in range(npeaks):
|
|
1357
|
+
var_name_phase = f'{feature_base_name}_{var_base}_phase_{i+1}'
|
|
1358
|
+
var_name_freq = f'{feature_base_name}_{var_base}_freq_{i+1}'
|
|
1359
|
+
if i < len(result_list):
|
|
1360
|
+
result = result_list[i]
|
|
1361
|
+
retdict[var_name_freq] = result['freq']
|
|
1362
|
+
retdict[var_name_phase] = phase_fold[result['index']]
|
|
1363
|
+
else:
|
|
1364
|
+
retdict[var_name_phase] = -999999.
|
|
1365
|
+
retdict[var_name_freq] = -999999.
|
|
1366
|
+
|
|
1367
|
+
# done
|
|
1368
|
+
return retdict
|
|
1369
|
+
|
|
1370
|
+
@staticmethod
|
|
1371
|
+
def rftau(trace,
|
|
1372
|
+
rtau=None, ftau=None, amp0=None, t0=None,
|
|
1373
|
+
feature_base_name='rftau',
|
|
1374
|
+
**kwargs):
|
|
1375
|
+
"""
|
|
1376
|
+
Feature extraction for the trace baseline.
|
|
1377
|
+
|
|
1378
|
+
Parameters
|
|
1379
|
+
----------
|
|
1380
|
+
trace : ndarray
|
|
1381
|
+
An ndarray containing the raw data to extract the feature
|
|
1382
|
+
from.
|
|
1383
|
+
|
|
1384
|
+
rtau : int, optional
|
|
1385
|
+
The rise time of the pulse, in samples.
|
|
1386
|
+
Default: 30
|
|
1387
|
+
|
|
1388
|
+
ftau : int, optional
|
|
1389
|
+
The fall time of the pulse, in samples.
|
|
1390
|
+
Default: 100
|
|
1391
|
+
|
|
1392
|
+
amp0 : int, optional
|
|
1393
|
+
The amplitude of the pulse.
|
|
1394
|
+
Default: estimated from data
|
|
1395
|
+
|
|
1396
|
+
t0 : int, optional
|
|
1397
|
+
The start time of the pulse, in samples.
|
|
1398
|
+
Default: midpoint - 10 samples
|
|
1399
|
+
|
|
1400
|
+
feature_base_name : str, optional
|
|
1401
|
+
output feature base name
|
|
1402
|
+
|
|
1403
|
+
Returns
|
|
1404
|
+
-------
|
|
1405
|
+
retdict : dict
|
|
1406
|
+
Dictionary containing the various extracted features.
|
|
1407
|
+
|
|
1408
|
+
"""
|
|
1409
|
+
|
|
1410
|
+
# check if trace is empty or None
|
|
1411
|
+
if (trace is None or trace.size==0):
|
|
1412
|
+
retdict = {
|
|
1413
|
+
feature_base_name: -999999.0,
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
return retdict
|
|
1417
|
+
|
|
1418
|
+
# set default parameters
|
|
1419
|
+
if rtau is None:
|
|
1420
|
+
rtau = 30 # samples
|
|
1421
|
+
if ftau is None:
|
|
1422
|
+
ftau = 100 # samples
|
|
1423
|
+
if t0 is None:
|
|
1424
|
+
t0 = int(trace.shape[-1]/2) - 10 # midpoint-10
|
|
1425
|
+
|
|
1426
|
+
lw = 800; uw = 1600 # could make these parameters; hard-coded for now. beware changing them, long windows eat CPU time with little added benefit
|
|
1427
|
+
tt = np.arange(0,trace.shape[0],1)
|
|
1428
|
+
baseline = np.mean(trace[t0-lw:t0])
|
|
1429
|
+
traceb = (trace-baseline) # get a good baseline in precisely the window we care about
|
|
1430
|
+
|
|
1431
|
+
# RC filter the trace -- I wish this were done in hardware
|
|
1432
|
+
fs = 1.25e6 # Sampling frequency (Hz)
|
|
1433
|
+
fc = 50e3 # Desired cutoff frequency (Hz)
|
|
1434
|
+
order = 1 # RC filter order
|
|
1435
|
+
b, a = signal.butter(order, fc / (0.5 * fs), btype='low')
|
|
1436
|
+
tracerc = signal.lfilter(b,a,traceb)
|
|
1437
|
+
|
|
1438
|
+
if amp0 is None:
|
|
1439
|
+
amp0 = np.max(tracerc[t0-lw:t0+uw])
|
|
1440
|
+
|
|
1441
|
+
try: # this is necessary for corner cases in which the template fit barfs
|
|
1442
|
+
(opt, cov) = curve_fit(utils.twopole, tt[t0-lw:t0+uw], tracerc[t0-lw:t0+uw], p0 = [rtau, ftau, amp0, t0])#, bounds=(0,[100,1000,1e-6,t0+100]))
|
|
1443
|
+
except:
|
|
1444
|
+
opt = [-1,-1,-1,-1]
|
|
1445
|
+
amp = np.max(utils.twopole(tt, *opt))
|
|
1446
|
+
chisq = np.sum((tracerc[t0-lw:t0+uw]-utils.twopole(tt[t0-lw:t0+uw], *opt))**2)/(uw+lw)
|
|
1447
|
+
|
|
1448
|
+
retdict = {
|
|
1449
|
+
('risetime_' + feature_base_name): opt[0],
|
|
1450
|
+
('falltime_' + feature_base_name): opt[1],
|
|
1451
|
+
('amplitud_' + feature_base_name): amp,
|
|
1452
|
+
('chisq_' + feature_base_name): chisq,
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
return retdict
|