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,967 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import vaex as vx
|
|
3
|
+
from pytesprocess.utils import utils
|
|
4
|
+
from pytesprocess.core import FilterData
|
|
5
|
+
from pytesprocess.salting import SaltInjector
|
|
6
|
+
import copy
|
|
7
|
+
import pyarrow as pa
|
|
8
|
+
from pytesdaqx.io import StreamReader
|
|
9
|
+
from pytesprocess.utils import extract_stream_id
|
|
10
|
+
from .data_source import RawDataSource, TriggerDataframeSource
|
|
11
|
+
from .event_context import EventContext
|
|
12
|
+
vx.settings.main.thread_count = 1
|
|
13
|
+
vx.settings.main.thread_count_io = 1
|
|
14
|
+
pa.set_cpu_count(1)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
'ProcessingData'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
class ProcessingData:
|
|
21
|
+
"""
|
|
22
|
+
Class to manage data used for processing,
|
|
23
|
+
in particular
|
|
24
|
+
- raw data files, event traces/windows and metadata
|
|
25
|
+
- lightweight filter-template access needed by trigger processing
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, raw_path, raw_files,
|
|
30
|
+
acquisition_name=None,
|
|
31
|
+
trigger_files=None,
|
|
32
|
+
trigger_group_name=None,
|
|
33
|
+
filter_file=None,
|
|
34
|
+
available_channels=None,
|
|
35
|
+
salting_dataframe=None,
|
|
36
|
+
verbose=True,
|
|
37
|
+
catalog=None):
|
|
38
|
+
"""
|
|
39
|
+
Intialize data processing
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
-----------
|
|
43
|
+
|
|
44
|
+
input_files : list
|
|
45
|
+
list of raw data files to be processed
|
|
46
|
+
|
|
47
|
+
acquisition_name : str, optional
|
|
48
|
+
raw data group name
|
|
49
|
+
default: None
|
|
50
|
+
|
|
51
|
+
filter_file : str, optional
|
|
52
|
+
full path to filter file
|
|
53
|
+
default: None
|
|
54
|
+
|
|
55
|
+
verbose : bool, optional
|
|
56
|
+
if True, display info
|
|
57
|
+
Default: True
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# verbose
|
|
62
|
+
self._verbose = verbose
|
|
63
|
+
|
|
64
|
+
# input raw files/path
|
|
65
|
+
self._raw_path = raw_path
|
|
66
|
+
self._raw_files = raw_files
|
|
67
|
+
self._acquisition_name = acquisition_name
|
|
68
|
+
self._catalog = catalog
|
|
69
|
+
|
|
70
|
+
# trigger dataframe
|
|
71
|
+
self._trigger_files = trigger_files
|
|
72
|
+
self._trigger_group_name = trigger_group_name
|
|
73
|
+
|
|
74
|
+
# filter data
|
|
75
|
+
self._filter_data = None
|
|
76
|
+
self._filter_file = filter_file
|
|
77
|
+
if filter_file is not None:
|
|
78
|
+
self._filter_data = FilterData()
|
|
79
|
+
self._filter_data.load_hdf5(filter_file, overwrite=True)
|
|
80
|
+
|
|
81
|
+
# trigger-dataframe mode
|
|
82
|
+
self._is_trigger_dataframe = False
|
|
83
|
+
if self._trigger_files is not None:
|
|
84
|
+
self._is_trigger_dataframe = True
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# Data sources and current event/window state. ProcessingData remains
|
|
88
|
+
# the public facade while source iteration and per-event state live in
|
|
89
|
+
# dedicated objects.
|
|
90
|
+
self._raw_source = RawDataSource(self._raw_files, catalog=self._catalog)
|
|
91
|
+
self._trigger_source = (
|
|
92
|
+
TriggerDataframeSource(self._trigger_files)
|
|
93
|
+
if self._is_trigger_dataframe else None
|
|
94
|
+
)
|
|
95
|
+
self._event = EventContext()
|
|
96
|
+
|
|
97
|
+
# get ADC and file info
|
|
98
|
+
self._data_info = self._extract_data_info()
|
|
99
|
+
|
|
100
|
+
# available channels
|
|
101
|
+
self._available_channels = available_channels
|
|
102
|
+
|
|
103
|
+
# salting
|
|
104
|
+
self._salting_dataframe = salting_dataframe
|
|
105
|
+
self._salting_inst = None
|
|
106
|
+
if salting_dataframe is not None:
|
|
107
|
+
if self._filter_data is None:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
'ERROR: A filter_file is required when salting injection is enabled.'
|
|
110
|
+
)
|
|
111
|
+
self._salting_inst = SaltInjector(
|
|
112
|
+
self._filter_data, verbose=verbose
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def verbose(self):
|
|
118
|
+
return self._verbose
|
|
119
|
+
|
|
120
|
+
def get_filter_data_inst(self):
|
|
121
|
+
return self._filter_data
|
|
122
|
+
|
|
123
|
+
def load_salting_dataframe(self):
|
|
124
|
+
"""
|
|
125
|
+
Load salting dataframe
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
if self._salting_dataframe is None:
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
# disable vaex multi-threading
|
|
132
|
+
vx.settings.main.thread_count = 1
|
|
133
|
+
vx.settings.main.thread_count_io = 1
|
|
134
|
+
pa.set_cpu_count(1)
|
|
135
|
+
|
|
136
|
+
# SaltInjector accepts either a Vaex dataframe or a path to one.
|
|
137
|
+
self._salting_inst.set_dataframe(self._salting_dataframe)
|
|
138
|
+
|
|
139
|
+
def configure_trigger_partitions(self, partition_target_duration_s=10.0,
|
|
140
|
+
overlap_left_samples=0,
|
|
141
|
+
overlap_right_samples=0):
|
|
142
|
+
"""Configure logical partitions used for continuous Zarr triggering."""
|
|
143
|
+
self._raw_source.configure_partitions(
|
|
144
|
+
partition_target_duration_s=partition_target_duration_s,
|
|
145
|
+
overlap_left_samples=overlap_left_samples,
|
|
146
|
+
overlap_right_samples=overlap_right_samples,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def get_partition_context(self):
|
|
150
|
+
"""Return current logical-partition coordinates, or ``None``."""
|
|
151
|
+
return self._raw_source.partition_context()
|
|
152
|
+
|
|
153
|
+
def set_stream(self, stream):
|
|
154
|
+
"""Select an input trigger-dataframe task or raw-data stream."""
|
|
155
|
+
vx.settings.main.thread_count_io = 1
|
|
156
|
+
self._event.reset()
|
|
157
|
+
|
|
158
|
+
if self._is_trigger_dataframe:
|
|
159
|
+
task_id = str(stream)
|
|
160
|
+
if self._verbose:
|
|
161
|
+
print('INFO: Loading dataframe trigger task ' + task_id)
|
|
162
|
+
self._trigger_source.select_task(task_id)
|
|
163
|
+
self._raw_source.close()
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
self._select_raw_stream(stream, force=True)
|
|
167
|
+
|
|
168
|
+
def _select_raw_stream(self, stream, force=False):
|
|
169
|
+
"""Select one canonical raw stream."""
|
|
170
|
+
self._raw_source.select_stream(stream, force=force)
|
|
171
|
+
self._event.stream_id = self._raw_source.stream_id
|
|
172
|
+
|
|
173
|
+
def get_raw_path(self):
|
|
174
|
+
return self._raw_path
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def read_next_event(self, channels=None, traces_config=None):
|
|
178
|
+
"""Read the next native record or dataframe-selected event."""
|
|
179
|
+
# Keep the established per-worker Vaex/PyArrow limits. These settings
|
|
180
|
+
# are intentionally unchanged in v14 because they avoid known
|
|
181
|
+
# multicore oversubscription/file-reading problems.
|
|
182
|
+
vx.settings.main.thread_count = 1
|
|
183
|
+
vx.settings.main.thread_count_io = 1
|
|
184
|
+
pa.set_cpu_count(1)
|
|
185
|
+
|
|
186
|
+
if not self._is_trigger_dataframe:
|
|
187
|
+
if self._raw_source.uses_continuous_partitions:
|
|
188
|
+
return self._read_next_continuous_partition(channels)
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
traces, info = self._raw_source.read_next_native(channels)
|
|
192
|
+
except StopIteration:
|
|
193
|
+
self._event.full_traces = None
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
self._event.reset(stream_id=self._raw_source.stream_id)
|
|
197
|
+
self._event.full_traces = traces
|
|
198
|
+
self._event.admin_info = self._normalize_reader_info(info, channels)
|
|
199
|
+
self._event.stream_number = int(
|
|
200
|
+
self._event.admin_info.get('stream_num', -1)
|
|
201
|
+
)
|
|
202
|
+
global_segment = self._event.admin_info.get('global_segment_num')
|
|
203
|
+
if global_segment is not None and int(global_segment) >= 0:
|
|
204
|
+
self._event.global_segment_number = int(global_segment)
|
|
205
|
+
|
|
206
|
+
if self._salting_inst is not None:
|
|
207
|
+
if self._event.global_segment_number is None:
|
|
208
|
+
raise NotImplementedError(
|
|
209
|
+
'Salting for native Zarr records is not implemented yet.'
|
|
210
|
+
)
|
|
211
|
+
chans = self._event.admin_info['detector_chans']
|
|
212
|
+
self._event.full_traces, self._event.salting_info = (
|
|
213
|
+
self._salting_inst.inject_record(
|
|
214
|
+
chans, self._event.full_traces,
|
|
215
|
+
stream_id=self._event.stream_id,
|
|
216
|
+
stream_number=self._event.stream_number,
|
|
217
|
+
global_segment_number=self._event.global_segment_number,
|
|
218
|
+
include_metadata=True,
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
return True
|
|
222
|
+
|
|
223
|
+
if traces_config is None:
|
|
224
|
+
raise ValueError(
|
|
225
|
+
'ERROR: No trace info available. Something went wrong...'
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
row = self._trigger_source.next_row()
|
|
229
|
+
if row is None:
|
|
230
|
+
self._event.full_traces = None
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
self._event.reset()
|
|
234
|
+
self._event.dataframe_info = row
|
|
235
|
+
|
|
236
|
+
def row_get(*names, default=None):
|
|
237
|
+
for name in names:
|
|
238
|
+
try:
|
|
239
|
+
if name in row and row[name] is not None:
|
|
240
|
+
return row[name]
|
|
241
|
+
except TypeError:
|
|
242
|
+
pass
|
|
243
|
+
return default
|
|
244
|
+
|
|
245
|
+
raw_stream = row_get('stream_id', 'stream_number')
|
|
246
|
+
if raw_stream is None:
|
|
247
|
+
raise ValueError(
|
|
248
|
+
'ERROR: Trigger dataframe does not identify the raw stream.'
|
|
249
|
+
)
|
|
250
|
+
self._select_raw_stream(raw_stream)
|
|
251
|
+
self._event.stream_id = self._raw_source.stream_id
|
|
252
|
+
|
|
253
|
+
stream_trigger_index = row_get('stream_trigger_index')
|
|
254
|
+
global_segment_number = row_get(
|
|
255
|
+
'global_segment_num', 'global_segment_number'
|
|
256
|
+
)
|
|
257
|
+
segment_trigger_index = row_get('segment_trigger_index')
|
|
258
|
+
|
|
259
|
+
if stream_trigger_index is None and global_segment_number is None:
|
|
260
|
+
raise ValueError(
|
|
261
|
+
'ERROR: Trigger dataframe needs stream_trigger_index (Zarr) or '
|
|
262
|
+
'global_segment_number (HDF5).'
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
self._event.truncated_traces = {}
|
|
266
|
+
|
|
267
|
+
# HDF5 salts are injected into the full native segment;
|
|
268
|
+
# stream-global salts are injected directly into each window.
|
|
269
|
+
if self._salting_inst is not None and stream_trigger_index is None:
|
|
270
|
+
full_record = {'global_segment_num': int(global_segment_number)}
|
|
271
|
+
traces, info = self._raw_source.read_record(
|
|
272
|
+
full_record, channels=channels
|
|
273
|
+
)
|
|
274
|
+
self._event.full_traces = traces
|
|
275
|
+
self._event.admin_info = self._normalize_reader_info(info, channels)
|
|
276
|
+
self._event.global_segment_number = int(global_segment_number)
|
|
277
|
+
self._event.stream_number = int(
|
|
278
|
+
row_get('stream_number', default=-1)
|
|
279
|
+
)
|
|
280
|
+
self._event.segment_trigger_index = int(segment_trigger_index)
|
|
281
|
+
chans = self._event.admin_info['detector_chans']
|
|
282
|
+
self._event.full_traces, self._event.salting_info = (
|
|
283
|
+
self._salting_inst.inject_record(
|
|
284
|
+
chans, self._event.full_traces,
|
|
285
|
+
stream_id=self._event.stream_id,
|
|
286
|
+
stream_number=self._event.stream_number,
|
|
287
|
+
global_segment_number=self._event.global_segment_number,
|
|
288
|
+
include_metadata=True,
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
for key_tuple in traces_config:
|
|
292
|
+
nb_samples = int(key_tuple[0])
|
|
293
|
+
nb_pretrigger_samples = int(key_tuple[1])
|
|
294
|
+
min_idx = self._event.segment_trigger_index - nb_pretrigger_samples
|
|
295
|
+
max_idx = min_idx + nb_samples
|
|
296
|
+
window = None
|
|
297
|
+
if (min_idx >= 0
|
|
298
|
+
and max_idx <= self._event.full_traces.shape[-1]):
|
|
299
|
+
window = self._event.full_traces[:, min_idx:max_idx].copy()
|
|
300
|
+
self._event.truncated_traces[key_tuple] = {
|
|
301
|
+
'traces': window, 'channels': chans
|
|
302
|
+
}
|
|
303
|
+
else:
|
|
304
|
+
for key_tuple in traces_config:
|
|
305
|
+
nb_samples = int(key_tuple[0])
|
|
306
|
+
nb_pretrigger_samples = int(key_tuple[1])
|
|
307
|
+
record = {}
|
|
308
|
+
if stream_trigger_index is not None:
|
|
309
|
+
record['stream_trigger_index'] = int(stream_trigger_index)
|
|
310
|
+
else:
|
|
311
|
+
record['global_segment_num'] = int(global_segment_number)
|
|
312
|
+
record['segment_trigger_index'] = int(segment_trigger_index)
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
traces, info = self._raw_source.read_record(
|
|
316
|
+
record,
|
|
317
|
+
channels=channels,
|
|
318
|
+
trace_length_samples=nb_samples,
|
|
319
|
+
pretrigger_length_samples=nb_pretrigger_samples,
|
|
320
|
+
)
|
|
321
|
+
except (ValueError, IndexError):
|
|
322
|
+
traces, info = None, None
|
|
323
|
+
|
|
324
|
+
if info is not None:
|
|
325
|
+
info = self._normalize_reader_info(info, channels)
|
|
326
|
+
self._event.admin_info = info
|
|
327
|
+
trace_channels = info['detector_chans']
|
|
328
|
+
else:
|
|
329
|
+
trace_channels = list(
|
|
330
|
+
channels or self._available_channels or []
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
if (self._salting_inst is not None
|
|
334
|
+
and traces is not None
|
|
335
|
+
and stream_trigger_index is not None):
|
|
336
|
+
trace_start_index = (
|
|
337
|
+
int(stream_trigger_index) - nb_pretrigger_samples
|
|
338
|
+
)
|
|
339
|
+
traces, salting_info = self._salting_inst.inject_window(
|
|
340
|
+
trace_channels, traces,
|
|
341
|
+
stream_id=self._event.stream_id,
|
|
342
|
+
trace_start_index=trace_start_index,
|
|
343
|
+
include_metadata=True,
|
|
344
|
+
)
|
|
345
|
+
if salting_info:
|
|
346
|
+
self._event.salting_info = salting_info
|
|
347
|
+
|
|
348
|
+
self._event.truncated_traces[key_tuple] = {
|
|
349
|
+
'traces': traces, 'channels': trace_channels
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if self._event.admin_info is None:
|
|
353
|
+
self._event.admin_info = {'detector_config': {}}
|
|
354
|
+
if global_segment_number is not None:
|
|
355
|
+
self._event.global_segment_number = int(global_segment_number)
|
|
356
|
+
self._event.stream_number = int(
|
|
357
|
+
row_get('stream_number', default=-1)
|
|
358
|
+
)
|
|
359
|
+
if stream_trigger_index is not None:
|
|
360
|
+
self._event.stream_trigger_index = int(stream_trigger_index)
|
|
361
|
+
elif segment_trigger_index is not None:
|
|
362
|
+
self._event.segment_trigger_index = int(segment_trigger_index)
|
|
363
|
+
return True
|
|
364
|
+
|
|
365
|
+
def _read_next_continuous_partition(self, channels=None):
|
|
366
|
+
"""Read one expanded logical partition from a continuous Zarr stream."""
|
|
367
|
+
result = self._raw_source.read_next_partition(
|
|
368
|
+
channels=channels, sample_rate_hz=self.get_sample_rate()
|
|
369
|
+
)
|
|
370
|
+
if result is None:
|
|
371
|
+
self._event.full_traces = None
|
|
372
|
+
return False
|
|
373
|
+
|
|
374
|
+
traces, info, read_start = result
|
|
375
|
+
self._event.reset(stream_id=self._raw_source.stream_id)
|
|
376
|
+
self._event.full_traces = traces
|
|
377
|
+
self._event.admin_info = self._normalize_reader_info(info, channels)
|
|
378
|
+
self._event.stream_number = int(
|
|
379
|
+
self._event.admin_info.get('stream_num', -1)
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
stream_start = self._event.admin_info.get('stream_start')
|
|
383
|
+
if stream_start is not None:
|
|
384
|
+
self._event.admin_info['event_time'] = (
|
|
385
|
+
float(stream_start) + read_start / float(self.get_sample_rate())
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
if self._salting_inst is not None:
|
|
389
|
+
trace_channels = self._event.admin_info['detector_chans']
|
|
390
|
+
self._event.full_traces, self._event.salting_info = (
|
|
391
|
+
self._salting_inst.inject_window(
|
|
392
|
+
trace_channels, self._event.full_traces,
|
|
393
|
+
stream_id=self._event.stream_id,
|
|
394
|
+
trace_start_index=read_start,
|
|
395
|
+
include_metadata=True,
|
|
396
|
+
)
|
|
397
|
+
)
|
|
398
|
+
return True
|
|
399
|
+
|
|
400
|
+
def _normalize_reader_info(self, info, channels=None):
|
|
401
|
+
"""Normalize reader metadata to canonical stream/acquisition names."""
|
|
402
|
+
out = dict(info or {})
|
|
403
|
+
detector_chans = out.get('detector_channels', out.get('detector_chans'))
|
|
404
|
+
if detector_chans is None:
|
|
405
|
+
detector_chans = list(channels or self._available_channels or [])
|
|
406
|
+
elif isinstance(detector_chans, str):
|
|
407
|
+
detector_chans = [detector_chans]
|
|
408
|
+
else:
|
|
409
|
+
detector_chans = list(detector_chans)
|
|
410
|
+
out['detector_channels'] = detector_chans
|
|
411
|
+
# Internal compatibility for existing trace/channel accessors. This is
|
|
412
|
+
# not written as a dataframe provenance field.
|
|
413
|
+
out['detector_chans'] = detector_chans
|
|
414
|
+
|
|
415
|
+
out.setdefault('stream_num', -1)
|
|
416
|
+
out.setdefault('stream_id', self._raw_source.stream_id)
|
|
417
|
+
out.setdefault('global_segment_num', -1)
|
|
418
|
+
out.setdefault('event_id', -1)
|
|
419
|
+
out.setdefault('event_time', 0)
|
|
420
|
+
|
|
421
|
+
measurement_type = out.get(
|
|
422
|
+
'measurement_type', out.get('run_type', out.get('data_type', 'unknown'))
|
|
423
|
+
)
|
|
424
|
+
out['measurement_type'] = measurement_type
|
|
425
|
+
out.pop('run_type', None)
|
|
426
|
+
out.setdefault('fridge_run', -1)
|
|
427
|
+
out['fridge_run_start'] = out.get(
|
|
428
|
+
'fridge_run_start', out.get('fridge_run_start_time', 0)
|
|
429
|
+
)
|
|
430
|
+
out.setdefault('stream_start', 0)
|
|
431
|
+
out.setdefault('acquisition_start', 0)
|
|
432
|
+
|
|
433
|
+
if 'detector_config' not in out:
|
|
434
|
+
try:
|
|
435
|
+
detector_config = copy.deepcopy(
|
|
436
|
+
self._raw_source.get_detector_settings()
|
|
437
|
+
)
|
|
438
|
+
except Exception:
|
|
439
|
+
detector_config = {}
|
|
440
|
+
for config in detector_config.values():
|
|
441
|
+
if ('tes_bias' not in config
|
|
442
|
+
and config.get('tes_bias_dc_amps') is not None):
|
|
443
|
+
config['tes_bias'] = config['tes_bias_dc_amps']
|
|
444
|
+
out['detector_config'] = detector_config
|
|
445
|
+
return out
|
|
446
|
+
|
|
447
|
+
@staticmethod
|
|
448
|
+
def _get_pytesprocess_trigger_type(info):
|
|
449
|
+
"""Return the legacy numeric pytesprocess trigger category.
|
|
450
|
+
|
|
451
|
+
Current pytesdaqx uses ``trigger_type`` for the DAQ trigger
|
|
452
|
+
configuration and stores string values such as ``"none"`` or
|
|
453
|
+
``"digital_edge"``. pytesprocess historically stores a different
|
|
454
|
+
quantity in its output dataframe:
|
|
455
|
+
|
|
456
|
+
1 = continuous/background
|
|
457
|
+
2 = external trigger
|
|
458
|
+
3 = random
|
|
459
|
+
4 = threshold/software trigger
|
|
460
|
+
|
|
461
|
+
Numeric values from legacy raw files are preserved. Otherwise the
|
|
462
|
+
value is derived from the current ``measurement_type``.
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
if not info:
|
|
466
|
+
return np.nan
|
|
467
|
+
|
|
468
|
+
raw_trigger_type = info.get('trigger_type')
|
|
469
|
+
|
|
470
|
+
# Legacy pytesdaq/pytesdaqx files may already contain the numeric
|
|
471
|
+
# pytesprocess category. Preserve it when unambiguous.
|
|
472
|
+
if isinstance(raw_trigger_type, (int, np.integer)):
|
|
473
|
+
return np.int16(raw_trigger_type)
|
|
474
|
+
if isinstance(raw_trigger_type, (float, np.floating)):
|
|
475
|
+
if np.isfinite(raw_trigger_type):
|
|
476
|
+
return np.int16(raw_trigger_type)
|
|
477
|
+
if isinstance(raw_trigger_type, str):
|
|
478
|
+
value = raw_trigger_type.strip()
|
|
479
|
+
if value.isdigit():
|
|
480
|
+
return np.int16(int(value))
|
|
481
|
+
|
|
482
|
+
measurement_type = str(
|
|
483
|
+
info.get('measurement_type', info.get('run_type', ''))
|
|
484
|
+
).strip().lower()
|
|
485
|
+
|
|
486
|
+
measurement_map = {
|
|
487
|
+
# Current canonical pytesdaqx names
|
|
488
|
+
'background': 1,
|
|
489
|
+
'external_trigger': 2,
|
|
490
|
+
'threshold': 4,
|
|
491
|
+
|
|
492
|
+
# Legacy aliases that may still occur in old metadata
|
|
493
|
+
'cont': 1,
|
|
494
|
+
'continuous': 1,
|
|
495
|
+
'open_cont': 1,
|
|
496
|
+
'restricted_cont': 1,
|
|
497
|
+
'exttrig': 2,
|
|
498
|
+
'thresh': 4,
|
|
499
|
+
}
|
|
500
|
+
if measurement_type in measurement_map:
|
|
501
|
+
return np.int16(measurement_map[measurement_type])
|
|
502
|
+
|
|
503
|
+
# Legacy files may provide data_mode even when measurement_type is
|
|
504
|
+
# absent or too generic. Do not synthesize data_mode for new data.
|
|
505
|
+
data_mode = str(info.get('data_mode', '')).strip().lower()
|
|
506
|
+
data_mode_map = {
|
|
507
|
+
'cont': 1,
|
|
508
|
+
'trig-ext': 2,
|
|
509
|
+
'rand': 3,
|
|
510
|
+
'threshold': 4,
|
|
511
|
+
}
|
|
512
|
+
if data_mode in data_mode_map:
|
|
513
|
+
return np.int16(data_mode_map[data_mode])
|
|
514
|
+
|
|
515
|
+
# A continuous ADC acquisition is safely equivalent to the legacy
|
|
516
|
+
# continuous category even for measurement types such as calibration.
|
|
517
|
+
adc_mode = str(info.get('adc_mode', '')).strip().lower()
|
|
518
|
+
if adc_mode == 'continuous':
|
|
519
|
+
return np.int16(1)
|
|
520
|
+
|
|
521
|
+
return np.nan
|
|
522
|
+
|
|
523
|
+
def get_event_admin(self, return_all=False):
|
|
524
|
+
"""Return canonical metadata for the current event/window."""
|
|
525
|
+
info = self._event.admin_info
|
|
526
|
+
if info is None:
|
|
527
|
+
return {}
|
|
528
|
+
|
|
529
|
+
if self._is_trigger_dataframe:
|
|
530
|
+
row = self._event.dataframe_info
|
|
531
|
+
if row is None:
|
|
532
|
+
return {}
|
|
533
|
+
admin = {}
|
|
534
|
+
for key, val in row.items():
|
|
535
|
+
admin[key] = np.nan if val is None else val
|
|
536
|
+
return admin
|
|
537
|
+
|
|
538
|
+
if return_all:
|
|
539
|
+
return info
|
|
540
|
+
|
|
541
|
+
admin = {}
|
|
542
|
+
global_segment = info.get('global_segment_num')
|
|
543
|
+
if global_segment is not None and int(global_segment) >= 0:
|
|
544
|
+
admin['global_segment_number'] = np.int64(global_segment)
|
|
545
|
+
|
|
546
|
+
admin['stream_number'] = np.int64(info.get('stream_num', -1))
|
|
547
|
+
if info.get('stream_id') is not None:
|
|
548
|
+
admin['stream_id'] = str(info['stream_id'])
|
|
549
|
+
if info.get('event_id') is not None:
|
|
550
|
+
admin['event_id'] = np.int32(info['event_id'])
|
|
551
|
+
if info.get('event_time') is not None:
|
|
552
|
+
admin['event_time'] = np.float64(info['event_time'])
|
|
553
|
+
admin['measurement_type'] = str(
|
|
554
|
+
info.get('measurement_type', 'unknown')
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
if self._acquisition_name is not None:
|
|
558
|
+
admin['acquisition_name'] = self._acquisition_name
|
|
559
|
+
else:
|
|
560
|
+
admin['acquisition_name'] = np.nan
|
|
561
|
+
|
|
562
|
+
admin['trigger_type'] = self._get_pytesprocess_trigger_type(info)
|
|
563
|
+
admin['trigger_amplitude'] = info.get('trigger_amplitude', np.nan)
|
|
564
|
+
admin['trigger_time'] = info.get('trigger_time', np.nan)
|
|
565
|
+
|
|
566
|
+
if info.get('fridge_run') is not None:
|
|
567
|
+
admin['fridge_run_number'] = np.int64(info['fridge_run'])
|
|
568
|
+
|
|
569
|
+
# Absolute start timestamps. EventBuilder derives per-trigger elapsed
|
|
570
|
+
# times from these when needed.
|
|
571
|
+
for source, dest in (
|
|
572
|
+
('fridge_run_start', 'fridge_run_start_time'),
|
|
573
|
+
('stream_start', 'stream_start_time'),
|
|
574
|
+
('acquisition_start', 'acquisition_start_time'),
|
|
575
|
+
):
|
|
576
|
+
value = info.get(source)
|
|
577
|
+
if value is not None:
|
|
578
|
+
admin[dest] = np.float64(value)
|
|
579
|
+
|
|
580
|
+
if self._event.salting_info is not None:
|
|
581
|
+
if 'salting_type' in self._event.salting_info:
|
|
582
|
+
admin['salting_type'] = self._event.salting_info['salting_type']
|
|
583
|
+
for key in (
|
|
584
|
+
'salting_dataframe_group_name',
|
|
585
|
+
'salting_dataframe_group_id',
|
|
586
|
+
'salting_dataframe_group_number',
|
|
587
|
+
):
|
|
588
|
+
if key in self._event.salting_info:
|
|
589
|
+
admin[key] = self._event.salting_info[key]
|
|
590
|
+
|
|
591
|
+
return admin
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def get_channel_settings(self, channel):
|
|
595
|
+
"""
|
|
596
|
+
Get channel settings dictionary
|
|
597
|
+
|
|
598
|
+
Parameters
|
|
599
|
+
---------
|
|
600
|
+
|
|
601
|
+
channel : str
|
|
602
|
+
channel can be a single channel
|
|
603
|
+
or sum of channels "chan1+chan2"
|
|
604
|
+
or multiple channels "chan1|chan2"
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
Return
|
|
608
|
+
------
|
|
609
|
+
settings_dict : dict
|
|
610
|
+
dictionnary with various detector settings variable
|
|
611
|
+
|
|
612
|
+
"""
|
|
613
|
+
|
|
614
|
+
# initialize output
|
|
615
|
+
settings_dict = dict()
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
# check info filled
|
|
619
|
+
if (self._event.admin_info is None or
|
|
620
|
+
'detector_config' not in self._event.admin_info):
|
|
621
|
+
return settings_dict
|
|
622
|
+
|
|
623
|
+
# get channels list
|
|
624
|
+
available_channels = self._available_channels
|
|
625
|
+
if available_channels is None:
|
|
626
|
+
available_channels = self._event.admin_info['detector_chans']
|
|
627
|
+
|
|
628
|
+
channels, separator = utils.split_channel_name(
|
|
629
|
+
channel,
|
|
630
|
+
available_channels=available_channels
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
# fill dictionary
|
|
634
|
+
for chan in channels:
|
|
635
|
+
settings_dict['tes_bias_' + chan] = (
|
|
636
|
+
self._event.admin_info['detector_config'][chan]['tes_bias'])
|
|
637
|
+
settings_dict['output_gain_' + chan] = (
|
|
638
|
+
self._event.admin_info['detector_config'][chan]['output_gain'])
|
|
639
|
+
|
|
640
|
+
return settings_dict
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def get_channel_trace(self, channel,
|
|
645
|
+
nb_samples=None,
|
|
646
|
+
nb_pretrigger_samples=None,
|
|
647
|
+
weights=None):
|
|
648
|
+
"""
|
|
649
|
+
Get trace (s)
|
|
650
|
+
|
|
651
|
+
Parameters
|
|
652
|
+
----------
|
|
653
|
+
|
|
654
|
+
channel : str
|
|
655
|
+
channel can be a single channel
|
|
656
|
+
or sum of channels "chan1+chan2"
|
|
657
|
+
or multiple channels "chan1|chan2"
|
|
658
|
+
|
|
659
|
+
Return:
|
|
660
|
+
-------
|
|
661
|
+
array : ndarray
|
|
662
|
+
array with trace values
|
|
663
|
+
|
|
664
|
+
"""
|
|
665
|
+
|
|
666
|
+
array = None
|
|
667
|
+
|
|
668
|
+
# get channels for case + or | used
|
|
669
|
+
available_channels = self._available_channels
|
|
670
|
+
if available_channels is None:
|
|
671
|
+
available_channels = self._event.admin_info['detector_chans']
|
|
672
|
+
|
|
673
|
+
channels, separator = utils.split_channel_name(
|
|
674
|
+
channel,
|
|
675
|
+
available_channels=available_channels
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
weights_array = None
|
|
679
|
+
if weights is not None:
|
|
680
|
+
|
|
681
|
+
weights_array = np.ones(len(channels))
|
|
682
|
+
|
|
683
|
+
for ichan, chan in enumerate(channels):
|
|
684
|
+
param = f'weight_{chan}'
|
|
685
|
+
if param not in weights:
|
|
686
|
+
raise ValueError(
|
|
687
|
+
f'ERROR: Missing parameter weight {param} '
|
|
688
|
+
f'for channel {channel}!'
|
|
689
|
+
)
|
|
690
|
+
val = weights[param]
|
|
691
|
+
weights_array[ichan] = val
|
|
692
|
+
|
|
693
|
+
# case full trace
|
|
694
|
+
if nb_samples is None:
|
|
695
|
+
|
|
696
|
+
# get array indices
|
|
697
|
+
channel_indices = list()
|
|
698
|
+
for chan in channels:
|
|
699
|
+
channel_indices.append(
|
|
700
|
+
self._event.admin_info['detector_chans'].index(chan)
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
if not channel_indices:
|
|
704
|
+
raise ValueError('Unable to get event traces for '
|
|
705
|
+
+ channel)
|
|
706
|
+
# get array
|
|
707
|
+
array = self._event.full_traces[channel_indices,:]
|
|
708
|
+
|
|
709
|
+
else:
|
|
710
|
+
|
|
711
|
+
if nb_pretrigger_samples is None:
|
|
712
|
+
raise ValueError(
|
|
713
|
+
'ERROR: "nb_pretrigger_samples" required!')
|
|
714
|
+
|
|
715
|
+
key = (nb_samples, nb_pretrigger_samples)
|
|
716
|
+
|
|
717
|
+
if (self._event.truncated_traces is None
|
|
718
|
+
or key not in self._event.truncated_traces.keys()):
|
|
719
|
+
raise ValueError('ERROR: Traces not available!')
|
|
720
|
+
|
|
721
|
+
channel_indices = list()
|
|
722
|
+
for chan in channels:
|
|
723
|
+
channel_indices.append(
|
|
724
|
+
self._event.truncated_traces[key]['channels'].index(chan)
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
# get array
|
|
728
|
+
if (self._event.truncated_traces[key]['traces'] is not None):
|
|
729
|
+
array = self._event.truncated_traces[key]['traces'][channel_indices,:]
|
|
730
|
+
|
|
731
|
+
# Build output
|
|
732
|
+
if array is None:
|
|
733
|
+
return array
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
if separator == '+':
|
|
737
|
+
if weights is not None:
|
|
738
|
+
weights_array = weights_array[:, np.newaxis]
|
|
739
|
+
array = array * weights_array
|
|
740
|
+
array = np.sum(array,axis=0)
|
|
741
|
+
|
|
742
|
+
elif separator == '-':
|
|
743
|
+
if weights is not None:
|
|
744
|
+
array = (array[0,:]*weights_array[0]
|
|
745
|
+
- array[1,:]*weights_array[1])
|
|
746
|
+
else:
|
|
747
|
+
array = array[0,:] - array[1,:]
|
|
748
|
+
|
|
749
|
+
elif separator is None:
|
|
750
|
+
array = array[0,:]
|
|
751
|
+
|
|
752
|
+
return array
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def get_template(self, channel, tag='default'):
|
|
757
|
+
"""Return a template and metadata from the loaded FilterData.
|
|
758
|
+
|
|
759
|
+
TriggerProcessing still uses this lightweight accessor. Feature OF
|
|
760
|
+
resource management is handled separately by FeatureResources.
|
|
761
|
+
"""
|
|
762
|
+
if self._filter_data is None:
|
|
763
|
+
raise ValueError('ERROR: No filter_file is loaded.')
|
|
764
|
+
template, _time, metadata = self._filter_data.get_template(
|
|
765
|
+
channel, tag=tag, return_metadata=True
|
|
766
|
+
)
|
|
767
|
+
return template, metadata
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def get_noise(self, channel, tag='default'):
|
|
771
|
+
"""Return PSD/CSD data from the loaded FilterData.
|
|
772
|
+
|
|
773
|
+
Kept as a lightweight compatibility accessor; feature OF resource
|
|
774
|
+
preparation no longer lives in ProcessingData.
|
|
775
|
+
"""
|
|
776
|
+
if self._filter_data is None:
|
|
777
|
+
raise ValueError('ERROR: No filter_file is loaded.')
|
|
778
|
+
channel_list = str(channel).replace(' ', '').split('|')
|
|
779
|
+
if len(channel_list) == 1:
|
|
780
|
+
return self._filter_data.get_psd(
|
|
781
|
+
channel, tag=tag, fold=False, return_metadata=True
|
|
782
|
+
)
|
|
783
|
+
return self._filter_data.get_csd(
|
|
784
|
+
channel, tag=tag, fold=False, return_metadata=True
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
def get_psd(self, channel, tag='default'):
|
|
789
|
+
"""Return an unfolded PSD and metadata from the loaded FilterData."""
|
|
790
|
+
if self._filter_data is None:
|
|
791
|
+
raise ValueError('ERROR: No filter_file is loaded.')
|
|
792
|
+
return self._filter_data.get_psd(
|
|
793
|
+
channel, tag=tag, fold=False, return_metadata=True
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def get_facility(self):
|
|
798
|
+
"""
|
|
799
|
+
Function to extract facility # from
|
|
800
|
+
metadata
|
|
801
|
+
|
|
802
|
+
Parameters
|
|
803
|
+
----------
|
|
804
|
+
None
|
|
805
|
+
|
|
806
|
+
Return
|
|
807
|
+
------
|
|
808
|
+
|
|
809
|
+
facility : int
|
|
810
|
+
facility number
|
|
811
|
+
|
|
812
|
+
"""
|
|
813
|
+
facility = None
|
|
814
|
+
if 'facility' in self._data_info.keys():
|
|
815
|
+
facility = self._data_info['facility']
|
|
816
|
+
|
|
817
|
+
return facility
|
|
818
|
+
|
|
819
|
+
def get_sample_rate(self):
|
|
820
|
+
"""
|
|
821
|
+
Function to extract sample rate from
|
|
822
|
+
metadata
|
|
823
|
+
|
|
824
|
+
Parameters
|
|
825
|
+
----------
|
|
826
|
+
None
|
|
827
|
+
|
|
828
|
+
Return
|
|
829
|
+
------
|
|
830
|
+
|
|
831
|
+
sample_rate : float
|
|
832
|
+
ADC sample rate used to take data
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
"""
|
|
836
|
+
sample_rate = None
|
|
837
|
+
if 'sample_rate_hz' in self._data_info:
|
|
838
|
+
sample_rate = self._data_info['sample_rate_hz']
|
|
839
|
+
elif 'sample_rate' in self._data_info:
|
|
840
|
+
sample_rate = self._data_info['sample_rate']
|
|
841
|
+
|
|
842
|
+
return sample_rate
|
|
843
|
+
|
|
844
|
+
def get_nb_samples(self):
|
|
845
|
+
"""
|
|
846
|
+
Function to extract number of samples information
|
|
847
|
+
from metadata
|
|
848
|
+
|
|
849
|
+
Parameters
|
|
850
|
+
----------
|
|
851
|
+
None
|
|
852
|
+
|
|
853
|
+
Return
|
|
854
|
+
------
|
|
855
|
+
|
|
856
|
+
nb_samples : int
|
|
857
|
+
number of samples of the traces
|
|
858
|
+
|
|
859
|
+
"""
|
|
860
|
+
nb_samples = None
|
|
861
|
+
if 'nb_samples' in self._data_info.keys():
|
|
862
|
+
nb_samples = self._data_info['nb_samples']
|
|
863
|
+
|
|
864
|
+
return nb_samples
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
def get_nb_pretrigger_samples(self):
|
|
868
|
+
"""
|
|
869
|
+
Function to extract number of pretrigger samples
|
|
870
|
+
information from metadata
|
|
871
|
+
|
|
872
|
+
Parameters
|
|
873
|
+
----------
|
|
874
|
+
None
|
|
875
|
+
|
|
876
|
+
Return
|
|
877
|
+
------
|
|
878
|
+
|
|
879
|
+
nb_samples : int
|
|
880
|
+
number of pretrigger samples
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
"""
|
|
885
|
+
nb_pretrigger_samples = None
|
|
886
|
+
if 'nb_pretrigger_samples' in self._data_info.keys():
|
|
887
|
+
nb_pretrigger_samples = self._data_info['nb_pretrigger_samples']
|
|
888
|
+
|
|
889
|
+
return nb_pretrigger_samples
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
def _extract_data_info(self, stream=None):
|
|
894
|
+
"""
|
|
895
|
+
Function to extract all metadata
|
|
896
|
+
from raw data
|
|
897
|
+
|
|
898
|
+
Parameters
|
|
899
|
+
----------
|
|
900
|
+
None
|
|
901
|
+
|
|
902
|
+
Return
|
|
903
|
+
------
|
|
904
|
+
|
|
905
|
+
data_info : dict
|
|
906
|
+
dictionary with ADC/data information
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
"""
|
|
910
|
+
|
|
911
|
+
# convert to stream id
|
|
912
|
+
stream_id = None
|
|
913
|
+
if stream is not None:
|
|
914
|
+
stream_id = extract_stream_id(stream)
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
data_info = None
|
|
918
|
+
|
|
919
|
+
if not self._raw_files:
|
|
920
|
+
raise ValueError('No file available to get sample rate!')
|
|
921
|
+
|
|
922
|
+
# StreamReader is the storage-independent metadata boundary for current
|
|
923
|
+
# pytesdaqx. Prefer the acquisition catalog when available; retain a
|
|
924
|
+
# resource-level fallback for older/direct ProcessingData callers.
|
|
925
|
+
reader_kwargs = {}
|
|
926
|
+
if stream_id is not None:
|
|
927
|
+
reader_kwargs['streams'] = stream_id
|
|
928
|
+
|
|
929
|
+
if self._catalog is not None:
|
|
930
|
+
reader_source = self._catalog.acquisition_path
|
|
931
|
+
reader_kwargs['catalog'] = self._catalog
|
|
932
|
+
elif stream_id is None:
|
|
933
|
+
reader_source = next(iter(self._raw_files.values()))[0]
|
|
934
|
+
else:
|
|
935
|
+
reader_source = self._raw_files[stream_id][0]
|
|
936
|
+
|
|
937
|
+
with StreamReader(reader_source, **reader_kwargs) as reader:
|
|
938
|
+
metadata = reader.get_metadata()
|
|
939
|
+
|
|
940
|
+
data_info = copy.deepcopy(metadata.get('groups', {}).get('adc', {}))
|
|
941
|
+
data_info['comment'] = metadata.get('comment')
|
|
942
|
+
data_info['facility'] = metadata.get('facility')
|
|
943
|
+
data_info['run_purpose'] = metadata.get('run_purpose', 'unknown')
|
|
944
|
+
|
|
945
|
+
# AcquisitionCatalog is the canonical source for storage/sample-rate
|
|
946
|
+
# information in current pytesdaqx. Keep the legacy names used by this
|
|
947
|
+
# class as aliases for now.
|
|
948
|
+
if self._catalog is not None:
|
|
949
|
+
view = self._catalog if stream_id is None else self._catalog.filter(streams=stream_id)
|
|
950
|
+
if view.sample_rate_hz is not None:
|
|
951
|
+
data_info['sample_rate_hz'] = float(view.sample_rate_hz)
|
|
952
|
+
data_info['sample_rate'] = float(view.sample_rate_hz)
|
|
953
|
+
if view.storage_format is not None:
|
|
954
|
+
data_info['storage_format'] = view.storage_format
|
|
955
|
+
if view.entries:
|
|
956
|
+
entry = view.entries[0]
|
|
957
|
+
if entry.get('facility') is not None:
|
|
958
|
+
data_info['facility'] = entry['facility']
|
|
959
|
+
native_length = entry.get('trace_length_samples')
|
|
960
|
+
if native_length is None:
|
|
961
|
+
native_length = entry.get('segment_length_samples')
|
|
962
|
+
if native_length is not None:
|
|
963
|
+
data_info.setdefault('nb_samples', int(native_length))
|
|
964
|
+
|
|
965
|
+
return data_info
|
|
966
|
+
|
|
967
|
+
|