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.
Files changed (60) hide show
  1. pytesprocess/__init__.py +9 -0
  2. pytesprocess/_version.py +2 -0
  3. pytesprocess/cli/__init__.py +1 -0
  4. pytesprocess/cli/commands/__init__.py +5 -0
  5. pytesprocess/cli/commands/event.py +66 -0
  6. pytesprocess/cli/commands/filter.py +17 -0
  7. pytesprocess/cli/commands/ivsweep.py +29 -0
  8. pytesprocess/cli/common.py +86 -0
  9. pytesprocess/cli/main.py +81 -0
  10. pytesprocess/config/__init__.py +4 -0
  11. pytesprocess/config/loader.py +94 -0
  12. pytesprocess/config/manager.py +297 -0
  13. pytesprocess/config/resolvers/__init__.py +5 -0
  14. pytesprocess/config/resolvers/common.py +56 -0
  15. pytesprocess/config/resolvers/feature.py +293 -0
  16. pytesprocess/config/resolvers/salting.py +86 -0
  17. pytesprocess/config/resolvers/trigger.py +84 -0
  18. pytesprocess/config/selectors.py +108 -0
  19. pytesprocess/config/validation.py +314 -0
  20. pytesprocess/config/warnings.py +2 -0
  21. pytesprocess/core/__init__.py +10 -0
  22. pytesprocess/core/algorithms.py +1455 -0
  23. pytesprocess/core/didv.py +1648 -0
  24. pytesprocess/core/eventbuilder.py +495 -0
  25. pytesprocess/core/filterbuilder.py +81 -0
  26. pytesprocess/core/filterdata.py +1849 -0
  27. pytesprocess/core/ivsweep.py +2072 -0
  28. pytesprocess/core/noise.py +923 -0
  29. pytesprocess/core/noisemodel.py +1408 -0
  30. pytesprocess/core/oftrigger.py +1035 -0
  31. pytesprocess/core/template.py +450 -0
  32. pytesprocess/process/__init__.py +6 -0
  33. pytesprocess/process/data_source.py +185 -0
  34. pytesprocess/process/event_context.py +35 -0
  35. pytesprocess/process/feature_plan.py +186 -0
  36. pytesprocess/process/feature_resources.py +267 -0
  37. pytesprocess/process/features.py +1024 -0
  38. pytesprocess/process/filterprocess.py +1176 -0
  39. pytesprocess/process/ivprocess.py +1380 -0
  40. pytesprocess/process/processing_data.py +967 -0
  41. pytesprocess/process/randoms.py +921 -0
  42. pytesprocess/process/triggers.py +1011 -0
  43. pytesprocess/salting/__init__.py +7 -0
  44. pytesprocess/salting/generator.py +364 -0
  45. pytesprocess/salting/injector.py +329 -0
  46. pytesprocess/salting/sampling.py +84 -0
  47. pytesprocess/utils/__init__.py +5 -0
  48. pytesprocess/utils/arg_utils.py +122 -0
  49. pytesprocess/utils/dataframe_output.py +120 -0
  50. pytesprocess/utils/filter_hdf5.py +594 -0
  51. pytesprocess/utils/utils.py +701 -0
  52. pytesprocess/workflows/__init__.py +3 -0
  53. pytesprocess/workflows/processing.py +317 -0
  54. pytesprocess/workflows/salting.py +133 -0
  55. pytesprocess-0.1.1.dist-info/METADATA +211 -0
  56. pytesprocess-0.1.1.dist-info/RECORD +60 -0
  57. pytesprocess-0.1.1.dist-info/WHEEL +5 -0
  58. pytesprocess-0.1.1.dist-info/entry_points.txt +2 -0
  59. pytesprocess-0.1.1.dist-info/licenses/LICENSE +21 -0
  60. 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)