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,1024 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import gc
|
|
3
|
+
import importlib
|
|
4
|
+
from itertools import repeat
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
import warnings
|
|
10
|
+
|
|
11
|
+
from humanfriendly import parse_size
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pandas as pd
|
|
14
|
+
import pyarrow as pa
|
|
15
|
+
import vaex as vx
|
|
16
|
+
import yaml
|
|
17
|
+
from pytesdaqx.io import AcquisitionCatalog
|
|
18
|
+
|
|
19
|
+
from pytesprocess.config import ProcessingConfig
|
|
20
|
+
from pytesprocess.core.algorithms import FeatureExtractors
|
|
21
|
+
from pytesprocess.process.feature_plan import FeaturePlan
|
|
22
|
+
from pytesprocess.process.feature_resources import EventTraceCache, FeatureResources
|
|
23
|
+
from pytesprocess.process.processing_data import ProcessingData
|
|
24
|
+
from pytesprocess.utils import create_dataframe_group
|
|
25
|
+
|
|
26
|
+
warnings.filterwarnings("ignore")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
vx.settings.main.thread_count = 1
|
|
30
|
+
vx.settings.main.thread_count_io = 1
|
|
31
|
+
pa.set_cpu_count(1)
|
|
32
|
+
|
|
33
|
+
os.environ["OMP_NUM_THREADS"] = "1"
|
|
34
|
+
os.environ["MKL_NUM_THREADS"] = "1"
|
|
35
|
+
os.environ["NUMEXPR_NUM_THREADS"] = "1"
|
|
36
|
+
os.environ["OPENBLAS_NUM_THREADS"] = "1"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
'FeatureProcessing'
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
class FeatureProcessing:
|
|
44
|
+
"""
|
|
45
|
+
Class to manage data processing and
|
|
46
|
+
extract features, dataframe can be saved
|
|
47
|
+
in hdf5 using vaex framework and returned
|
|
48
|
+
as a pandas dataframe (not both)
|
|
49
|
+
|
|
50
|
+
Multiple nodes can be used if data splitted in
|
|
51
|
+
different streams/tasks
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, data_path, config_data,
|
|
56
|
+
streams=None,
|
|
57
|
+
trigger_dataframe_path=None,
|
|
58
|
+
external_file=None,
|
|
59
|
+
processing_label=None,
|
|
60
|
+
restricted=False,
|
|
61
|
+
data_type='background',
|
|
62
|
+
salting_dataframe=None,
|
|
63
|
+
verbose=True):
|
|
64
|
+
"""
|
|
65
|
+
Intialize data processing
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
---------
|
|
69
|
+
|
|
70
|
+
data_path : str or list of str
|
|
71
|
+
data group directory containing data OR full path to HDF5 file
|
|
72
|
+
(or list of files). Data can be either raw data or
|
|
73
|
+
vaex dataframes
|
|
74
|
+
|
|
75
|
+
config_file : str
|
|
76
|
+
Full path and file name to the YAML settings for the
|
|
77
|
+
processing.
|
|
78
|
+
|
|
79
|
+
output_path : str, optional (default=No saved file)
|
|
80
|
+
base directory where output feature file will be saved
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
external_file : str, optional (default=no external files)
|
|
84
|
+
The path to a .py file with a FeatureExtractors class
|
|
85
|
+
to add algorithms developed by users. The name of the static
|
|
86
|
+
functions cannot be same as in _features (no duplicate). This is
|
|
87
|
+
meant for rapid development of features without
|
|
88
|
+
needing to rebuild the package.
|
|
89
|
+
|
|
90
|
+
streams : str or list of str, optional
|
|
91
|
+
Streams to process from the acquisition.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
processing_label : str, optional
|
|
95
|
+
an optional processing name. This is used to be build output subdirectory name
|
|
96
|
+
and is saved as a feature in DetaFrame so it can then be used later during
|
|
97
|
+
analysis to make a cut on a specific processing when mutliple
|
|
98
|
+
datasets/processing are added together.
|
|
99
|
+
|
|
100
|
+
restricted : boolean
|
|
101
|
+
if True, use restricted data
|
|
102
|
+
if False (default), exclude restricted data
|
|
103
|
+
|
|
104
|
+
data_type : string
|
|
105
|
+
data type: "background", "calibration", "iv", ...
|
|
106
|
+
|
|
107
|
+
salting_dataframe : str or vaex dataframe
|
|
108
|
+
str if path to vaex hdf5 file or directly a dataframe
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
verbose : bool, optional
|
|
112
|
+
if True, display info
|
|
113
|
+
|
|
114
|
+
Return
|
|
115
|
+
------
|
|
116
|
+
None
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# processing id
|
|
120
|
+
self._processing_label = processing_label
|
|
121
|
+
|
|
122
|
+
# streams
|
|
123
|
+
self._streams = streams
|
|
124
|
+
|
|
125
|
+
# restricted data
|
|
126
|
+
self._restricted = restricted
|
|
127
|
+
if data_type != 'background': # only background data are restricted
|
|
128
|
+
self._restricted = False
|
|
129
|
+
|
|
130
|
+
# display
|
|
131
|
+
self._verbose = verbose
|
|
132
|
+
|
|
133
|
+
# if no dataframe, the data type needs to be threshold
|
|
134
|
+
if trigger_dataframe_path is None:
|
|
135
|
+
data_type = 'threshold'
|
|
136
|
+
|
|
137
|
+
# data type
|
|
138
|
+
self._data_type = data_type
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# catalog
|
|
142
|
+
catalog_full = AcquisitionCatalog(data_path, verbose=verbose)
|
|
143
|
+
|
|
144
|
+
# filter based on user input
|
|
145
|
+
self._catalog = catalog_full.filter(
|
|
146
|
+
measurement_types=self._data_type,
|
|
147
|
+
streams=self._streams,
|
|
148
|
+
restricted=self._restricted)
|
|
149
|
+
|
|
150
|
+
self._acquisition_name = self._catalog.acquisition_name
|
|
151
|
+
self._acquisition_path = self._catalog.acquisition_path
|
|
152
|
+
path_obj = Path(self._acquisition_path)
|
|
153
|
+
self._acquisition_base_path = str(path_obj.parent)
|
|
154
|
+
self._available_channels = self._catalog.record_channels
|
|
155
|
+
self._sample_rate_hz = self._catalog.sample_rate_hz
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
rawdata_files = copy.deepcopy(
|
|
159
|
+
self._catalog.select_files_by_stream(
|
|
160
|
+
stream_key='stream_id',
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if not rawdata_files:
|
|
165
|
+
raise ValueError('ERROR: No files were found! Check configuration...')
|
|
166
|
+
|
|
167
|
+
# list of streams
|
|
168
|
+
self._stream_list = list(rawdata_files.keys())
|
|
169
|
+
|
|
170
|
+
# config file
|
|
171
|
+
config_dict = {}
|
|
172
|
+
if isinstance(config_data, str):
|
|
173
|
+
|
|
174
|
+
if not os.path.isfile(config_data):
|
|
175
|
+
raise ValueError(f'ERROR: argument "{config_data}" '
|
|
176
|
+
f'should be a file or ProcessingConfig object!')
|
|
177
|
+
|
|
178
|
+
yaml = ProcessingConfig(config_data, self._available_channels,
|
|
179
|
+
sample_rate=self._sample_rate_hz)
|
|
180
|
+
|
|
181
|
+
config_dict = copy.deepcopy(yaml.get_config('feature'))
|
|
182
|
+
|
|
183
|
+
else:
|
|
184
|
+
|
|
185
|
+
if not isinstance(config_data, ProcessingConfig):
|
|
186
|
+
raise ValueError(
|
|
187
|
+
'ERROR: raw data argument should be either '
|
|
188
|
+
'a directory or ProcessingConfig object'
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
config_dict = copy.deepcopy(config_data.get_config('feature'))
|
|
192
|
+
|
|
193
|
+
# save info
|
|
194
|
+
self._processing_config = config_dict['channels']
|
|
195
|
+
self._selected_channels = config_dict['channel_list']
|
|
196
|
+
self._weights = config_dict['weights']
|
|
197
|
+
self._feature_plan = FeaturePlan(
|
|
198
|
+
self._processing_config, self._available_channels, self._sample_rate_hz
|
|
199
|
+
)
|
|
200
|
+
self._traces_config = self._feature_plan.traces_config
|
|
201
|
+
self._filter_file = config_dict['overall'].get('filter_file')
|
|
202
|
+
|
|
203
|
+
# check channels to be processed
|
|
204
|
+
if not self._selected_channels:
|
|
205
|
+
raise ValueError('ERROR: No channels to be processed! ' +
|
|
206
|
+
'Check configuration...')
|
|
207
|
+
|
|
208
|
+
# Dataframe file list
|
|
209
|
+
self._dataframe_task_list = None
|
|
210
|
+
trigger_files = None
|
|
211
|
+
trigger_path = None
|
|
212
|
+
trigger_group_name = None
|
|
213
|
+
|
|
214
|
+
if trigger_dataframe_path is not None:
|
|
215
|
+
|
|
216
|
+
trigger_files, trigger_path, trigger_group_name = (
|
|
217
|
+
self._get_file_list(trigger_dataframe_path)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if not trigger_files:
|
|
221
|
+
raise ValueError(f'No dataframe files were found! '
|
|
222
|
+
f'Check configuration...')
|
|
223
|
+
|
|
224
|
+
self._dataframe_task_list = list(trigger_files.keys())
|
|
225
|
+
|
|
226
|
+
# External feature extractors
|
|
227
|
+
self._external_file = None
|
|
228
|
+
if external_file is not None:
|
|
229
|
+
if not os.path.exists(external_file):
|
|
230
|
+
raise ValueError('External feature extractors file "'
|
|
231
|
+
+ external_file
|
|
232
|
+
+ '" not found!')
|
|
233
|
+
self._external_file = external_file
|
|
234
|
+
if self._verbose:
|
|
235
|
+
print('INFO: External feature extractor = '
|
|
236
|
+
+ self._external_file)
|
|
237
|
+
|
|
238
|
+
# get list of available features and check for duplicate
|
|
239
|
+
self._algorithm_list, self._ext_algorithm_list = (
|
|
240
|
+
self._extract_algorithm_list()
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# instantiate processing data
|
|
244
|
+
self._output_group_path = None
|
|
245
|
+
|
|
246
|
+
self._processing_data_inst = ProcessingData(
|
|
247
|
+
self._acquisition_base_path,
|
|
248
|
+
rawdata_files,
|
|
249
|
+
acquisition_name=self._acquisition_name,
|
|
250
|
+
trigger_files=trigger_files,
|
|
251
|
+
trigger_group_name=trigger_group_name,
|
|
252
|
+
filter_file=self._filter_file,
|
|
253
|
+
available_channels=self._available_channels,
|
|
254
|
+
salting_dataframe=salting_dataframe,
|
|
255
|
+
verbose=verbose,
|
|
256
|
+
catalog=self._catalog)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# unique id list
|
|
261
|
+
self._id_list = self._stream_list
|
|
262
|
+
if self._dataframe_task_list is not None:
|
|
263
|
+
self._id_list = self._dataframe_task_list
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def get_output_path(self):
|
|
267
|
+
"""Return the current feature dataframe-group path."""
|
|
268
|
+
return self._output_group_path
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def process(self,
|
|
272
|
+
nevents=-1,
|
|
273
|
+
lgc_save=False, save_path=None,
|
|
274
|
+
lgc_output=False,
|
|
275
|
+
ncores=1,
|
|
276
|
+
memory_limit='2GB'):
|
|
277
|
+
|
|
278
|
+
"""
|
|
279
|
+
Process data
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
---------
|
|
283
|
+
|
|
284
|
+
nevents : int, optional
|
|
285
|
+
number of events to be processed
|
|
286
|
+
if not all events, requires ncores = 1
|
|
287
|
+
Default: all available (=-1).
|
|
288
|
+
|
|
289
|
+
lgc_save : bool, optional
|
|
290
|
+
if True, save dataframe in hdf5 files
|
|
291
|
+
Default: False
|
|
292
|
+
|
|
293
|
+
lgc_output : bool, optional
|
|
294
|
+
if True, return dataframe
|
|
295
|
+
Default: False
|
|
296
|
+
|
|
297
|
+
save_path : str, optional
|
|
298
|
+
base directory where output group will be saved
|
|
299
|
+
default: same base path as input data
|
|
300
|
+
|
|
301
|
+
ncores: int, optional
|
|
302
|
+
number of cores that will be used for processing
|
|
303
|
+
default: 1
|
|
304
|
+
|
|
305
|
+
memory_limit : str or float, optional
|
|
306
|
+
memory safety limit when returning a dataframe in memory.
|
|
307
|
+
Saved output is not split by this value; each worker writes one F#### shard.
|
|
308
|
+
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# check input
|
|
313
|
+
if (ncores>1 and nevents>-1):
|
|
314
|
+
raise ValueError('ERROR: Multi cores processing only allowed when '
|
|
315
|
+
+ 'processing ALL events!')
|
|
316
|
+
|
|
317
|
+
if lgc_output and lgc_save:
|
|
318
|
+
raise ValueError('ERROR: Unable to save and output datafame '
|
|
319
|
+
+ 'at the same time. Set either lgc_output '
|
|
320
|
+
+ 'or lgc_save to False.')
|
|
321
|
+
|
|
322
|
+
# check number cores allowed
|
|
323
|
+
if ncores>len(self._id_list):
|
|
324
|
+
ncores = len(self._id_list)
|
|
325
|
+
if self._verbose:
|
|
326
|
+
print('INFO: Changing number cores to '
|
|
327
|
+
+ str(ncores) + ' (maximum allowed)')
|
|
328
|
+
|
|
329
|
+
# Create one dataframe-group identity for the complete feature call.
|
|
330
|
+
output_group_path = None
|
|
331
|
+
dataframe_group = None
|
|
332
|
+
|
|
333
|
+
if lgc_save:
|
|
334
|
+
if save_path is None:
|
|
335
|
+
save_path = self._processing_data_inst.get_raw_path()
|
|
336
|
+
if '/raw' in save_path:
|
|
337
|
+
save_path = save_path.replace('/raw', '/processed')
|
|
338
|
+
if 'processed' not in save_path:
|
|
339
|
+
save_path += '/processed'
|
|
340
|
+
|
|
341
|
+
if self._acquisition_name not in save_path:
|
|
342
|
+
save_path = save_path + '/' + self._acquisition_name
|
|
343
|
+
|
|
344
|
+
dataframe_group = create_dataframe_group(
|
|
345
|
+
save_path, dataframe_type='feature',
|
|
346
|
+
facility=self._processing_data_inst.get_facility(),
|
|
347
|
+
processing_label=self._processing_label,
|
|
348
|
+
restricted=self._restricted, data_type=self._data_type,
|
|
349
|
+
)
|
|
350
|
+
output_group_path = dataframe_group.path
|
|
351
|
+
if self._verbose:
|
|
352
|
+
print(f'INFO: Processing output group path: {output_group_path}')
|
|
353
|
+
|
|
354
|
+
self._output_group_path = output_group_path
|
|
355
|
+
|
|
356
|
+
# Prepare static feature resources once: templates, CSDs, template
|
|
357
|
+
# FFTs, phi/weights and NxMx2 matrices. Event signals are loaded lazily.
|
|
358
|
+
if self._verbose:
|
|
359
|
+
print('INFO: Preparing feature resources!')
|
|
360
|
+
self._feature_resources = FeatureResources(
|
|
361
|
+
self._feature_plan,
|
|
362
|
+
self._processing_data_inst.get_filter_data_inst(),
|
|
363
|
+
self._sample_rate_hz,
|
|
364
|
+
available_channels=self._available_channels,
|
|
365
|
+
verbose=self._verbose,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# convert memory usage in bytes
|
|
369
|
+
if isinstance(memory_limit, str):
|
|
370
|
+
memory_limit = parse_size(memory_limit)
|
|
371
|
+
|
|
372
|
+
# initialize output
|
|
373
|
+
output_df = None
|
|
374
|
+
|
|
375
|
+
# case only 1 node used for processing
|
|
376
|
+
if ncores == 1:
|
|
377
|
+
output_df = self._process(1,
|
|
378
|
+
self._id_list,
|
|
379
|
+
nevents,
|
|
380
|
+
lgc_save,
|
|
381
|
+
lgc_output,
|
|
382
|
+
dataframe_group,
|
|
383
|
+
output_group_path,
|
|
384
|
+
memory_limit)
|
|
385
|
+
|
|
386
|
+
else:
|
|
387
|
+
|
|
388
|
+
# split data
|
|
389
|
+
work_item_splits = self._split_work_items(ncores)
|
|
390
|
+
|
|
391
|
+
# for multi-core processing, we need to decrease the
|
|
392
|
+
# max memory so it fits in RAM
|
|
393
|
+
memory_limit /= ncores
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
# lauch pool processing
|
|
397
|
+
if self._verbose:
|
|
398
|
+
print(f'INFO: Processing with be split between {ncores} cores!')
|
|
399
|
+
|
|
400
|
+
node_nums = list(range(ncores+1))[1:]
|
|
401
|
+
pool = Pool(processes=ncores)
|
|
402
|
+
output_df_list = pool.starmap(self._process,
|
|
403
|
+
zip(node_nums,
|
|
404
|
+
work_item_splits,
|
|
405
|
+
repeat(nevents),
|
|
406
|
+
repeat(lgc_save),
|
|
407
|
+
repeat(lgc_output),
|
|
408
|
+
repeat(dataframe_group),
|
|
409
|
+
repeat(output_group_path),
|
|
410
|
+
repeat(memory_limit)))
|
|
411
|
+
pool.close()
|
|
412
|
+
pool.join()
|
|
413
|
+
|
|
414
|
+
# concatenate
|
|
415
|
+
output_df = pd.concat(output_df_list)
|
|
416
|
+
|
|
417
|
+
# processing done
|
|
418
|
+
if self._verbose:
|
|
419
|
+
print('INFO: Feature processing done!')
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
if lgc_output:
|
|
423
|
+
return output_df
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _process(self, node_num,
|
|
427
|
+
work_items, nevents,
|
|
428
|
+
lgc_save,
|
|
429
|
+
lgc_output,
|
|
430
|
+
dataframe_group,
|
|
431
|
+
output_group_path,
|
|
432
|
+
memory_limit):
|
|
433
|
+
"""
|
|
434
|
+
Process data
|
|
435
|
+
|
|
436
|
+
Parameters
|
|
437
|
+
---------
|
|
438
|
+
|
|
439
|
+
node_num : int
|
|
440
|
+
node id number, used for display
|
|
441
|
+
|
|
442
|
+
work_items : list of str
|
|
443
|
+
Raw stream IDs or trigger-dataframe task IDs assigned to this worker
|
|
444
|
+
|
|
445
|
+
nevents : int, optional
|
|
446
|
+
number of events to be processed
|
|
447
|
+
if not all events, requires ncores = 1
|
|
448
|
+
Default: all available (=-1)
|
|
449
|
+
|
|
450
|
+
lgc_save : bool, optional
|
|
451
|
+
if True, save dataframe in hdf5 files
|
|
452
|
+
(dataframe not returned)
|
|
453
|
+
if False, return dataframe (memory limit applies
|
|
454
|
+
so not all events may be processed)
|
|
455
|
+
Default: True
|
|
456
|
+
|
|
457
|
+
save_path : str, optional
|
|
458
|
+
base directory where output feature file will be saved
|
|
459
|
+
default: same base path as input data
|
|
460
|
+
|
|
461
|
+
ncores: int, optional
|
|
462
|
+
number of cores that will be used for processing
|
|
463
|
+
default: 1
|
|
464
|
+
|
|
465
|
+
memory_limit : float, optionl
|
|
466
|
+
memory safety limit when returning data in memory. Saved output
|
|
467
|
+
is one F#### shard per worker/task and is not split by this value.
|
|
468
|
+
|
|
469
|
+
"""
|
|
470
|
+
|
|
471
|
+
# node string (for display)
|
|
472
|
+
node_num_str = str()
|
|
473
|
+
if node_num>-1:
|
|
474
|
+
node_num_str = ' Node #' + str(node_num)
|
|
475
|
+
|
|
476
|
+
# load salting dataframe
|
|
477
|
+
self._processing_data_inst.load_salting_dataframe()
|
|
478
|
+
|
|
479
|
+
# feature extractors
|
|
480
|
+
FE = FeatureExtractors
|
|
481
|
+
FE_ext = None
|
|
482
|
+
if self._external_file is not None:
|
|
483
|
+
FE_ext = self._load_external_extractors(self._external_file)
|
|
484
|
+
|
|
485
|
+
output_file = None
|
|
486
|
+
dataframe_metadata = {}
|
|
487
|
+
if lgc_save:
|
|
488
|
+
output_file = dataframe_group.file_path(node_num)
|
|
489
|
+
dataframe_metadata = dataframe_group.row_metadata(node_num)
|
|
490
|
+
|
|
491
|
+
event_counter = 0
|
|
492
|
+
|
|
493
|
+
# initialize data frame
|
|
494
|
+
feature_df = pd.DataFrame()
|
|
495
|
+
event_rows = []
|
|
496
|
+
|
|
497
|
+
# Loop raw streams or trigger-dataframe tasks assigned to this worker.
|
|
498
|
+
for item_index, work_item in enumerate(work_items):
|
|
499
|
+
final_stream = (item_index == len(work_items) - 1)
|
|
500
|
+
|
|
501
|
+
if self._verbose:
|
|
502
|
+
print('INFO' + node_num_str
|
|
503
|
+
+ ': starting processing work item '
|
|
504
|
+
+ work_item)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
# set file list
|
|
508
|
+
self._processing_data_inst.set_stream(work_item)
|
|
509
|
+
|
|
510
|
+
# loop events
|
|
511
|
+
do_stop = False
|
|
512
|
+
start_time = time.time()
|
|
513
|
+
while (not do_stop):
|
|
514
|
+
|
|
515
|
+
# -----------------------
|
|
516
|
+
# Check number events
|
|
517
|
+
# and memory usage
|
|
518
|
+
# -----------------------
|
|
519
|
+
|
|
520
|
+
nevents_limit_reached = (nevents>0 and event_counter>=nevents)
|
|
521
|
+
|
|
522
|
+
# flag memory limit reached
|
|
523
|
+
memory_limit_reached = False
|
|
524
|
+
if event_rows:
|
|
525
|
+
memory_usage = sum(sys.getsizeof(row) for row in event_rows)
|
|
526
|
+
memory_limit_reached = (not lgc_save and memory_usage >= memory_limit)
|
|
527
|
+
|
|
528
|
+
# display
|
|
529
|
+
if self._verbose:
|
|
530
|
+
|
|
531
|
+
if (event_counter%100==0 and event_counter!=0):
|
|
532
|
+
print('INFO' + node_num_str
|
|
533
|
+
+ ': Local number of events = '
|
|
534
|
+
+ str(event_counter)
|
|
535
|
+
+ ' (memory = ' + str(memory_usage/1e6) + ' MB)')
|
|
536
|
+
|
|
537
|
+
time_running = time.time() - start_time
|
|
538
|
+
speed = event_counter/time_running
|
|
539
|
+
print('INFO' + node_num_str
|
|
540
|
+
+ ': Speed = '
|
|
541
|
+
+ f'{speed:.4f}' + ' events per second')
|
|
542
|
+
|
|
543
|
+
# -----------------------
|
|
544
|
+
# Read next event
|
|
545
|
+
# -----------------------
|
|
546
|
+
|
|
547
|
+
success = self._processing_data_inst.read_next_event(
|
|
548
|
+
channels=self._selected_channels,
|
|
549
|
+
traces_config=self._traces_config
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
# end of file
|
|
553
|
+
if not success:
|
|
554
|
+
do_stop = True
|
|
555
|
+
|
|
556
|
+
# -----------------------
|
|
557
|
+
# Handle stop or
|
|
558
|
+
# memory limit
|
|
559
|
+
# -----------------------
|
|
560
|
+
|
|
561
|
+
# now let's handle case we need to stop
|
|
562
|
+
# memory/nb events limit reached
|
|
563
|
+
|
|
564
|
+
if ((do_stop and (final_stream or not lgc_save))
|
|
565
|
+
or nevents_limit_reached
|
|
566
|
+
or memory_limit_reached):
|
|
567
|
+
|
|
568
|
+
# convert to pandas
|
|
569
|
+
feature_df = pd.DataFrame(event_rows)
|
|
570
|
+
event_rows = []
|
|
571
|
+
|
|
572
|
+
# save one final F#### shard for this processing task
|
|
573
|
+
if lgc_save:
|
|
574
|
+
feature_df = feature_df.reset_index(drop=True)
|
|
575
|
+
vx.settings.main.thread_count = 1
|
|
576
|
+
vx.settings.main.thread_count_io = 1
|
|
577
|
+
pa.set_cpu_count(1)
|
|
578
|
+
feature_vx = vx.from_pandas(feature_df, copy_index=False)
|
|
579
|
+
feature_vx.export_hdf5(output_file, mode='w')
|
|
580
|
+
feature_vx.close()
|
|
581
|
+
del feature_df, feature_vx
|
|
582
|
+
gc.collect()
|
|
583
|
+
feature_df = pd.DataFrame()
|
|
584
|
+
|
|
585
|
+
# case maximum number of events reached
|
|
586
|
+
# -> processing done!
|
|
587
|
+
if nevents_limit_reached:
|
|
588
|
+
if self._verbose:
|
|
589
|
+
print('INFO' + node_num_str
|
|
590
|
+
+ ': Requested nb events reached. '
|
|
591
|
+
+ 'Stopping processing!')
|
|
592
|
+
|
|
593
|
+
return feature_df
|
|
594
|
+
|
|
595
|
+
# case memory limit reached and not saving file
|
|
596
|
+
# -> processing done
|
|
597
|
+
# case memory limit reached
|
|
598
|
+
# -> processing needs to stop!
|
|
599
|
+
if lgc_output and memory_limit_reached:
|
|
600
|
+
raise ValueError(
|
|
601
|
+
'ERROR: memory limit reached! '
|
|
602
|
+
+ 'Change memory limit or only save hdf5 files '
|
|
603
|
+
+'(lgc_save=True AND lgc_output=False) '
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
# Now let's stop or increment event counter....
|
|
607
|
+
if do_stop:
|
|
608
|
+
break
|
|
609
|
+
else:
|
|
610
|
+
event_counter += 1
|
|
611
|
+
|
|
612
|
+
# -----------------------
|
|
613
|
+
# Features calculation
|
|
614
|
+
# -----------------------
|
|
615
|
+
|
|
616
|
+
# initialize event features dictionary
|
|
617
|
+
event_features = dict()
|
|
618
|
+
|
|
619
|
+
# Per-event trace/FFT cache. Static OF resources remain intact
|
|
620
|
+
# across events; QETpy signal state is populated only on demand.
|
|
621
|
+
event_cache = EventTraceCache(
|
|
622
|
+
self._processing_data_inst,
|
|
623
|
+
self._sample_rate_hz,
|
|
624
|
+
weights=self._weights,
|
|
625
|
+
)
|
|
626
|
+
self._feature_resources.begin_event()
|
|
627
|
+
|
|
628
|
+
# Preserve input trigger-product provenance before assigning
|
|
629
|
+
# this feature product's own dataframe_group_* identity.
|
|
630
|
+
admin_data = self._processing_data_inst.get_event_admin()
|
|
631
|
+
if 'dataframe_group_id' in admin_data:
|
|
632
|
+
for key in ('name', 'id', 'number'):
|
|
633
|
+
source = f'dataframe_group_{key}'
|
|
634
|
+
if source in admin_data:
|
|
635
|
+
event_features[f'trigger_dataframe_group_{key}'] = admin_data[source]
|
|
636
|
+
if 'processing_label' in admin_data:
|
|
637
|
+
event_features['trigger_processing_label'] = admin_data['processing_label']
|
|
638
|
+
if 'dataframe_file_index' in admin_data:
|
|
639
|
+
event_features['trigger_dataframe_file_index'] = admin_data['dataframe_file_index']
|
|
640
|
+
event_features.update(admin_data)
|
|
641
|
+
event_features.update(dataframe_metadata)
|
|
642
|
+
|
|
643
|
+
# Detector settings
|
|
644
|
+
for channel in self._processing_config.keys():
|
|
645
|
+
event_features.update(
|
|
646
|
+
self._processing_data_inst.get_channel_settings(channel)
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
# Pulse features: execute the precompiled feature plan.
|
|
651
|
+
for plan in self._feature_plan.algorithms:
|
|
652
|
+
if plan.base_algorithm in self._algorithm_list:
|
|
653
|
+
extractor = getattr(FE, plan.base_algorithm)
|
|
654
|
+
elif plan.base_algorithm in self._ext_algorithm_list:
|
|
655
|
+
extractor = getattr(FE_ext, plan.base_algorithm)
|
|
656
|
+
else:
|
|
657
|
+
raise ValueError(
|
|
658
|
+
f'ERROR: Cannot find algorithm "{plan.base_algorithm}" anywhere. '
|
|
659
|
+
'Check feature extractor exists!'
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
kwargs = self._build_algorithm_kwargs(plan)
|
|
663
|
+
|
|
664
|
+
if plan.resource_kind == 'optimal_filter':
|
|
665
|
+
resource = self._feature_resources.get_of_base(
|
|
666
|
+
plan, event_cache
|
|
667
|
+
)
|
|
668
|
+
extracted_features = extractor(
|
|
669
|
+
plan.channel, resource, **kwargs
|
|
670
|
+
)
|
|
671
|
+
elif plan.resource_kind == 'fft':
|
|
672
|
+
resource = event_cache.get_fft_data(
|
|
673
|
+
plan.channel, plan.trace_spec
|
|
674
|
+
)
|
|
675
|
+
extracted_features = extractor(
|
|
676
|
+
plan.channel, resource, **kwargs
|
|
677
|
+
)
|
|
678
|
+
else:
|
|
679
|
+
trace = event_cache.get_trace(
|
|
680
|
+
plan.channel, plan.trace_spec
|
|
681
|
+
)
|
|
682
|
+
extracted_features = extractor(trace, **kwargs)
|
|
683
|
+
|
|
684
|
+
for feature_base_name, value in extracted_features.items():
|
|
685
|
+
feature_name = f'{feature_base_name}_{plan.feature_channel}'
|
|
686
|
+
event_features[feature_name] = value
|
|
687
|
+
|
|
688
|
+
# done processing event!
|
|
689
|
+
# append event dictionary to list
|
|
690
|
+
event_rows.append(event_features)
|
|
691
|
+
|
|
692
|
+
# return features
|
|
693
|
+
print(f'INFO {node_num_str}: Processing done. Returning dataframe!')
|
|
694
|
+
return feature_df
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def _get_file_list(self, dataframe_path):
|
|
698
|
+
"""Return trigger dataframe shards as independent feature tasks.
|
|
699
|
+
|
|
700
|
+
Parameters
|
|
701
|
+
----------
|
|
702
|
+
dataframe_path : str or list of str
|
|
703
|
+
A dataframe-group directory, one HDF5 shard, or a list of HDF5
|
|
704
|
+
shards. A directory cannot be mixed with explicit files.
|
|
705
|
+
|
|
706
|
+
Returns
|
|
707
|
+
-------
|
|
708
|
+
task_dict : dict
|
|
709
|
+
Mapping of task IDs to one input dataframe shard each.
|
|
710
|
+
base_path : str
|
|
711
|
+
Parent directory containing the dataframe group.
|
|
712
|
+
group_name : str
|
|
713
|
+
Input dataframe-group directory name.
|
|
714
|
+
"""
|
|
715
|
+
paths = [dataframe_path] if isinstance(dataframe_path, str) else list(dataframe_path)
|
|
716
|
+
|
|
717
|
+
file_list = []
|
|
718
|
+
base_path = None
|
|
719
|
+
group_name = None
|
|
720
|
+
|
|
721
|
+
for item in paths:
|
|
722
|
+
path = Path(item)
|
|
723
|
+
if path.is_dir():
|
|
724
|
+
if len(paths) != 1:
|
|
725
|
+
raise ValueError(
|
|
726
|
+
"Only one dataframe-group directory may be supplied; "
|
|
727
|
+
"do not mix a directory with explicit files."
|
|
728
|
+
)
|
|
729
|
+
base_path = str(path.parent)
|
|
730
|
+
group_name = path.name
|
|
731
|
+
file_list = [str(file_path) for file_path in path.glob("*.hdf5")]
|
|
732
|
+
elif path.is_file():
|
|
733
|
+
if path.suffix != ".hdf5":
|
|
734
|
+
continue
|
|
735
|
+
if base_path is None:
|
|
736
|
+
base_path = str(path.parent.parent)
|
|
737
|
+
group_name = path.parent.name
|
|
738
|
+
file_list.append(str(path))
|
|
739
|
+
else:
|
|
740
|
+
raise ValueError(f'File or directory "{item}" does not exist!')
|
|
741
|
+
|
|
742
|
+
file_list = sorted(set(file_list))
|
|
743
|
+
if not file_list:
|
|
744
|
+
raise ValueError(
|
|
745
|
+
"ERROR: No input Vaex HDF5 dataframe files were found. "
|
|
746
|
+
"Check the trigger dataframe path."
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
task_dict = {
|
|
750
|
+
f"task_{file_index:06d}": [file_name]
|
|
751
|
+
for file_index, file_name in enumerate(file_list, start=1)
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if self._verbose:
|
|
755
|
+
print(
|
|
756
|
+
f"INFO: Found {len(file_list)} dataframe file(s); each file "
|
|
757
|
+
"is available as an independent feature task."
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
return task_dict, base_path, group_name
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def _load_external_extractors(self, external_file):
|
|
764
|
+
"""
|
|
765
|
+
Helper function for loading an alternative SingleChannelExtractors
|
|
766
|
+
class.
|
|
767
|
+
|
|
768
|
+
Parameters
|
|
769
|
+
----------
|
|
770
|
+
|
|
771
|
+
external_file : str
|
|
772
|
+
external feature file name
|
|
773
|
+
|
|
774
|
+
Return
|
|
775
|
+
------
|
|
776
|
+
|
|
777
|
+
module : object
|
|
778
|
+
FeatureExtractors module
|
|
779
|
+
|
|
780
|
+
"""
|
|
781
|
+
|
|
782
|
+
module_name = 'pytesprocess.process'
|
|
783
|
+
|
|
784
|
+
spec = importlib.util.spec_from_file_location(module_name,
|
|
785
|
+
external_file)
|
|
786
|
+
module = importlib.util.module_from_spec(spec)
|
|
787
|
+
sys.modules[module_name] = module
|
|
788
|
+
spec.loader.exec_module(module)
|
|
789
|
+
|
|
790
|
+
return module.FeatureExtractors
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def _create_output_directory(self, base_path, facility,
|
|
795
|
+
restricted=False,
|
|
796
|
+
data_type='background'):
|
|
797
|
+
"""Compatibility wrapper around the common dataframe-group helper."""
|
|
798
|
+
group = create_dataframe_group(
|
|
799
|
+
base_path, dataframe_type='feature', facility=facility,
|
|
800
|
+
processing_label=self._processing_label, restricted=restricted,
|
|
801
|
+
data_type=data_type,
|
|
802
|
+
)
|
|
803
|
+
return group.path, group.group_number
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def _build_algorithm_kwargs(self, plan):
|
|
807
|
+
"""Return runtime extractor kwargs from a compiled feature plan."""
|
|
808
|
+
kwargs = {
|
|
809
|
+
key: value for key, value in plan.params.items()
|
|
810
|
+
if key != 'run'
|
|
811
|
+
}
|
|
812
|
+
kwargs['fs'] = self._sample_rate_hz
|
|
813
|
+
|
|
814
|
+
if plan.trace_spec is None:
|
|
815
|
+
nb_samples = self._processing_data_inst.get_nb_samples()
|
|
816
|
+
nb_pretrigger_samples = (
|
|
817
|
+
self._processing_data_inst.get_nb_pretrigger_samples()
|
|
818
|
+
)
|
|
819
|
+
else:
|
|
820
|
+
nb_samples = plan.trace_spec.nb_samples
|
|
821
|
+
nb_pretrigger_samples = plan.trace_spec.nb_pretrigger_samples
|
|
822
|
+
|
|
823
|
+
kwargs['nb_samples'] = nb_samples
|
|
824
|
+
kwargs['nb_pretrigger_samples'] = nb_pretrigger_samples
|
|
825
|
+
|
|
826
|
+
window_min, window_max = self._get_window_indices(**kwargs)
|
|
827
|
+
kwargs['window_min_index'] = window_min
|
|
828
|
+
kwargs['window_max_index'] = window_max
|
|
829
|
+
kwargs['feature_base_name'] = plan.name
|
|
830
|
+
return kwargs
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
def _extract_algorithm_list(self):
|
|
834
|
+
"""
|
|
835
|
+
Extract list for algorithms, check for duplicates
|
|
836
|
+
|
|
837
|
+
Parameters
|
|
838
|
+
----------
|
|
839
|
+
None
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
Return
|
|
843
|
+
------
|
|
844
|
+
algorithm_list : list
|
|
845
|
+
list of available algorithm from _features.py FeatureExtractors class
|
|
846
|
+
|
|
847
|
+
ext_algorithm_list : list
|
|
848
|
+
list of availble algorithms from external files
|
|
849
|
+
|
|
850
|
+
"""
|
|
851
|
+
|
|
852
|
+
algorithm_list = list()
|
|
853
|
+
ext_algorithm_list = list()
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
# internal feature extractor
|
|
857
|
+
FE = FeatureExtractors
|
|
858
|
+
for attribute in dir(FE):
|
|
859
|
+
if attribute[0] == '_':
|
|
860
|
+
continue
|
|
861
|
+
algorithm_list.append(attribute)
|
|
862
|
+
|
|
863
|
+
# external feature extractor
|
|
864
|
+
if self._external_file is not None:
|
|
865
|
+
FE_ext = self._load_external_extractors(self._external_file)
|
|
866
|
+
for attribute in dir(FE_ext):
|
|
867
|
+
if attribute[0] == '_':
|
|
868
|
+
continue
|
|
869
|
+
# check for duplicate
|
|
870
|
+
if attribute in algorithm_list:
|
|
871
|
+
raise ValueError('External algorithm ' + attribute
|
|
872
|
+
+ ' is a duplicate from internal feature extractor!'
|
|
873
|
+
+ ' This is nto allowed. You need to change name...')
|
|
874
|
+
|
|
875
|
+
else:
|
|
876
|
+
ext_algorithm_list.append(attribute)
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
return algorithm_list, ext_algorithm_list
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def _split_work_items(self, ncores):
|
|
883
|
+
"""
|
|
884
|
+
Split feature work items between workers
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
Parameters
|
|
888
|
+
----------
|
|
889
|
+
|
|
890
|
+
ncores : int
|
|
891
|
+
number of cores
|
|
892
|
+
|
|
893
|
+
Return
|
|
894
|
+
------
|
|
895
|
+
|
|
896
|
+
output_list : list
|
|
897
|
+
list of dictionaries (length=ncores) containing
|
|
898
|
+
data
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
"""
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
output_list = list()
|
|
905
|
+
|
|
906
|
+
# Split independent raw-stream/dataframe tasks.
|
|
907
|
+
work_item_split = np.array_split(self._id_list, ncores)
|
|
908
|
+
|
|
909
|
+
# remove empty array
|
|
910
|
+
for work_item_sublist in work_item_split:
|
|
911
|
+
if work_item_sublist.size == 0:
|
|
912
|
+
continue
|
|
913
|
+
output_list.append(list(work_item_sublist))
|
|
914
|
+
|
|
915
|
+
return output_list
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
def _get_window_indices(self, nb_samples,
|
|
919
|
+
nb_pretrigger_samples, fs,
|
|
920
|
+
window_min_from_start_usec=None,
|
|
921
|
+
window_min_to_end_usec=None,
|
|
922
|
+
window_min_from_trig_usec=None,
|
|
923
|
+
window_max_from_start_usec=None,
|
|
924
|
+
window_max_to_end_usec=None,
|
|
925
|
+
window_max_from_trig_usec=None,
|
|
926
|
+
**kwargs):
|
|
927
|
+
"""
|
|
928
|
+
Calculate window index min and max from various types
|
|
929
|
+
of window definition
|
|
930
|
+
|
|
931
|
+
Parameters
|
|
932
|
+
---------
|
|
933
|
+
|
|
934
|
+
nb_samples : int
|
|
935
|
+
total number of samples
|
|
936
|
+
|
|
937
|
+
nb_pretrigger_samples : int
|
|
938
|
+
number of pretrigger samples
|
|
939
|
+
|
|
940
|
+
window_min_from_start_usec : float, optional
|
|
941
|
+
OF filter window start in micro seconds defined
|
|
942
|
+
from beginning of trace
|
|
943
|
+
|
|
944
|
+
window_min_to_end_usec : float, optional
|
|
945
|
+
OF filter window start in micro seconds defined
|
|
946
|
+
as length to end of trace
|
|
947
|
+
|
|
948
|
+
window_min_from_trig_usec : float, optional
|
|
949
|
+
OF filter window start in micro seconds from
|
|
950
|
+
pre-trigger (can be negative if prior pre-trigger)
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
window_max_from_start_usec : float, optional
|
|
954
|
+
OF filter window max in micro seconds defined
|
|
955
|
+
from beginning of trace
|
|
956
|
+
|
|
957
|
+
window_max_to_end_usec : float, optional
|
|
958
|
+
OF filter window max in micro seconds defined
|
|
959
|
+
as length to end of trace
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
window_max_from_trig_usec : float, optional
|
|
963
|
+
OF filter window end in micro seconds from
|
|
964
|
+
pre-trigger (can be negative if prior pre-trigger)
|
|
965
|
+
|
|
966
|
+
Return:
|
|
967
|
+
------
|
|
968
|
+
|
|
969
|
+
min_index : int
|
|
970
|
+
trace index window min
|
|
971
|
+
|
|
972
|
+
max_index : int
|
|
973
|
+
trace index window max
|
|
974
|
+
"""
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
# min window
|
|
978
|
+
min_index = 0
|
|
979
|
+
if window_min_from_start_usec is not None:
|
|
980
|
+
min_index = int(window_min_from_start_usec*fs*1e-6)
|
|
981
|
+
elif window_min_to_end_usec is not None:
|
|
982
|
+
min_index = (nb_samples
|
|
983
|
+
- abs(int(window_min_to_end_usec*fs*1e-6))
|
|
984
|
+
- 1)
|
|
985
|
+
elif window_min_from_trig_usec is not None:
|
|
986
|
+
min_index = (nb_pretrigger_samples
|
|
987
|
+
+ int(window_min_from_trig_usec*fs*1e-6))
|
|
988
|
+
|
|
989
|
+
# check
|
|
990
|
+
if min_index<0:
|
|
991
|
+
min_index=0
|
|
992
|
+
elif min_index>nb_samples-1:
|
|
993
|
+
min_index=nb_samples-1
|
|
994
|
+
|
|
995
|
+
# max index
|
|
996
|
+
max_index = nb_samples -1
|
|
997
|
+
if window_max_from_start_usec is not None:
|
|
998
|
+
max_index = int(window_max_from_start_usec*fs*1e-6)
|
|
999
|
+
elif window_max_to_end_usec is not None:
|
|
1000
|
+
max_index = (nb_samples
|
|
1001
|
+
- abs(int(window_max_to_end_usec*fs*1e-6))
|
|
1002
|
+
- 1)
|
|
1003
|
+
elif window_max_from_trig_usec is not None:
|
|
1004
|
+
max_index = (nb_pretrigger_samples
|
|
1005
|
+
+ int(window_max_from_trig_usec*fs*1e-6))
|
|
1006
|
+
|
|
1007
|
+
# check
|
|
1008
|
+
if max_index<0:
|
|
1009
|
+
max_index=0
|
|
1010
|
+
elif max_index>nb_samples-1:
|
|
1011
|
+
max_index=nb_samples-1
|
|
1012
|
+
|
|
1013
|
+
|
|
1014
|
+
if max_index<min_index:
|
|
1015
|
+
raise ValueError('ERROR window calculation: '
|
|
1016
|
+
+ 'max index smaller than min!'
|
|
1017
|
+
+ 'Check configuration!')
|
|
1018
|
+
|
|
1019
|
+
return min_index, max_index
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
|