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,495 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pyarrow as pa
|
|
5
|
+
import vaex as vx
|
|
6
|
+
|
|
7
|
+
warnings.filterwarnings("ignore")
|
|
8
|
+
|
|
9
|
+
vx.settings.main.thread_count = 1
|
|
10
|
+
vx.settings.main.thread_count_io = 1
|
|
11
|
+
pa.set_cpu_count(1)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EventBuilder:
|
|
15
|
+
"""
|
|
16
|
+
Class for storing trigger data from single continuous trace,
|
|
17
|
+
finding coincident event, and constructing event(s) information
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""
|
|
22
|
+
Intialization
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# trigger id
|
|
26
|
+
self._current_trigger_id = 0
|
|
27
|
+
self._current_event_time = 0
|
|
28
|
+
self._current_nb_samples = None
|
|
29
|
+
|
|
30
|
+
# event dataframe containing all triggers
|
|
31
|
+
self._event_df = None
|
|
32
|
+
|
|
33
|
+
# Initialize trigger data
|
|
34
|
+
self._trigger_objects = None
|
|
35
|
+
self._trigger_names = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def clear_event(self):
|
|
39
|
+
"""
|
|
40
|
+
clear
|
|
41
|
+
"""
|
|
42
|
+
self._event_df = None
|
|
43
|
+
self._trigger_names = None
|
|
44
|
+
|
|
45
|
+
# FIXME clear object...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_event_df(self):
|
|
49
|
+
"""
|
|
50
|
+
Get event data frame
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
return self._event_df
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def add_trigger_object(self, trigger_name, trigger_object):
|
|
58
|
+
"""
|
|
59
|
+
Add trigger object
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
if self._trigger_objects is None:
|
|
63
|
+
self._trigger_objects = dict()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if trigger_name in self._trigger_objects.keys():
|
|
67
|
+
raise ValueError(
|
|
68
|
+
'ERROR: Trigger object "' + trigger_name
|
|
69
|
+
+ ' already stored!')
|
|
70
|
+
|
|
71
|
+
# store in dictionary
|
|
72
|
+
self._trigger_objects[trigger_name] = trigger_object
|
|
73
|
+
|
|
74
|
+
# keep trigger name in list
|
|
75
|
+
if self._trigger_names is None:
|
|
76
|
+
self._trigger_names = list()
|
|
77
|
+
|
|
78
|
+
self._trigger_names.append(trigger_name)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_trigger_object(self, trigger_name):
|
|
82
|
+
"""
|
|
83
|
+
Get trigger object
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
if trigger_name not in self._trigger_objects.keys():
|
|
87
|
+
raise ValueError(
|
|
88
|
+
'ERROR: Trigger object "' + trigger_name
|
|
89
|
+
+ ' does not exist!')
|
|
90
|
+
|
|
91
|
+
return self._trigger_object[trigger_name]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def add_trigger_data(self, trigger_name, trigger_data):
|
|
96
|
+
"""
|
|
97
|
+
Add trigger data dictionary for a specific
|
|
98
|
+
trigger channel
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
# intialize if needed
|
|
102
|
+
if self._trigger_names is None:
|
|
103
|
+
self._trigger_names = list()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# check if trigger channel already saved
|
|
107
|
+
if trigger_name in self._trigger_names:
|
|
108
|
+
raise ValueError('ERROR: Trigger data for channel '
|
|
109
|
+
+ trigger_name + ' already added!')
|
|
110
|
+
|
|
111
|
+
# add to list
|
|
112
|
+
self._trigger_names.append(trigger_name)
|
|
113
|
+
|
|
114
|
+
# dataframe
|
|
115
|
+
if self._event_df is None:
|
|
116
|
+
self._event_df = trigger_data
|
|
117
|
+
else:
|
|
118
|
+
self._event_df = vx.concat(
|
|
119
|
+
[self._event_df, trigger_data]
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# sort by trigger index
|
|
123
|
+
self._event_df = self._event_df.sort('trigger_index')
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def acquire_triggers(self, trigger_name, trace, thresh,
|
|
128
|
+
pileup_window_msec=None,
|
|
129
|
+
pileup_window_samples=None,
|
|
130
|
+
positive_pulses=True,
|
|
131
|
+
run_residual=False,
|
|
132
|
+
sat_amps_50kHz=None,
|
|
133
|
+
edge_exclusion_msec=None,
|
|
134
|
+
livetime=None):
|
|
135
|
+
"""
|
|
136
|
+
calc
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
# find trigger object
|
|
140
|
+
if trigger_name not in self._trigger_objects.keys():
|
|
141
|
+
raise ValueError(
|
|
142
|
+
'ERROR: Trigger object ' + trigger_name
|
|
143
|
+
+ ' not found!')
|
|
144
|
+
|
|
145
|
+
trigger_obj = self._trigger_objects[trigger_name]
|
|
146
|
+
|
|
147
|
+
# update trace
|
|
148
|
+
trigger_obj.update_trace(trace)
|
|
149
|
+
self._current_nb_samples = trace.shape[-1]
|
|
150
|
+
|
|
151
|
+
# find triggers
|
|
152
|
+
trigger_obj.find_triggers(
|
|
153
|
+
thresh,
|
|
154
|
+
pileup_window_msec=pileup_window_msec,
|
|
155
|
+
pileup_window_samples=pileup_window_samples,
|
|
156
|
+
positive_pulses=positive_pulses,
|
|
157
|
+
residual=run_residual,
|
|
158
|
+
saturation_amplitudes_LPF_50kHz=sat_amps_50kHz,
|
|
159
|
+
edge_exclusion_msec=edge_exclusion_msec,
|
|
160
|
+
livetime=livetime
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# append trigger data to event dataframe
|
|
164
|
+
# sort by trigger index
|
|
165
|
+
df = trigger_obj.get_trigger_data_df()
|
|
166
|
+
if (df is not None and len(df)!=0):
|
|
167
|
+
if self._event_df is None:
|
|
168
|
+
self._event_df = df
|
|
169
|
+
else:
|
|
170
|
+
self._event_df = vx.concat(
|
|
171
|
+
[self._event_df, df]
|
|
172
|
+
)
|
|
173
|
+
# sort by trigger index
|
|
174
|
+
self._event_df = self._event_df.sort('trigger_index')
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def build_event(self, event_metadata=None,
|
|
180
|
+
fs=None,
|
|
181
|
+
coincident_window_msec=None,
|
|
182
|
+
coincident_window_samples=None,
|
|
183
|
+
nb_trigger_channels=None,
|
|
184
|
+
trace_length_continuous_sec=None):
|
|
185
|
+
"""
|
|
186
|
+
Function to merge coincident
|
|
187
|
+
events based on user defined window (in msec or samples)
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
# metadata
|
|
191
|
+
if event_metadata is None:
|
|
192
|
+
event_metadata = dict()
|
|
193
|
+
|
|
194
|
+
# sample rate
|
|
195
|
+
if (fs is None and
|
|
196
|
+
'sample_rate' in event_metadata.keys()):
|
|
197
|
+
fs = event_metadata['sample_rate']
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# check if fs required
|
|
201
|
+
if (fs is None and coincident_window_msec is not None):
|
|
202
|
+
raise ValueError('ERROR: sample rate required ("fs")')
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# trace length in seconds
|
|
206
|
+
if trace_length_continuous_sec is None:
|
|
207
|
+
|
|
208
|
+
if (self._current_nb_samples is None
|
|
209
|
+
and 'nb_samples' in event_metadata.keys()):
|
|
210
|
+
self._current_nb_samples = (
|
|
211
|
+
event_metadata['nb_samples'])
|
|
212
|
+
|
|
213
|
+
if (self._current_nb_samples is None or fs is None):
|
|
214
|
+
raise ValueError('ERROR: "trace_length_continuous_sec" '
|
|
215
|
+
+ 'argument required!')
|
|
216
|
+
|
|
217
|
+
trace_length_continuous_sec = (
|
|
218
|
+
self._current_nb_samples/fs)
|
|
219
|
+
|
|
220
|
+
# event time
|
|
221
|
+
event_time_start = np.nan
|
|
222
|
+
event_time_end = np.nan
|
|
223
|
+
if 'event_time' in event_metadata.keys():
|
|
224
|
+
event_time_data = event_metadata['event_time']
|
|
225
|
+
if (event_time_data>=self._current_event_time):
|
|
226
|
+
event_time_start = event_time_data
|
|
227
|
+
else:
|
|
228
|
+
event_time_start = self._current_event_time
|
|
229
|
+
event_time_end = int(event_time_start + trace_length_continuous_sec)
|
|
230
|
+
|
|
231
|
+
# store event time
|
|
232
|
+
self._current_event_time = event_time_end
|
|
233
|
+
|
|
234
|
+
# check if any triggers
|
|
235
|
+
if (self._event_df is None or len(self._event_df)==0):
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
# merge coincident events
|
|
239
|
+
if (nb_trigger_channels is None
|
|
240
|
+
or nb_trigger_channels > 1):
|
|
241
|
+
self._merge_coincident_triggers(
|
|
242
|
+
fs=fs,
|
|
243
|
+
coincident_window_msec=coincident_window_msec,
|
|
244
|
+
coincident_window_samples=coincident_window_samples
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# number of triggers (after merging coincident events)
|
|
248
|
+
nb_triggers = len(self._event_df)
|
|
249
|
+
|
|
250
|
+
# Canonical processing/raw provenance.
|
|
251
|
+
default_val_string = pa.array([None]*nb_triggers, type=pa.string())
|
|
252
|
+
metadata_string_dict = {
|
|
253
|
+
'measurement_type': default_val_string,
|
|
254
|
+
}
|
|
255
|
+
for key in metadata_string_dict:
|
|
256
|
+
if key in event_metadata:
|
|
257
|
+
val = str(event_metadata[key]).replace('\0', '')
|
|
258
|
+
metadata_string_dict[key] = pa.array(
|
|
259
|
+
[val]*nb_triggers, type=pa.string()
|
|
260
|
+
)
|
|
261
|
+
# Input compatibility for old callers that still provide data_type or
|
|
262
|
+
# run_type rather than measurement_type.
|
|
263
|
+
if metadata_string_dict['measurement_type'].null_count == nb_triggers:
|
|
264
|
+
for legacy_key in ('data_type', 'run_type'):
|
|
265
|
+
if legacy_key in event_metadata:
|
|
266
|
+
val = str(event_metadata[legacy_key]).replace('\0', '')
|
|
267
|
+
metadata_string_dict['measurement_type'] = pa.array(
|
|
268
|
+
[val]*nb_triggers, type=pa.string()
|
|
269
|
+
)
|
|
270
|
+
break
|
|
271
|
+
for key, val in metadata_string_dict.items():
|
|
272
|
+
self._event_df[key] = val
|
|
273
|
+
|
|
274
|
+
default_val = np.array([-1]*nb_triggers, dtype=np.int64)
|
|
275
|
+
metadata_dict = {
|
|
276
|
+
'stream_number': default_val.copy(),
|
|
277
|
+
'global_segment_number': default_val.copy(),
|
|
278
|
+
'fridge_run_number': default_val.copy(),
|
|
279
|
+
}
|
|
280
|
+
for key in metadata_dict:
|
|
281
|
+
if key in event_metadata and event_metadata[key] is not None:
|
|
282
|
+
metadata_dict[key] = np.array(
|
|
283
|
+
[np.int64(event_metadata[key])]*nb_triggers
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Absolute start timestamps are stored separately from elapsed times.
|
|
287
|
+
start_time_fields = (
|
|
288
|
+
'stream_start_time',
|
|
289
|
+
'acquisition_start_time',
|
|
290
|
+
'fridge_run_start_time',
|
|
291
|
+
)
|
|
292
|
+
absolute_starts = {}
|
|
293
|
+
for key in start_time_fields:
|
|
294
|
+
value = event_metadata.get(key)
|
|
295
|
+
if value is None or not np.isfinite(float(value)):
|
|
296
|
+
absolute_starts[key] = np.nan
|
|
297
|
+
self._event_df[key] = np.full(nb_triggers, np.nan)
|
|
298
|
+
else:
|
|
299
|
+
absolute_starts[key] = float(value)
|
|
300
|
+
self._event_df[key] = np.full(nb_triggers, float(value))
|
|
301
|
+
|
|
302
|
+
trigger_times = self._event_df['trigger_time'].values
|
|
303
|
+
event_times = trigger_times + event_time_start
|
|
304
|
+
event_times_int = np.int64(np.around(event_times))
|
|
305
|
+
metadata_dict['event_time'] = event_times_int
|
|
306
|
+
|
|
307
|
+
elapsed_map = {
|
|
308
|
+
'stream_start_time': 'time_since_stream_start_s',
|
|
309
|
+
'acquisition_start_time': 'time_since_acquisition_start_s',
|
|
310
|
+
'fridge_run_start_time': 'time_since_fridge_run_start_s',
|
|
311
|
+
}
|
|
312
|
+
for start_key, elapsed_key in elapsed_map.items():
|
|
313
|
+
start_value = absolute_starts[start_key]
|
|
314
|
+
if np.isfinite(start_value):
|
|
315
|
+
self._event_df[elapsed_key] = event_times - start_value
|
|
316
|
+
else:
|
|
317
|
+
self._event_df[elapsed_key] = np.full(nb_triggers, np.nan)
|
|
318
|
+
|
|
319
|
+
# trigger id
|
|
320
|
+
metadata_dict['trigger_prod_id'] = (
|
|
321
|
+
np.array(range(nb_triggers))
|
|
322
|
+
+ np.int64(self._current_trigger_id)
|
|
323
|
+
+ 1)
|
|
324
|
+
|
|
325
|
+
self._current_trigger_id = metadata_dict['trigger_prod_id'][-1]
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# add to dataframe
|
|
329
|
+
for key, val in metadata_dict.items():
|
|
330
|
+
self._event_df[key] = val
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _merge_coincident_triggers(self, fs=None,
|
|
334
|
+
coincident_window_msec=None,
|
|
335
|
+
coincident_window_samples=None):
|
|
336
|
+
"""
|
|
337
|
+
Function to merge coincident
|
|
338
|
+
events based on user defined window (in msec or samples)
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
# check
|
|
342
|
+
if (self._event_df is None or len(self._event_df)==0):
|
|
343
|
+
raise ValueError('ERROR: No trigger data '
|
|
344
|
+
+ 'available')
|
|
345
|
+
|
|
346
|
+
# merge window
|
|
347
|
+
merge_window = 0
|
|
348
|
+
if coincident_window_msec is not None:
|
|
349
|
+
if fs is None:
|
|
350
|
+
raise ValueError(
|
|
351
|
+
'ERROR: sample rate "fs" needs to be provided!'
|
|
352
|
+
)
|
|
353
|
+
merge_window = int(coincident_window_msec*fs/1000)
|
|
354
|
+
elif coincident_window_samples is not None:
|
|
355
|
+
merge_window = coincident_window_samples
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
if merge_window == 0:
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
# let's convert vaex dataframe to pandas so we can modify it
|
|
362
|
+
# more easily
|
|
363
|
+
df_pandas = self._event_df.to_pandas_df()
|
|
364
|
+
|
|
365
|
+
# get trigger index and delta chi2
|
|
366
|
+
trigger_indices = np.array(df_pandas['trigger_index'].values)
|
|
367
|
+
trigger_delta_chi2s = np.array(df_pandas['trigger_delta_chi2'].values)
|
|
368
|
+
trigger_names = np.array(df_pandas['trigger_channel'].values)
|
|
369
|
+
|
|
370
|
+
# find list of indices within merge_window
|
|
371
|
+
# then store in list of index ranges
|
|
372
|
+
lgc_coincident = np.diff(trigger_indices) < merge_window
|
|
373
|
+
lgc_coincident = np.concatenate(([0], lgc_coincident, [0]))
|
|
374
|
+
lgc_coincident_diff = np.abs(np.diff(lgc_coincident))
|
|
375
|
+
coincident_ranges = np.where(lgc_coincident_diff == 1)[0].reshape(-1, 2)
|
|
376
|
+
|
|
377
|
+
# let's first loop through ranges
|
|
378
|
+
# then
|
|
379
|
+
# - disregard if only one channel (=pileups)
|
|
380
|
+
# - further split if combination of pile-ups and coincidents
|
|
381
|
+
# then save indices
|
|
382
|
+
coincident_indices = list()
|
|
383
|
+
for range_it in coincident_ranges:
|
|
384
|
+
indices = np.arange(range_it[0], range_it[1]+1)
|
|
385
|
+
channels = trigger_names[indices]
|
|
386
|
+
channels_unique = np.unique(channels)
|
|
387
|
+
|
|
388
|
+
# case single channel -> pileup
|
|
389
|
+
if len(channels_unique) == 1:
|
|
390
|
+
continue
|
|
391
|
+
|
|
392
|
+
# case only coincident
|
|
393
|
+
if len(channels_unique) == len(channels):
|
|
394
|
+
coincident_indices.append(indices)
|
|
395
|
+
|
|
396
|
+
# case mix coincident/pileup -> split
|
|
397
|
+
|
|
398
|
+
# let's do something simple. Group indices
|
|
399
|
+
# in function of time so each sublist have
|
|
400
|
+
# single channels
|
|
401
|
+
|
|
402
|
+
if len(channels_unique) < len(channels):
|
|
403
|
+
|
|
404
|
+
# initialize list split
|
|
405
|
+
indices_split = list()
|
|
406
|
+
|
|
407
|
+
# loop channels
|
|
408
|
+
current_chan_list = list()
|
|
409
|
+
current_ind_list = list()
|
|
410
|
+
for chan_it in range(len(channels)):
|
|
411
|
+
|
|
412
|
+
chan = channels[chan_it]
|
|
413
|
+
chan_ind = indices[chan_it]
|
|
414
|
+
|
|
415
|
+
if chan in current_chan_list:
|
|
416
|
+
# save current indices
|
|
417
|
+
indices_split.append(current_ind_list)
|
|
418
|
+
|
|
419
|
+
# reset list
|
|
420
|
+
current_chan_list = list()
|
|
421
|
+
current_ind_list = list()
|
|
422
|
+
|
|
423
|
+
# add to list
|
|
424
|
+
current_chan_list.append(chan)
|
|
425
|
+
current_ind_list.append(chan_ind)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
# save last iteration
|
|
430
|
+
if current_ind_list:
|
|
431
|
+
indices_split.append(current_ind_list)
|
|
432
|
+
|
|
433
|
+
# loop indices and save if not single value
|
|
434
|
+
for inds in indices_split:
|
|
435
|
+
# if single channel -> not coincident
|
|
436
|
+
if len(inds)==1:
|
|
437
|
+
continue
|
|
438
|
+
else:
|
|
439
|
+
coincident_indices.append(inds)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
# Loop coincident indices then
|
|
443
|
+
# - find the one with maximum amplitude -> primary trigger
|
|
444
|
+
# - merge other channels trigger value to primary and remove row
|
|
445
|
+
column_inds_to_drop = list()
|
|
446
|
+
for inds in coincident_indices:
|
|
447
|
+
|
|
448
|
+
# amplitudes
|
|
449
|
+
delta_chi2s = trigger_delta_chi2s[inds]
|
|
450
|
+
max_index = delta_chi2s.argmax()
|
|
451
|
+
|
|
452
|
+
# primary trigger
|
|
453
|
+
primary_index = int(inds[max_index])
|
|
454
|
+
primary_channel = trigger_names[primary_index]
|
|
455
|
+
|
|
456
|
+
# other triggers
|
|
457
|
+
other_indices = inds[inds!=primary_index]
|
|
458
|
+
other_channels = trigger_names[other_indices]
|
|
459
|
+
|
|
460
|
+
if not isinstance(other_indices, np.ndarray):
|
|
461
|
+
other_indices = [other_indices]
|
|
462
|
+
other_channels = [other_channels]
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
# loop other channels and add trigger specific parameters
|
|
466
|
+
# into primary channel row
|
|
467
|
+
for other_it in range(len(other_indices)):
|
|
468
|
+
|
|
469
|
+
other_index = int(other_indices[other_it])
|
|
470
|
+
other_chan = str(other_channels[other_it])
|
|
471
|
+
|
|
472
|
+
# find channel specific columns
|
|
473
|
+
column_names = np.array(
|
|
474
|
+
df_pandas.columns[df_pandas.iloc[other_index].notnull()]
|
|
475
|
+
).astype('U')
|
|
476
|
+
|
|
477
|
+
matching_elements = np.char.find(column_names, other_chan) >= 0
|
|
478
|
+
column_names = column_names[matching_elements]
|
|
479
|
+
|
|
480
|
+
# replace values
|
|
481
|
+
for column_name in column_names:
|
|
482
|
+
df_pandas[column_name][primary_index] = (
|
|
483
|
+
df_pandas[column_name][other_index])
|
|
484
|
+
|
|
485
|
+
# add to drop list
|
|
486
|
+
column_inds_to_drop.append(other_index)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
# drop rows non primary trigger channels
|
|
490
|
+
if column_inds_to_drop:
|
|
491
|
+
df_pandas = df_pandas.drop(column_inds_to_drop)
|
|
492
|
+
|
|
493
|
+
# convert back to vaex
|
|
494
|
+
self._event_df = vx.from_pandas(df_pandas, copy_index=False)
|
|
495
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from pytesprocess.core.filterdata import FilterData
|
|
2
|
+
from pytesprocess.core.noise import Noise
|
|
3
|
+
from pytesprocess.core.template import Template
|
|
4
|
+
from pytesprocess.core.didv import DIDVAnalysis
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FilterBuilder:
|
|
8
|
+
"""
|
|
9
|
+
Orchestrator class for building a shared filter-data object
|
|
10
|
+
using Noise, Template, and DIDVAnalysis processors.
|
|
11
|
+
|
|
12
|
+
All processors share the same internal _filter_data dictionary.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, verbose=True,
|
|
16
|
+
auto_save_hdf5=False,
|
|
17
|
+
didv_save_path=None):
|
|
18
|
+
self._verbose = verbose
|
|
19
|
+
|
|
20
|
+
# shared persistent store
|
|
21
|
+
self._filter_store = {}
|
|
22
|
+
|
|
23
|
+
# generic store API
|
|
24
|
+
self.store = FilterData(verbose=verbose,
|
|
25
|
+
filter_data=self._filter_store)
|
|
26
|
+
|
|
27
|
+
# processors sharing same store
|
|
28
|
+
self.noise = Noise(verbose=verbose,
|
|
29
|
+
filter_data=self._filter_store)
|
|
30
|
+
|
|
31
|
+
self.template = Template(verbose=verbose,
|
|
32
|
+
filter_data=self._filter_store)
|
|
33
|
+
|
|
34
|
+
self.didv = DIDVAnalysis(verbose=verbose,
|
|
35
|
+
auto_save_hdf5=auto_save_hdf5,
|
|
36
|
+
save_path=didv_save_path,
|
|
37
|
+
filter_data=self._filter_store)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def filter_data(self):
|
|
41
|
+
"""
|
|
42
|
+
Direct access to the shared filter-data dictionary.
|
|
43
|
+
"""
|
|
44
|
+
return self._filter_store
|
|
45
|
+
|
|
46
|
+
def describe(self):
|
|
47
|
+
"""
|
|
48
|
+
Describe current shared filter data.
|
|
49
|
+
"""
|
|
50
|
+
self.store.describe()
|
|
51
|
+
|
|
52
|
+
def clear(self, channels=None, tag=None,
|
|
53
|
+
clear_noise_state=True,
|
|
54
|
+
clear_template_state=False,
|
|
55
|
+
clear_didv_state=True):
|
|
56
|
+
"""
|
|
57
|
+
Clear shared filter data and optionally processor transient state.
|
|
58
|
+
"""
|
|
59
|
+
self.store.clear_data(channels=channels, tag=tag)
|
|
60
|
+
|
|
61
|
+
if clear_noise_state:
|
|
62
|
+
self.noise.clear_randoms()
|
|
63
|
+
|
|
64
|
+
if clear_template_state:
|
|
65
|
+
if hasattr(self.template, "clear"):
|
|
66
|
+
self.template.clear(channels=channels)
|
|
67
|
+
|
|
68
|
+
if clear_didv_state:
|
|
69
|
+
self.didv.clear(channels=channels)
|
|
70
|
+
|
|
71
|
+
def load_hdf5(self, file_name, overwrite=True):
|
|
72
|
+
"""
|
|
73
|
+
Load filter data into shared store.
|
|
74
|
+
"""
|
|
75
|
+
self.store.load_hdf5(file_name, overwrite=overwrite)
|
|
76
|
+
|
|
77
|
+
def save_hdf5(self, file_name, overwrite=False):
|
|
78
|
+
"""
|
|
79
|
+
Save shared store to file.
|
|
80
|
+
"""
|
|
81
|
+
self.store.save_hdf5(file_name, overwrite=overwrite)
|