legend-daq2lh5 1.0.2__py3-none-any.whl → 1.2.0a1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
![GitHub pull requests](https://img.shields.io/github/issues-pr/legend-exp/legend-daq2lh5?logo=github)
|
61
61
|
![License](https://img.shields.io/github/license/legend-exp/legend-daq2lh5)
|
62
62
|
[![Read the Docs](https://img.shields.io/readthedocs/legend-daq2lh5?logo=readthedocs)](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
|