legend-daq2lh5 1.0.2__py3-none-any.whl → 1.2.0a1__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.
- daq2lh5/_version.py +11 -3
- daq2lh5/buffer_processor/buffer_processor.py +23 -5
- daq2lh5/buffer_processor/lh5_buffer_processor.py +10 -14
- daq2lh5/build_raw.py +11 -9
- daq2lh5/data_decoder.py +31 -33
- daq2lh5/fc/fc_config_decoder.py +1 -1
- daq2lh5/orca/orca_digitizers.py +2 -2
- daq2lh5/orca/orca_flashcam.py +1 -0
- daq2lh5/orca/orca_packet.py +14 -7
- daq2lh5/orca/orca_streamer.py +126 -14
- daq2lh5/raw_buffer.py +8 -10
- {legend_daq2lh5-1.0.2.dist-info → legend_daq2lh5-1.2.0a1.dist-info}/METADATA +17 -4
- {legend_daq2lh5-1.0.2.dist-info → legend_daq2lh5-1.2.0a1.dist-info}/RECORD +17 -17
- {legend_daq2lh5-1.0.2.dist-info → legend_daq2lh5-1.2.0a1.dist-info}/WHEEL +1 -1
- {legend_daq2lh5-1.0.2.dist-info → legend_daq2lh5-1.2.0a1.dist-info}/LICENSE +0 -0
- {legend_daq2lh5-1.0.2.dist-info → legend_daq2lh5-1.2.0a1.dist-info}/entry_points.txt +0 -0
- {legend_daq2lh5-1.0.2.dist-info → legend_daq2lh5-1.2.0a1.dist-info}/top_level.txt +0 -0
daq2lh5/_version.py
CHANGED
@@ -2,7 +2,15 @@
|
|
2
2
|
# don't change, don't track in version control
|
3
3
|
TYPE_CHECKING = False
|
4
4
|
if TYPE_CHECKING:
|
5
|
-
from typing import Tuple
|
5
|
+
from typing import Tuple, Union
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
7
|
+
else:
|
8
|
+
VERSION_TUPLE = object
|
6
9
|
|
7
|
-
|
8
|
-
|
10
|
+
version: str
|
11
|
+
__version__: str
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
13
|
+
version_tuple: VERSION_TUPLE
|
14
|
+
|
15
|
+
__version__ = version = '1.2.0a1'
|
16
|
+
__version_tuple__ = version_tuple = (1, 2, 0)
|
@@ -48,10 +48,17 @@ def buffer_processor(rb: RawBuffer) -> Table:
|
|
48
48
|
``"dtype_conv": {"lgdo": "dtype" [, ...]}`` `(dict)`
|
49
49
|
Casts `lgdo` to the requested data type.
|
50
50
|
|
51
|
-
``"compression": {
|
51
|
+
``"compression": {"lgdo": "codec_name" [, ...]}`` `(dict)`
|
52
52
|
Updates the `compression` attribute of `lgdo` to `codec_name`. The
|
53
53
|
attribute sets the compression algorithm applied by
|
54
|
-
:func
|
54
|
+
:func:`~lgdo.lh5.store.LH5Store.read` before writing `lgdo` to
|
55
|
+
disk. Can be used to apply custom waveform compression algorithms from
|
56
|
+
:mod:`lgdo.compression`.
|
57
|
+
|
58
|
+
``"hdf5_settings": {"lgdo": { <HDF5 settings> }}`` `(dict)`
|
59
|
+
Updates the `hdf5_settings` attribute of `lgdo`. The attribute sets the
|
60
|
+
HDF5 dataset options applied by
|
61
|
+
:func:`~lgdo.lh5.store.LH5Store.read` before writing `lgdo` to
|
55
62
|
disk.
|
56
63
|
|
57
64
|
Parameters
|
@@ -102,7 +109,9 @@ def buffer_processor(rb: RawBuffer) -> Table:
|
|
102
109
|
,}
|
103
110
|
"compression": {
|
104
111
|
"windowed_waveform/values": RadwareSigcompress(codec_shift=-32768),
|
105
|
-
|
112
|
+
}
|
113
|
+
"hdf5_settings": {
|
114
|
+
"presummed_waveform/values": {"shuffle": True, "compression": "lzf"},
|
106
115
|
}
|
107
116
|
}
|
108
117
|
},
|
@@ -143,7 +152,7 @@ def buffer_processor(rb: RawBuffer) -> Table:
|
|
143
152
|
if "drop" in rb.proc_spec.keys():
|
144
153
|
process_drop(rb, tmp_table)
|
145
154
|
|
146
|
-
#
|
155
|
+
# assign compression attributes
|
147
156
|
if "compression" in rb.proc_spec.keys():
|
148
157
|
for name, codec in rb.proc_spec["compression"].items():
|
149
158
|
ptr = tmp_table
|
@@ -154,6 +163,15 @@ def buffer_processor(rb: RawBuffer) -> Table:
|
|
154
163
|
codec if isinstance(codec, WaveformCodec) else str2wfcodec(codec)
|
155
164
|
)
|
156
165
|
|
166
|
+
# and HDF5 settings
|
167
|
+
if "hdf5_settings" in rb.proc_spec.keys():
|
168
|
+
for name, settings in rb.proc_spec["hdf5_settings"].items():
|
169
|
+
ptr = tmp_table
|
170
|
+
for word in name.split("/"):
|
171
|
+
ptr = ptr[word]
|
172
|
+
|
173
|
+
ptr.attrs["hdf5_settings"] = settings
|
174
|
+
|
157
175
|
return tmp_table
|
158
176
|
|
159
177
|
|
@@ -277,7 +295,7 @@ def process_windowed_t0(t0s: Array, dts: Array, start_index: int) -> Array:
|
|
277
295
|
|
278
296
|
|
279
297
|
def process_dsp(rb: RawBuffer, tmp_table: Table) -> None:
|
280
|
-
r"""Run a DSP processing chain
|
298
|
+
r"""Run a DSP processing chain with :mod:`dspeed`.
|
281
299
|
|
282
300
|
Run a provided DSP config from `rb.proc_spec` using
|
283
301
|
:func:`.dsp.build_processing_chain`, and add specified outputs to the
|
@@ -6,7 +6,7 @@ import os
|
|
6
6
|
|
7
7
|
import h5py
|
8
8
|
import lgdo
|
9
|
-
from lgdo import
|
9
|
+
from lgdo import lh5
|
10
10
|
|
11
11
|
from ..buffer_processor.buffer_processor import buffer_processor
|
12
12
|
from ..raw_buffer import RawBuffer, RawBufferLibrary
|
@@ -54,14 +54,14 @@ def lh5_buffer_processor(
|
|
54
54
|
"""
|
55
55
|
|
56
56
|
# Initialize the input raw file
|
57
|
-
raw_store = LH5Store()
|
57
|
+
raw_store = lh5.LH5Store()
|
58
58
|
lh5_file = raw_store.gimme_file(lh5_raw_file_in, "r")
|
59
59
|
if lh5_file is None:
|
60
60
|
raise ValueError(f"input file not found: {lh5_raw_file_in}")
|
61
61
|
return
|
62
62
|
|
63
63
|
# List the groups in the raw file
|
64
|
-
lh5_groups =
|
64
|
+
lh5_groups = lh5.ls(lh5_raw_file_in)
|
65
65
|
lh5_tables = []
|
66
66
|
|
67
67
|
# check if group points to raw data; sometimes 'raw' is nested, e.g g024/raw
|
@@ -69,21 +69,19 @@ def lh5_buffer_processor(
|
|
69
69
|
# Make sure that the upper level key isn't a dataset
|
70
70
|
if isinstance(lh5_file[tb], h5py.Dataset):
|
71
71
|
lh5_tables.append(f"{tb}")
|
72
|
-
elif "raw" not in tb and
|
72
|
+
elif "raw" not in tb and lh5.ls(lh5_file, f"{tb}/raw"):
|
73
73
|
lh5_tables.append(f"{tb}/raw")
|
74
74
|
# Look one layer deeper for a :meth:`lgdo.Table` if necessary
|
75
|
-
elif
|
75
|
+
elif lh5.ls(lh5_file, f"{tb}"):
|
76
76
|
# Check to make sure that this isn't a table itself
|
77
|
-
maybe_table, _ = raw_store.
|
77
|
+
maybe_table, _ = raw_store.read(f"{tb}", lh5_file)
|
78
78
|
if isinstance(maybe_table, lgdo.Table):
|
79
79
|
lh5_tables.append(f"{tb}")
|
80
80
|
del maybe_table
|
81
81
|
# otherwise, go deeper
|
82
82
|
else:
|
83
|
-
for sub_table in
|
84
|
-
maybe_table, _ = raw_store.
|
85
|
-
f"{tb}/{sub_table}", lh5_file
|
86
|
-
)
|
83
|
+
for sub_table in lh5.ls(lh5_file, f"{tb}"):
|
84
|
+
maybe_table, _ = raw_store.read(f"{tb}/{sub_table}", lh5_file)
|
87
85
|
if isinstance(maybe_table, lgdo.Table):
|
88
86
|
lh5_tables.append(f"{tb}/{sub_table}")
|
89
87
|
del maybe_table
|
@@ -114,7 +112,7 @@ def lh5_buffer_processor(
|
|
114
112
|
|
115
113
|
# Write everything in the raw file to the new file, check for proc_spec under either the group name, out_name, or the name
|
116
114
|
for tb in lh5_tables:
|
117
|
-
lgdo_obj, _ = raw_store.
|
115
|
+
lgdo_obj, _ = raw_store.read(f"{tb}", lh5_file)
|
118
116
|
|
119
117
|
# Find the out_name.
|
120
118
|
# If the top level group has an lgdo table in it, then the out_name is group
|
@@ -198,6 +196,4 @@ def lh5_buffer_processor(
|
|
198
196
|
pass
|
199
197
|
|
200
198
|
# Write the (possibly processed) lgdo_obj to a file
|
201
|
-
raw_store.
|
202
|
-
lgdo_obj, out_name, lh5_file=proc_file_name, group=group_name
|
203
|
-
)
|
199
|
+
raw_store.write(lgdo_obj, out_name, lh5_file=proc_file_name, group=group_name)
|
daq2lh5/build_raw.py
CHANGED
@@ -6,10 +6,8 @@ import logging
|
|
6
6
|
import os
|
7
7
|
import time
|
8
8
|
|
9
|
-
import hdf5plugin
|
10
|
-
import lgdo
|
11
9
|
import numpy as np
|
12
|
-
from lgdo
|
10
|
+
from lgdo import lh5
|
13
11
|
from tqdm.auto import tqdm
|
14
12
|
|
15
13
|
from .compass.compass_streamer import CompassStreamer
|
@@ -28,7 +26,7 @@ def build_raw(
|
|
28
26
|
n_max: int = np.inf,
|
29
27
|
overwrite: bool = False,
|
30
28
|
compass_config_file: str = None,
|
31
|
-
|
29
|
+
hdf5_settings: dict[str, ...] = None,
|
32
30
|
**kwargs,
|
33
31
|
) -> None:
|
34
32
|
"""Convert data into LEGEND HDF5 raw-tier format.
|
@@ -77,12 +75,16 @@ def build_raw(
|
|
77
75
|
json-shorthand for the output specification (see
|
78
76
|
:mod:`.compass.compass_event_decoder`).
|
79
77
|
|
80
|
-
|
81
|
-
forwarded to
|
78
|
+
hdf5_settings
|
79
|
+
keyword arguments (as a dict) forwarded to
|
80
|
+
:meth:`lgdo.lh5.store.LH5Store.write`.
|
82
81
|
|
83
82
|
**kwargs
|
84
83
|
sent to :class:`.RawBufferLibrary` generation as `kw_dict` argument.
|
85
84
|
"""
|
85
|
+
if hdf5_settings is None:
|
86
|
+
hdf5_settings = {}
|
87
|
+
|
86
88
|
# convert any environment variables in in_stream so that we can check for readability
|
87
89
|
in_stream = os.path.expandvars(in_stream)
|
88
90
|
# later: fix if in_stream is not a file
|
@@ -222,8 +224,8 @@ def build_raw(
|
|
222
224
|
os.remove(out_file_glob[0])
|
223
225
|
|
224
226
|
# Write header data
|
225
|
-
lh5_store =
|
226
|
-
write_to_lh5_and_clear(header_data, lh5_store,
|
227
|
+
lh5_store = lh5.LH5Store(keep_open=True)
|
228
|
+
write_to_lh5_and_clear(header_data, lh5_store, **hdf5_settings)
|
227
229
|
|
228
230
|
# Now loop through the data
|
229
231
|
n_bytes_last = streamer.n_bytes_read
|
@@ -248,7 +250,7 @@ def build_raw(
|
|
248
250
|
if log.getEffectiveLevel() <= logging.INFO and n_max < np.inf:
|
249
251
|
progress_bar.update(n_read)
|
250
252
|
|
251
|
-
write_to_lh5_and_clear(chunk_list, lh5_store,
|
253
|
+
write_to_lh5_and_clear(chunk_list, lh5_store, **hdf5_settings)
|
252
254
|
|
253
255
|
if n_max <= 0:
|
254
256
|
log.info(f"Wrote {n_max} rows, exiting...")
|
daq2lh5/data_decoder.py
CHANGED
@@ -3,13 +3,10 @@ Base classes for decoding data into raw LGDO Tables or files
|
|
3
3
|
"""
|
4
4
|
from __future__ import annotations
|
5
5
|
|
6
|
-
from typing import Union
|
7
|
-
|
8
6
|
import lgdo
|
9
7
|
import numpy as np
|
10
|
-
from lgdo import
|
11
|
-
|
12
|
-
LGDO = Union[lgdo.Scalar, lgdo.Struct, lgdo.Array, lgdo.VectorOfVectors]
|
8
|
+
from lgdo import LGDO
|
9
|
+
from lgdo.lh5 import LH5Store
|
13
10
|
|
14
11
|
|
15
12
|
class DataDecoder:
|
@@ -18,37 +15,39 @@ class DataDecoder:
|
|
18
15
|
Most decoders will repeatedly decode the same set of values from each
|
19
16
|
packet. The values that get decoded need to be described by a dict stored
|
20
17
|
in `self.decoded_values` that helps determine how to set up the buffers and
|
21
|
-
write them to file as :class
|
22
|
-
are made whose columns correspond to
|
23
|
-
packet data gets pushed to the end of
|
18
|
+
write them to file as :class:`~lgdo.types.lgdo.LGDO`\ s.
|
19
|
+
:class:`~lgdo.types.table.Table`\ s are made whose columns correspond to
|
20
|
+
the elements of `decoded_values`, and packet data gets pushed to the end of
|
21
|
+
the table one row at a time.
|
24
22
|
|
25
23
|
Any key-value entry in a configuration dictionary attached to an element
|
26
24
|
of `decoded_values` is typically interpreted as an attribute to be attached
|
27
25
|
to the corresponding LGDO. This feature can be for example exploited to
|
28
|
-
specify
|
29
|
-
:meth
|
26
|
+
specify HDF5 dataset settings used by
|
27
|
+
:meth:`~lgdo.lh5.store.LH5Store.write` to write LGDOs to disk.
|
30
28
|
|
31
29
|
For example ::
|
32
30
|
|
33
31
|
from lgdo.compression import RadwareSigcompress
|
34
32
|
|
35
33
|
FCEventDecoder.decoded_values = {
|
36
|
-
"packet_id": {"dtype": "uint32", "compression": "gzip"},
|
34
|
+
"packet_id": {"dtype": "uint32", "hdf5_settings": {"compression": "gzip"}},
|
37
35
|
# ...
|
38
36
|
"waveform": {
|
39
37
|
"dtype": "uint16",
|
40
38
|
"datatype": "waveform",
|
41
39
|
# ...
|
42
40
|
"compression": {"values": RadwareSigcompress(codec_shift=-32768)},
|
41
|
+
"hdf5_settings": {"t0": {"compression": "lzf", shuffle: True}},
|
43
42
|
}
|
44
43
|
}
|
45
44
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
written to disk,
|
50
|
-
and with the
|
51
|
-
waveform compressor.
|
45
|
+
The LGDO corresponding to ``packet_id`` will have its `hdf5_settings`
|
46
|
+
attribute set as ``{"compression": "gzip"}``, while ``waveform.values``
|
47
|
+
will have its `compression` attribute set to
|
48
|
+
``RadwareSigcompress(codec_shift=-32768)``. Before being written to disk,
|
49
|
+
they will be compressed with the HDF5 built-in Gzip filter and with the
|
50
|
+
:class:`~lgdo.compression.radware.RadwareSigcompress` waveform compressor.
|
52
51
|
|
53
52
|
Examples
|
54
53
|
--------
|
@@ -118,7 +117,7 @@ class DataDecoder:
|
|
118
117
|
"""Make an LGDO for this :class:`DataDecoder` to fill.
|
119
118
|
|
120
119
|
This default version of this function allocates a
|
121
|
-
:class
|
120
|
+
:class:`~lgdo.types.table.Table` using the `decoded_values` for key. If a
|
122
121
|
different type of LGDO object is required for this decoder, overload
|
123
122
|
this function.
|
124
123
|
|
@@ -178,7 +177,10 @@ class DataDecoder:
|
|
178
177
|
dt = attrs.pop("dt")
|
179
178
|
dt_units = attrs.pop("dt_units")
|
180
179
|
wf_len = attrs.pop("wf_len")
|
181
|
-
|
180
|
+
settings = {
|
181
|
+
"compression": attrs.pop("compression", {}),
|
182
|
+
"hdf5_settings": attrs.pop("hdf5_settings", {}),
|
183
|
+
}
|
182
184
|
|
183
185
|
wf_table = lgdo.WaveformTable(
|
184
186
|
size=size,
|
@@ -190,24 +192,20 @@ class DataDecoder:
|
|
190
192
|
dtype=dtype,
|
191
193
|
attrs=attrs,
|
192
194
|
)
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
if "t0" in compression:
|
202
|
-
wf_table.t0.attrs["compression"] = compression["t0"]
|
203
|
-
if "dt" in compression:
|
204
|
-
wf_table.dt.attrs["compression"] = compression["dt"]
|
195
|
+
|
196
|
+
# attach compression/hdf5_settings to sub-fields
|
197
|
+
for el in ["values", "t0", "dt"]:
|
198
|
+
for settings_name in ("hdf5_settings", "compression"):
|
199
|
+
if el in settings[settings_name]:
|
200
|
+
wf_table[el].attrs[settings_name] = settings[settings_name][
|
201
|
+
el
|
202
|
+
]
|
205
203
|
|
206
204
|
data_obj.add_field(field, wf_table)
|
207
205
|
continue
|
208
206
|
|
209
207
|
# Parse datatype for remaining lgdos
|
210
|
-
datatype, shape, elements = lgdo.
|
208
|
+
datatype, shape, elements = lgdo.lh5.utils.parse_datatype(datatype)
|
211
209
|
|
212
210
|
# ArrayOfEqualSizedArrays
|
213
211
|
if datatype == "array_of_equalsized_arrays":
|
@@ -258,7 +256,7 @@ class DataDecoder:
|
|
258
256
|
n_rows = self.garbage_table.loc
|
259
257
|
if n_rows == 0:
|
260
258
|
return
|
261
|
-
lh5_store.
|
259
|
+
lh5_store.write(
|
262
260
|
self.garbage_table, "garbage", filename, group, n_rows=n_rows, append=True
|
263
261
|
)
|
264
262
|
self.garbage_table.clear()
|
daq2lh5/fc/fc_config_decoder.py
CHANGED
daq2lh5/orca/orca_digitizers.py
CHANGED
@@ -98,11 +98,11 @@ class ORSIS3302DecoderForEnergy(OrcaDecoder):
|
|
98
98
|
sys.exit()
|
99
99
|
self.decoded_values[ccc]["waveform"]["wf_len"] = trace_length
|
100
100
|
|
101
|
-
def get_key_lists(self) -> list[list[
|
101
|
+
def get_key_lists(self) -> list[list[int]]:
|
102
102
|
key_lists = []
|
103
103
|
for key in self.decoded_values.keys():
|
104
104
|
key_lists.append([key])
|
105
|
-
return
|
105
|
+
return key_lists
|
106
106
|
|
107
107
|
def get_decoded_values(self, key: int = None) -> dict[str, Any]:
|
108
108
|
if key is None:
|
daq2lh5/orca/orca_flashcam.py
CHANGED
@@ -326,6 +326,7 @@ class ORFlashCamListenerStatusDecoder(OrcaDecoder):
|
|
326
326
|
def decode_packet(
|
327
327
|
self, packet: OrcaPacket, packet_id: int, rbl: RawBufferLibrary
|
328
328
|
) -> bool:
|
329
|
+
return False # FIXME: skip decoding until pyfcutils is updated
|
329
330
|
"""Decode the ORCA FlashCam Status packet."""
|
330
331
|
# aliases for brevity
|
331
332
|
if len(rbl) != 1:
|
daq2lh5/orca/orca_packet.py
CHANGED
@@ -47,11 +47,9 @@ def hex_dump(
|
|
47
47
|
as_short: bool = False,
|
48
48
|
id_dict: dict = None,
|
49
49
|
use_logging: bool = True,
|
50
|
+
return_output=False,
|
50
51
|
) -> None:
|
51
|
-
|
52
|
-
if use_logging:
|
53
|
-
dump_cmd = log.debug
|
54
|
-
|
52
|
+
output = []
|
55
53
|
data_id = get_data_id(packet, shift=shift_data_id)
|
56
54
|
n_words = get_n_words(packet)
|
57
55
|
if id_dict is not None:
|
@@ -62,9 +60,9 @@ def hex_dump(
|
|
62
60
|
else:
|
63
61
|
heading = f"data ID = {data_id}"
|
64
62
|
if print_n_words:
|
65
|
-
|
63
|
+
output.append(f"{heading}: {n_words} words")
|
66
64
|
else:
|
67
|
-
|
65
|
+
output.append(f"{heading}:")
|
68
66
|
n_to_print = int(np.minimum(n_words, max_words))
|
69
67
|
pad = int(np.ceil(np.log10(n_to_print)))
|
70
68
|
for i in range(n_to_print):
|
@@ -76,4 +74,13 @@ def hex_dump(
|
|
76
74
|
line += f" {packet[i]}"
|
77
75
|
if as_short:
|
78
76
|
line += f" {np.frombuffer(packet[i:i+1].tobytes(), dtype='uint16')}"
|
79
|
-
|
77
|
+
output.append(line)
|
78
|
+
|
79
|
+
dump_cmd = print # noqa: T202
|
80
|
+
if use_logging:
|
81
|
+
dump_cmd = log.debug
|
82
|
+
for line in output:
|
83
|
+
dump_cmd(line)
|
84
|
+
|
85
|
+
if return_output:
|
86
|
+
return output
|
daq2lh5/orca/orca_streamer.py
CHANGED
@@ -32,30 +32,138 @@ class OrcaStreamer(DataStreamer):
|
|
32
32
|
def __init__(self) -> None:
|
33
33
|
super().__init__()
|
34
34
|
self.in_stream = None
|
35
|
+
self.packet_locs = []
|
35
36
|
self.buffer = np.empty(1024, dtype="uint32") # start with a 4 kB packet buffer
|
36
37
|
self.header = None
|
37
38
|
self.header_decoder = OrcaHeaderDecoder()
|
38
39
|
self.decoder_id_dict = {} # dict of data_id to decoder object
|
39
40
|
self.rbl_id_dict = {} # dict of RawBufferLists for each data_id
|
41
|
+
self.missing_decoders = []
|
42
|
+
|
43
|
+
def load_packet_header(self) -> np.uint32 | None:
|
44
|
+
"""Loads the packet header at the current read location into the buffer
|
45
|
+
|
46
|
+
and updates internal variables.
|
47
|
+
"""
|
48
|
+
pkt_hdr = self.buffer[:1]
|
49
|
+
n_bytes_read = self.in_stream.readinto(pkt_hdr) # buffer is at least 4 kB long
|
50
|
+
self.n_bytes_read += n_bytes_read
|
51
|
+
if n_bytes_read == 0: # EOF
|
52
|
+
return None
|
53
|
+
if n_bytes_read != 4:
|
54
|
+
raise RuntimeError(f"only got {n_bytes_read} bytes for packet header")
|
55
|
+
|
56
|
+
# packet is valid. Can set the packet_id and log its location
|
57
|
+
self.packet_id += 1
|
58
|
+
filepos = self.in_stream.tell() - n_bytes_read
|
59
|
+
if self.packet_id < len(self.packet_locs):
|
60
|
+
if self.packet_locs[self.packet_id] != filepos:
|
61
|
+
raise RuntimeError(
|
62
|
+
f"filepos for packet {self.packet_id} was {filepos} but {self.packet_locs[self.packet_id]} was expected"
|
63
|
+
)
|
64
|
+
else:
|
65
|
+
if len(self.packet_locs) != self.packet_id:
|
66
|
+
raise RuntimeError(
|
67
|
+
f"loaded packet {self.packet_id} after packet {len(self.packet_locs)-1}"
|
68
|
+
)
|
69
|
+
self.packet_locs.append(filepos)
|
70
|
+
|
71
|
+
return pkt_hdr
|
72
|
+
|
73
|
+
def skip_packet(self, n: int = 1) -> bool:
|
74
|
+
"""Skip a packets without loading it into the internal buffer.
|
75
|
+
|
76
|
+
Requires loading the header. Optionally skips n packets.
|
77
|
+
|
78
|
+
Returns
|
79
|
+
----------
|
80
|
+
succeeded
|
81
|
+
returns False if reached EOF, otherwise returns true
|
82
|
+
"""
|
83
|
+
if self.in_stream is None:
|
84
|
+
raise RuntimeError("self.in_stream is None")
|
85
|
+
if not int(n) >= 0:
|
86
|
+
raise ValueError(f"n must be a non-negative int, can't be {n}")
|
87
|
+
n = int(n)
|
88
|
+
while n > 0:
|
89
|
+
pkt_hdr = self.load_packet_header()
|
90
|
+
if pkt_hdr is None:
|
91
|
+
return False
|
92
|
+
self.in_stream.seek((orca_packet.get_n_words(pkt_hdr) - 1) * 4, 1)
|
93
|
+
n -= 1
|
94
|
+
return True
|
95
|
+
|
96
|
+
def build_packet_locs(self, saveloc=True) -> None:
|
97
|
+
loc = self.in_stream.tell()
|
98
|
+
pid = self.packet_id
|
99
|
+
if len(self.packet_locs) > 0:
|
100
|
+
self.in_stream.seek(self.packet_locs[-1])
|
101
|
+
self.packet_id = len(self.packet_locs) - 2
|
102
|
+
while self.skip_packet():
|
103
|
+
pass # builds the rest of the packet_locs list
|
104
|
+
if saveloc:
|
105
|
+
self.in_stream.seek(loc)
|
106
|
+
self.packet_id = pid
|
107
|
+
|
108
|
+
def count_packets(self, saveloc=True) -> None:
|
109
|
+
self.build_packet_locs(saveloc=saveloc)
|
110
|
+
return len(self.packet_locs)
|
40
111
|
|
41
112
|
# TODO: need to correct for endianness?
|
42
|
-
def load_packet(
|
113
|
+
def load_packet(
|
114
|
+
self, index: int = None, whence: int = 0, skip_unknown_ids: bool = False
|
115
|
+
) -> np.uint32 | None:
|
43
116
|
"""Loads the next packet into the internal buffer.
|
44
117
|
|
45
118
|
Returns packet as a :class:`numpy.uint32` view of the buffer (a slice),
|
46
119
|
returns ``None`` at EOF.
|
120
|
+
|
121
|
+
Parameters
|
122
|
+
----------
|
123
|
+
index
|
124
|
+
Optionally give an index of packet to skip to, relative to the
|
125
|
+
"whence" location. Can be positive or negative. If out-of-range for
|
126
|
+
the file, None will be returned.
|
127
|
+
whence
|
128
|
+
used when an index is supplied. Follows the file.seek() convention:
|
129
|
+
whence = 0 (default) means index is relative to the beginning of the
|
130
|
+
file; whence = 1 means index is relative to the current position in
|
131
|
+
the file; whence = 2 means relative to the end of the file.
|
132
|
+
|
133
|
+
Returns
|
134
|
+
----------
|
135
|
+
packet
|
136
|
+
a view of the internal buffer spanning the packet data (uint32
|
137
|
+
ndarray). If you want to hold on to the packet data while you load
|
138
|
+
more packets, you can call copy() on the view to make a copy.
|
47
139
|
"""
|
48
140
|
if self.in_stream is None:
|
49
141
|
raise RuntimeError("self.in_stream is None")
|
50
142
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
143
|
+
if index is not None:
|
144
|
+
if whence not in [0, 1, 2]:
|
145
|
+
raise ValueError(f"whence can't be {whence}")
|
146
|
+
index = int(index)
|
147
|
+
# convert whence 1 or 2 to whence = 0
|
148
|
+
if whence == 1: # index is relative to current position
|
149
|
+
index += self.packet_id - 1
|
150
|
+
elif whence == 2: # index is relative to end of file
|
151
|
+
self.build_packet_locs(saveloc=False)
|
152
|
+
index += len(self.packet_locs) - 2
|
153
|
+
if index < 0:
|
154
|
+
self.in_stream.seek(0)
|
155
|
+
self.packet_id = -1
|
156
|
+
return None
|
157
|
+
while index >= len(self.packet_locs):
|
158
|
+
if not self.skip_packet():
|
159
|
+
return None
|
160
|
+
self.in_stream.seek(self.packet_locs[index])
|
161
|
+
self.packet_id = index - 1
|
162
|
+
|
163
|
+
# load packet header
|
164
|
+
pkt_hdr = self.load_packet_header()
|
165
|
+
if pkt_hdr is None:
|
56
166
|
return None
|
57
|
-
if n_bytes_read != 4:
|
58
|
-
raise RuntimeError(f"only got {n_bytes_read} bytes for packet header")
|
59
167
|
|
60
168
|
# if it's a short packet, we are done
|
61
169
|
if orca_packet.is_short(pkt_hdr):
|
@@ -69,7 +177,6 @@ class OrcaStreamer(DataStreamer):
|
|
69
177
|
not in self.decoder_id_dict
|
70
178
|
):
|
71
179
|
self.in_stream.seek((n_words - 1) * 4, 1)
|
72
|
-
self.n_bytes_read += (n_words - 1) * 4 # well, we didn't really read it...
|
73
180
|
return pkt_hdr
|
74
181
|
|
75
182
|
# load into buffer, resizing as necessary
|
@@ -204,15 +311,17 @@ class OrcaStreamer(DataStreamer):
|
|
204
311
|
"""
|
205
312
|
|
206
313
|
self.set_in_stream(stream_name)
|
314
|
+
self.packet_id = -1
|
207
315
|
|
208
316
|
# read in the header
|
209
317
|
packet = self.load_packet()
|
318
|
+
if packet is None:
|
319
|
+
raise RuntimeError(f"no orca data in file {stream_name}")
|
210
320
|
if orca_packet.get_data_id(packet) != 0:
|
211
321
|
raise RuntimeError(
|
212
322
|
f"got data id {orca_packet.get_data_id(packet)} for header"
|
213
323
|
)
|
214
324
|
|
215
|
-
self.packet_id = 0
|
216
325
|
self.any_full |= self.header_decoder.decode_packet(packet, self.packet_id)
|
217
326
|
self.header = self.header_decoder.header
|
218
327
|
|
@@ -240,9 +349,7 @@ class OrcaStreamer(DataStreamer):
|
|
240
349
|
name = id_to_dec_name_dict[data_id]
|
241
350
|
if name not in instantiated_decoders:
|
242
351
|
if name not in globals():
|
243
|
-
|
244
|
-
f"no implementation of {name}, corresponding packets will be skipped"
|
245
|
-
)
|
352
|
+
self.missing_decoders.append(data_id)
|
246
353
|
continue
|
247
354
|
decoder = globals()[name]
|
248
355
|
instantiated_decoders[name] = decoder(header=self.header)
|
@@ -296,13 +403,18 @@ class OrcaStreamer(DataStreamer):
|
|
296
403
|
packet = self.load_packet(skip_unknown_ids=True)
|
297
404
|
if packet is None:
|
298
405
|
return False
|
299
|
-
self.packet_id += 1
|
300
406
|
|
301
407
|
# look up the data id, decoder, and rbl
|
302
408
|
data_id = orca_packet.get_data_id(packet, shift=False)
|
303
409
|
log.debug(
|
304
410
|
f"packet {self.packet_id}: data_id = {data_id}, decoder = {'None' if data_id not in self.decoder_id_dict else type(self.decoder_id_dict[data_id]).__name__}"
|
305
411
|
)
|
412
|
+
if data_id in self.missing_decoders:
|
413
|
+
name = self.header.get_id_to_decoder_name_dict(shift_data_id=False)[
|
414
|
+
data_id
|
415
|
+
]
|
416
|
+
log.warning(f"no implementation of {name}, packets were skipped")
|
417
|
+
continue
|
306
418
|
if data_id in self.rbl_id_dict:
|
307
419
|
break
|
308
420
|
|
daq2lh5/raw_buffer.py
CHANGED
@@ -65,21 +65,19 @@ keys.
|
|
65
65
|
from __future__ import annotations
|
66
66
|
|
67
67
|
import os
|
68
|
-
from typing import Union
|
69
68
|
|
70
69
|
import lgdo
|
71
|
-
from lgdo import
|
70
|
+
from lgdo import LGDO
|
71
|
+
from lgdo.lh5 import LH5Store
|
72
72
|
|
73
73
|
from .buffer_processor.buffer_processor import buffer_processor
|
74
74
|
|
75
|
-
LGDO = Union[lgdo.Scalar, lgdo.Struct, lgdo.Array, lgdo.VectorOfVectors]
|
76
|
-
|
77
75
|
|
78
76
|
class RawBuffer:
|
79
77
|
r"""Base class to represent a buffer of raw data.
|
80
78
|
|
81
79
|
A :class:`RawBuffer` is in essence a an LGDO object (typically a
|
82
|
-
:class
|
80
|
+
:class:`~lgdo.types.table.Table`) to which decoded data will be written, along
|
83
81
|
with some meta-data distinguishing what data goes into it, and where the
|
84
82
|
LGDO gets written out. Also holds on to the current location in the buffer
|
85
83
|
for writing.
|
@@ -88,7 +86,7 @@ class RawBuffer:
|
|
88
86
|
----------
|
89
87
|
lgdo
|
90
88
|
the LGDO used as the actual buffer. Typically a
|
91
|
-
:class
|
89
|
+
:class:`~lgdo.types.table.Table`. Set to ``None`` upon creation so that the
|
92
90
|
user or a decoder can initialize it later.
|
93
91
|
key_list
|
94
92
|
a list of keys (e.g. channel numbers) identifying data to be written
|
@@ -107,7 +105,7 @@ class RawBuffer:
|
|
107
105
|
proc_spec
|
108
106
|
a dictionary containing the following:
|
109
107
|
- a DSP config file, passed as a dictionary, or as a path to a JSON file
|
110
|
-
- an array containing: the name of an
|
108
|
+
- an array containing: the name of an LGDO object stored in the :class:`.RawBuffer` to be sliced,
|
111
109
|
the start and end indices of the slice, and the new name for the sliced object
|
112
110
|
- a dictionary of fields to drop
|
113
111
|
- a dictionary of new fields and their return datatype
|
@@ -440,11 +438,11 @@ def write_to_lh5_and_clear(
|
|
440
438
|
files (saves some time opening / closing files).
|
441
439
|
**kwargs
|
442
440
|
keyword-arguments forwarded to
|
443
|
-
:meth
|
441
|
+
:meth:`lgdo.lh5.store.LH5Store.write`.
|
444
442
|
|
445
443
|
See Also
|
446
444
|
--------
|
447
|
-
|
445
|
+
lgdo.lh5.store.LH5Store.write
|
448
446
|
"""
|
449
447
|
if lh5_store is None:
|
450
448
|
lh5_store = lgdo.LH5Store()
|
@@ -470,7 +468,7 @@ def write_to_lh5_and_clear(
|
|
470
468
|
|
471
469
|
# write if requested...
|
472
470
|
if filename != "":
|
473
|
-
lh5_store.
|
471
|
+
lh5_store.write(
|
474
472
|
lgdo_to_write,
|
475
473
|
rb.out_name,
|
476
474
|
filename,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
|
-
Name:
|
3
|
-
Version: 1.
|
2
|
+
Name: legend_daq2lh5
|
3
|
+
Version: 1.2.0a1
|
4
4
|
Summary: Convert digitizer data to LH5
|
5
5
|
Home-page: https://github.com/legend-exp/legend-daq2lh5
|
6
6
|
Author: Jason Detwiler
|
@@ -26,10 +26,10 @@ Classifier: Topic :: Software Development
|
|
26
26
|
Requires-Python: >=3.9
|
27
27
|
Description-Content-Type: text/markdown
|
28
28
|
License-File: LICENSE
|
29
|
-
Requires-Dist: dspeed
|
29
|
+
Requires-Dist: dspeed >=1.3.0a4
|
30
30
|
Requires-Dist: h5py >=3.2.0
|
31
31
|
Requires-Dist: hdf5plugin
|
32
|
-
Requires-Dist: legend-pydataobj
|
32
|
+
Requires-Dist: legend-pydataobj >=1.5.0a1
|
33
33
|
Requires-Dist: numpy >=1.21
|
34
34
|
Requires-Dist: pyfcutils
|
35
35
|
Requires-Dist: tqdm >=4.27
|
@@ -60,3 +60,16 @@ Requires-Dist: pytest-cov ; extra == 'test'
|
|
60
60
|

|
61
61
|

|
62
62
|
[](https://legend-daq2lh5.readthedocs.io)
|
63
|
+
|
64
|
+
JSON-configurable conversion of digitized data into
|
65
|
+
[LEGEND HDF5](https://legend-exp.github.io/legend-data-format-specs/dev/hdf5/),
|
66
|
+
with optional data pre-processing via [dspeed](https://dspeed.readthedocs.io)
|
67
|
+
and data compression via [legend-pydataobj](https://legend-pydataobj.readthedocs.io).
|
68
|
+
|
69
|
+
Currently supported DAQ data formats:
|
70
|
+
* [FlashCam](https://www.mizzi-computer.de/home)
|
71
|
+
* [CoMPASS](https://www.caen.it/products/compass)
|
72
|
+
* [ORCA](https://github.com/unc-enap/Orca), reading out:
|
73
|
+
- FlashCam
|
74
|
+
- [Struck SIS3302](https://www.struck.de/sis3302.htm)
|
75
|
+
- [Struck SIS3316](https://www.struck.de/sis3316.html)
|
@@ -1,36 +1,36 @@
|
|
1
1
|
daq2lh5/__init__.py,sha256=VPmwKuZSA0icpce05ojhnsKWhR4_QUgD0oVXUoN9wks,975
|
2
|
-
daq2lh5/_version.py,sha256=
|
3
|
-
daq2lh5/build_raw.py,sha256=
|
2
|
+
daq2lh5/_version.py,sha256=k5PS9p0a5Ey36DDxagN4mnTZow7bHSa0Oh_ycx0FrX4,413
|
3
|
+
daq2lh5/build_raw.py,sha256=JFXC5ln9u353TUZMksY3zydLiV2HlxqdI6_Y2_ZMCIE,10524
|
4
4
|
daq2lh5/cli.py,sha256=HCZ9Vyg-gqvairN9zJIpBjw5vLpp9ZUOOQYLFxloLL8,2912
|
5
|
-
daq2lh5/data_decoder.py,sha256=
|
5
|
+
daq2lh5/data_decoder.py,sha256=ka2WIJuPvsG892__HCW1SagCEzyiZJ2kQP6zGDMtlr0,10641
|
6
6
|
daq2lh5/data_streamer.py,sha256=6SEAekOHyfC4k3E0df0lW37ap6ZemVFbH8PYMl6UvCU,14130
|
7
7
|
daq2lh5/logging.py,sha256=Nu3wgIoWN7cyUxuzPom5rMwFvTlBu8p8d9uONHDquRg,965
|
8
|
-
daq2lh5/raw_buffer.py,sha256=
|
8
|
+
daq2lh5/raw_buffer.py,sha256=dyPUok0N3MP41oP9F8sO_PrH7-SWs9UdPh7dqCF729g,17687
|
9
9
|
daq2lh5/buffer_processor/__init__.py,sha256=7k6v_KPximtv7805QnX4-xp_S3vqvqwDfdV3q95oZJo,84
|
10
|
-
daq2lh5/buffer_processor/buffer_processor.py,sha256=
|
11
|
-
daq2lh5/buffer_processor/lh5_buffer_processor.py,sha256=
|
10
|
+
daq2lh5/buffer_processor/buffer_processor.py,sha256=GUxpNDbqGLuUEZmXjeratipbzmki12RFNYZkxgMtesg,14483
|
11
|
+
daq2lh5/buffer_processor/lh5_buffer_processor.py,sha256=yL1ru0_GTsZx099oi45sXL-FxPfdChtStd_IFtZNI_Q,8222
|
12
12
|
daq2lh5/compass/__init__.py,sha256=mOXHWp7kRDgNTPQty3E8k2KPSy_vAzjneKfAcCVaPyE,132
|
13
13
|
daq2lh5/compass/compass_config_parser.py,sha256=zeAsOo1dOJPGLL8-zkAcdYRkqt8BodtOPi96n7fWsl4,12300
|
14
14
|
daq2lh5/compass/compass_event_decoder.py,sha256=kiPOaEu8SgLD2wbSPbBahcbTBBRAIw35wtVLBcwPcXY,7386
|
15
15
|
daq2lh5/compass/compass_header_decoder.py,sha256=AA-Md2FIT3nD4mXX9CrWvbbfmKiA436-BTmzcU3_XOY,2823
|
16
16
|
daq2lh5/compass/compass_streamer.py,sha256=zSl7IqO0ID0wcixkLE9QVEG3bF9hfGVITVPomCeOFTM,8841
|
17
17
|
daq2lh5/fc/__init__.py,sha256=bB1j6r-bDmylNi0iutQeAJGjsDSjLSoXMqFfXWwfb8I,141
|
18
|
-
daq2lh5/fc/fc_config_decoder.py,sha256=
|
18
|
+
daq2lh5/fc/fc_config_decoder.py,sha256=RLRfUOZN0vYbAprqTymP7TGg641IiP9rgCGIOwWVKzU,1996
|
19
19
|
daq2lh5/fc/fc_event_decoder.py,sha256=JIRsySnxeuY3wmxjJOrTXo6wpelVup8WIvxU-fkPL-A,8131
|
20
20
|
daq2lh5/fc/fc_status_decoder.py,sha256=o_3vTAgYXelZxIsreCYioVYid2mY-wqloYKlxoCqX5Q,3390
|
21
21
|
daq2lh5/fc/fc_streamer.py,sha256=S0imXdVsiyolPvxI1uiBngpC58DporSNZPqx1HeVi5o,5737
|
22
22
|
daq2lh5/orca/__init__.py,sha256=Xf6uOIOzk_QkKH_7VizGlCo3iuiAgLtUE3A07x_HXC0,175
|
23
23
|
daq2lh5/orca/orca_base.py,sha256=-XIolXsHj-1EdewaGxyvJTZvRGZsDyZe-5PzVOd-LFY,1333
|
24
|
-
daq2lh5/orca/orca_digitizers.py,sha256=
|
25
|
-
daq2lh5/orca/orca_flashcam.py,sha256=
|
24
|
+
daq2lh5/orca/orca_digitizers.py,sha256=BsAA3OgQ13YIirDM8pd_xDY3F5FqEY4YjSHviflmov8,20867
|
25
|
+
daq2lh5/orca/orca_flashcam.py,sha256=gsvPorUXk1Jn-U93GsxXJ5z6pbTK2yjsYDqZFVCm57U,33088
|
26
26
|
daq2lh5/orca/orca_header.py,sha256=1tDRG8l9Gqu4c0K4BjXBSC5eiLTzY_HaCsgNBiv5EgI,4283
|
27
27
|
daq2lh5/orca/orca_header_decoder.py,sha256=ORIIyfx22ybyKc-uyWy5ER49-dl3BGpHdfV8OCDmjIw,1632
|
28
|
-
daq2lh5/orca/orca_packet.py,sha256=
|
28
|
+
daq2lh5/orca/orca_packet.py,sha256=TcdfuYN8_gcug_Xdjz98KqjHw1MqJ4J98zc7WI2xtf4,2488
|
29
29
|
daq2lh5/orca/orca_run_decoder.py,sha256=3atKXC6mDi8_PK6ICUBBJ-LyaTM8OU31kKWIpmttRr4,2065
|
30
|
-
daq2lh5/orca/orca_streamer.py,sha256=
|
31
|
-
legend_daq2lh5-1.
|
32
|
-
legend_daq2lh5-1.
|
33
|
-
legend_daq2lh5-1.
|
34
|
-
legend_daq2lh5-1.
|
35
|
-
legend_daq2lh5-1.
|
36
|
-
legend_daq2lh5-1.
|
30
|
+
daq2lh5/orca/orca_streamer.py,sha256=VbD9PF-rx_Rk-rEy7XECPmgxr6kZSUf0tC7Qbol3Qeg,15693
|
31
|
+
legend_daq2lh5-1.2.0a1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
32
|
+
legend_daq2lh5-1.2.0a1.dist-info/METADATA,sha256=QiBKAO0ycatdNK5W8HlhHXA28pUnqFr2iPnjyjr2RAE,3755
|
33
|
+
legend_daq2lh5-1.2.0a1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
34
|
+
legend_daq2lh5-1.2.0a1.dist-info/entry_points.txt,sha256=R08R4NrHi0ab5MJN_qKqzePVzrLSsw5WpmbiwwduYjw,59
|
35
|
+
legend_daq2lh5-1.2.0a1.dist-info/top_level.txt,sha256=MJQVLyLqMgMKBdVfNXFaCKCjHKakAs19VLbC9ctXZ7A,8
|
36
|
+
legend_daq2lh5-1.2.0a1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|