legend-daq2lh5 1.6.1__py3-none-any.whl → 1.6.3__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 CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '1.6.1'
21
- __version_tuple__ = version_tuple = (1, 6, 1)
20
+ __version__ = version = '1.6.3'
21
+ __version_tuple__ = version_tuple = (1, 6, 3)
daq2lh5/cli.py CHANGED
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
+ import json
6
7
  import os
7
8
  import sys
8
9
 
@@ -78,6 +79,13 @@ def daq2lh5_cli():
78
79
  parser.add_argument(
79
80
  "--overwrite", "-w", action="store_true", help="""Overwrite output files"""
80
81
  )
82
+ parser.add_argument(
83
+ "--kwargs",
84
+ "-k",
85
+ type=str,
86
+ default="{}",
87
+ help="""Any additional kwargs to pass to build_raw, will be parsed as a JSON string""",
88
+ )
81
89
 
82
90
  args = parser.parse_args()
83
91
 
@@ -92,6 +100,9 @@ def daq2lh5_cli():
92
100
  print(__version__) # noqa: T201
93
101
  sys.exit()
94
102
 
103
+ if args.kwargs:
104
+ kwargs = json.loads(args.kwargs)
105
+
95
106
  for stream in args.in_stream:
96
107
  basename = os.path.splitext(os.path.basename(stream))[0]
97
108
  build_raw(
@@ -102,4 +113,5 @@ def daq2lh5_cli():
102
113
  n_max=args.max_rows,
103
114
  overwrite=args.overwrite,
104
115
  orig_basename=basename,
116
+ **kwargs,
105
117
  )
@@ -25,8 +25,10 @@ compass_decoded_values = {
25
25
  "channel": {"dtype": "uint32"},
26
26
  # Timestamp of event
27
27
  "timestamp": {"dtype": "float64", "units": "ps"},
28
- # Energy of event
28
+ # Energy of event in channels
29
29
  "energy": {"dtype": "uint32"},
30
+ # Energy of event, calibrated
31
+ "energy_calibrated": {"dtype": "float64"},
30
32
  # Energy short of event
31
33
  "energy_short": {"dtype": "uint32"},
32
34
  # Flags that the digitizer raised
@@ -153,50 +155,56 @@ class CompassEventDecoder(DataDecoder):
153
155
  # the time stamp also does not care about if we have an energy short present
154
156
  tbl["timestamp"].nda[ii] = np.frombuffer(packet[4:12], dtype=np.uint64)[0]
155
157
 
156
- # get the rest of the values depending on if there is an energy_short present
157
- if int(header["energy_short"].value) == 1: # again, the header is a struct
158
+ # stumble our way through the energy, depending on what the header says
159
+ bytes_read = 12
160
+ if int(header["energy_channels"].value) == 1:
158
161
  tbl["energy"].nda[ii] = np.frombuffer(packet[12:14], dtype=np.uint16)[0]
159
- tbl["energy_short"].nda[ii] = np.frombuffer(packet[14:16], dtype=np.uint16)[
160
- 0
161
- ]
162
- tbl["flags"].nda[ii] = np.frombuffer(packet[16:20], np.uint32)[0]
163
- tbl["num_samples"].nda[ii] = np.frombuffer(packet[21:25], dtype=np.uint32)[
164
- 0
165
- ]
166
-
167
- if (
168
- tbl["num_samples"].nda[ii]
169
- != self.decoded_values[bc]["waveform"]["wf_len"]
170
- ): # make sure that the waveform we read in is the same length as in the config
171
- raise RuntimeError(
172
- f"Waveform size {tbl['num_samples'].nda[ii]} doesn't match expected size {self.decoded_values[bc]['waveform']['wf_len']}. "
173
- "Skipping packet"
174
- )
175
-
176
- tbl["waveform"]["values"].nda[ii] = np.frombuffer(
177
- packet[25:], dtype=np.uint16
178
- )
179
-
162
+ bytes_read += 2
163
+ if int(header["energy_calibrated"].value) == 1:
164
+ tbl["energy_calibrated"].nda[ii] = None
165
+ elif (int(header["energy_calibrated"].value) == 1) and (
166
+ int(header["energy_channels"].value) == 0
167
+ ):
168
+ tbl["energy_calibrated"].nda[ii] = np.frombuffer(
169
+ packet[14:22], dtype=np.float64
170
+ )[0]
171
+ bytes_read += 8
172
+ tbl["energy"].nda[ii] = None
180
173
  else:
181
- tbl["energy"].nda[ii] = np.frombuffer(packet[12:14], dtype=np.uint16)[0]
182
- tbl["energy_short"].nda[ii] = None
183
- tbl["flags"].nda[ii] = np.frombuffer(packet[14:18], np.uint32)[0]
184
- tbl["num_samples"].nda[ii] = np.frombuffer(packet[19:23], dtype=np.uint32)[
185
- 0
186
- ]
187
-
188
- if (
189
- tbl["num_samples"].nda[ii]
190
- != self.decoded_values[bc]["waveform"]["wf_len"]
191
- ): # make sure that the waveform we read in is the same length as in the config
192
- raise RuntimeError(
193
- f"Waveform size {tbl['num_samples'].nda[ii]} doesn't match expected size {self.decoded_values[bc]['waveform']['wf_len']}. "
194
- "Skipping packet"
195
- )
196
-
197
- tbl["waveform"]["values"].nda[ii] = np.frombuffer(
198
- packet[23:], dtype=np.uint16
174
+ tbl["energy_calibrated"].nda[ii] = np.frombuffer(
175
+ packet[12:20], dtype=np.float64
176
+ )[0]
177
+ bytes_read += 8
178
+
179
+ # now handle the energy short
180
+ if int(header["energy_short"].value) == 1:
181
+ tbl["energy_short"].nda[ii] = np.frombuffer(
182
+ packet[bytes_read : bytes_read + 2], dtype=np.uint16
183
+ )[0]
184
+ bytes_read += 2
185
+ else:
186
+ tbl["energy_short"].nda[ii] = 0
187
+
188
+ tbl["flags"].nda[ii] = np.frombuffer(
189
+ packet[bytes_read : bytes_read + 4], np.uint32
190
+ )[0]
191
+ bytes_read += 5 # skip over the waveform code
192
+ tbl["num_samples"].nda[ii] = np.frombuffer(
193
+ packet[bytes_read : bytes_read + 4], dtype=np.uint32
194
+ )[0]
195
+ bytes_read += 4
196
+
197
+ if (
198
+ tbl["num_samples"].nda[ii] != self.decoded_values[bc]["waveform"]["wf_len"]
199
+ ): # make sure that the waveform we read in is the same length as in the config
200
+ raise RuntimeError(
201
+ f"Waveform size {tbl['num_samples'].nda[ii]} doesn't match expected size {self.decoded_values[bc]['waveform']['wf_len']}. "
202
+ "Skipping packet"
199
203
  )
200
204
 
205
+ tbl["waveform"]["values"].nda[ii] = np.frombuffer(
206
+ packet[bytes_read:], dtype=np.uint16
207
+ )
208
+
201
209
  evt_rbkd[bc].loc += 1
202
210
  return evt_rbkd[bc].is_full()
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
 
5
5
  import lgdo
6
+ import numpy as np
6
7
 
7
8
  from ..data_decoder import DataDecoder
8
9
  from .compass_config_parser import compass_config_to_struct
@@ -19,9 +20,7 @@ class CompassHeaderDecoder(DataDecoder):
19
20
  super().__init__(*args, **kwargs)
20
21
  self.config = None # initialize to none, because compass_config_to_struct always returns a struct
21
22
 
22
- def decode_header(
23
- self, in_stream: bytes, config_file: str = None, wf_len: int = None
24
- ) -> dict:
23
+ def decode_header(self, in_stream: bytes, config_file: str = None) -> dict:
25
24
  """Decode the CoMPASS file header, and add CoMPASS config data to the header, if present.
26
25
 
27
26
  Parameters
@@ -30,8 +29,6 @@ class CompassHeaderDecoder(DataDecoder):
30
29
  The stream of data to have its header decoded
31
30
  config_file
32
31
  The config file for the CoMPASS data, if present
33
- wf_len
34
- The length of the first waveform in the file, only pre-calculated when the config_file is none
35
32
 
36
33
  Returns
37
34
  -------
@@ -39,27 +36,203 @@ class CompassHeaderDecoder(DataDecoder):
39
36
  A dict containing the header information, as well as the important config information
40
37
  of wf_len and num_enabled_channels
41
38
  """
42
- self.config = compass_config_to_struct(config_file, wf_len)
43
-
39
+ wf_len = None
44
40
  config_names = [
45
41
  "energy_channels", # energy is given in channels (0: false, 1: true)
46
42
  "energy_calibrated", # energy is given in keV/MeV, according to the calibration (0: false, 1: true)
47
43
  "energy_short", # energy short is present (0: false, 1: true)
48
44
  "waveform_samples", # waveform samples are present (0: false, 1: true)
49
- ]
50
- header_in_bytes = in_stream.read(
51
- 2
52
- ) # we always have to read in the first 2 bytes of the header for CoMPASS v2 files
53
- header_in_binary = bin(int.from_bytes(header_in_bytes, byteorder="little"))
54
- header_as_list = str(header_in_binary)[
55
- ::-1
56
- ] # reverse it as we care about bit 0, bit 1, etc.
57
-
58
- for i, name in enumerate(config_names):
45
+ "header_present", # there is a 2 byte header present in the file (0: false, 1: true)
46
+ ] # need to determine which of these are present in a file
47
+
48
+ # First need to check if the first two bytes are of the form 0xCAEx
49
+ # CoMPASS specs say that every file should start with this header, but if CoMPASS writes size-limited files, then this header may not be present in *all* files...
50
+ header_in_bytes = in_stream.read(2)
51
+
52
+ if header_in_bytes[-1] == int.from_bytes(b"\xca", byteorder="big"):
53
+ log.debug("header is present in file.")
54
+ header_in_binary = bin(int.from_bytes(header_in_bytes, byteorder="little"))
55
+ header_as_list = str(header_in_binary)[
56
+ ::-1
57
+ ] # reverse it as we care about bit 0, bit 1, etc.
58
+ header_dict = dict(
59
+ {
60
+ "energy_channels": int(header_as_list[0]) == 1,
61
+ "energy_calibrated": int(header_as_list[1]) == 1,
62
+ "energy_channels_calibrated": int(header_as_list[0])
63
+ == 1 & int(header_as_list[1])
64
+ == 1,
65
+ "energy_short": int(header_as_list[2]) == 1,
66
+ "waveform_samples": int(header_as_list[3]) == 1,
67
+ "header_present": True,
68
+ }
69
+ )
70
+
71
+ # if we don't have the wf_len, get it now
72
+
73
+ if config_file is None:
74
+ if header_dict["waveform_samples"] == 0:
75
+ wf_len = 0
76
+ else:
77
+ wf_byte_len = 4
78
+ bytes_to_read = (
79
+ 12 # covers 2-byte board, 2-byte channel, 8-byte time stamp
80
+ )
81
+ bytes_to_read += (
82
+ 2 * header_dict["energy_channels"]
83
+ + 8 * header_dict["energy_calibrated"]
84
+ + 2 * header_dict["energy_short"]
85
+ )
86
+ bytes_to_read += 4 + 1 # 4-byte flags, 1-byte waveform code
87
+ first_bytes = in_stream.read(bytes_to_read + wf_byte_len)
88
+
89
+ wf_len = np.frombuffer(
90
+ first_bytes[bytes_to_read : bytes_to_read + wf_byte_len],
91
+ dtype=np.uint32,
92
+ )[0]
93
+
94
+ # if header is not present, we need to play some tricks
95
+ # either energy short is present or not
96
+ # and one of three options for energy (ADC, calibrated, both)
97
+ else:
98
+ # If the 2 byte header is not present, then we have read in the board by accident
99
+ header_in_bytes += in_stream.read(
100
+ 10
101
+ ) # read in the 2-byte ch and 8-byte timestamp
102
+ bytes_read = 12
103
+ fixed_header_start_len = (
104
+ 12 # always 12 bytes: 2-byte board, 2-byte channel, 8-byte timestamp
105
+ )
106
+ possible_energy_header_byte_lengths = [
107
+ 2,
108
+ 8,
109
+ 10,
110
+ ] # either ADC, Calibrated, or both
111
+ possible_energy_short_header_byte_lengths = [
112
+ 0,
113
+ 2,
114
+ ] # energy short is present or not
115
+ fixed_header_part = 5 # 5 bytes from flags + code
116
+ wf_len_bytes = 4 # wf_len is 4 bytes long
117
+
118
+ for prefix in possible_energy_header_byte_lengths:
119
+ terminate = False
120
+ for suffix in possible_energy_short_header_byte_lengths:
121
+
122
+ # ---- first packet -------
123
+ # don't read more than we have to, check how many more bytes we need to read in
124
+ difference = (
125
+ fixed_header_start_len
126
+ + prefix
127
+ + suffix
128
+ + fixed_header_part
129
+ + wf_len_bytes
130
+ - bytes_read
131
+ )
132
+ if difference > 0:
133
+ # just read a bit more data
134
+ header_in_bytes += in_stream.read(difference)
135
+ bytes_read += difference
136
+
137
+ wf_len_guess = np.frombuffer(
138
+ header_in_bytes[
139
+ fixed_header_start_len
140
+ + prefix
141
+ + suffix
142
+ + fixed_header_part : fixed_header_start_len
143
+ + prefix
144
+ + suffix
145
+ + fixed_header_part
146
+ + wf_len_bytes
147
+ ],
148
+ dtype=np.uint32,
149
+ )[0]
150
+
151
+ # read in the first waveform data
152
+ difference = (
153
+ fixed_header_start_len
154
+ + prefix
155
+ + suffix
156
+ + fixed_header_part
157
+ + wf_len_bytes
158
+ + 2 * wf_len_guess
159
+ - bytes_read
160
+ )
161
+ if difference > 0:
162
+ header_in_bytes += in_stream.read(2 * wf_len_guess)
163
+ bytes_read += 2 * wf_len_guess
164
+
165
+ # ------ second packet header ----------
166
+ difference = (
167
+ 2
168
+ * (
169
+ fixed_header_start_len
170
+ + prefix
171
+ + suffix
172
+ + fixed_header_part
173
+ + wf_len_bytes
174
+ )
175
+ + 2 * wf_len_guess
176
+ - bytes_read
177
+ )
178
+ if difference > 0:
179
+ header_in_bytes += in_stream.read(difference)
180
+ bytes_read += (
181
+ fixed_header_start_len
182
+ + prefix
183
+ + suffix
184
+ + fixed_header_part
185
+ + wf_len_bytes
186
+ )
187
+ wf_len_guess_2 = np.frombuffer(
188
+ header_in_bytes[
189
+ 2
190
+ * (
191
+ fixed_header_start_len
192
+ + prefix
193
+ + suffix
194
+ + fixed_header_part
195
+ )
196
+ + wf_len_bytes
197
+ + 2
198
+ * wf_len_guess : 2
199
+ * (
200
+ fixed_header_start_len
201
+ + prefix
202
+ + suffix
203
+ + fixed_header_part
204
+ + wf_len_bytes
205
+ )
206
+ + 2 * wf_len_guess
207
+ ],
208
+ dtype=np.uint32,
209
+ )[0]
210
+
211
+ # if the waveform lengths agree, then we can stride packets correctly
212
+ if wf_len_guess_2 == wf_len_guess:
213
+ header_dict = dict(
214
+ {
215
+ "energy_channels": prefix == 2,
216
+ "energy_calibrated": prefix == 8,
217
+ "energy_channels_calibrated": prefix == 10,
218
+ "energy_short": suffix == 2,
219
+ "waveform_samples": wf_len != 0,
220
+ "header_present": False,
221
+ }
222
+ )
223
+ wf_len = wf_len_guess
224
+ terminate = True
225
+ break
226
+ if terminate:
227
+ break
228
+
229
+ self.config = compass_config_to_struct(config_file, wf_len)
230
+
231
+ for name in config_names:
59
232
  if name in self.config:
60
233
  log.warning(f"{name} already in self.config. skipping...")
61
234
  continue
62
- value = int(header_as_list[i])
235
+ value = int(header_dict[name])
63
236
  self.config.add_field(
64
237
  str(name), lgdo.Scalar(value)
65
238
  ) # self.config is a struct
@@ -72,22 +72,6 @@ class CompassStreamer(DataStreamer):
72
72
  So, we must read this header once, and then proceed to read packets in.
73
73
  """
74
74
  # If a config file is not present, the wf_len can be determined by opening the first few bytes of the in_stream
75
- wf_len = None
76
- if self.compass_config_file is None:
77
- self.set_in_stream(stream_name)
78
-
79
- first_bytes = self.in_stream.read(27)
80
-
81
- energy_short = str(
82
- bin(int.from_bytes(first_bytes[:2], byteorder="little"))
83
- )[::-1][2]
84
-
85
- if int(energy_short) == 1:
86
- [wf_len] = np.frombuffer(first_bytes[23:27], dtype=np.uint32)
87
- else:
88
- [wf_len] = np.frombuffer(first_bytes[21:25], dtype=np.uint32)
89
-
90
- self.close_stream()
91
75
 
92
76
  # set the in_stream
93
77
  self.set_in_stream(stream_name)
@@ -95,11 +79,20 @@ class CompassStreamer(DataStreamer):
95
79
 
96
80
  # read in and decode the file header info, passing the compass_config_file, if present
97
81
  self.header = self.header_decoder.decode_header(
98
- self.in_stream, self.compass_config_file, wf_len
82
+ self.in_stream, self.compass_config_file
99
83
  ) # returns an lgdo.Struct
100
- self.n_bytes_read += (
101
- 2 # there are 2 bytes in the header, for a 16 bit number to read out
102
- )
84
+ self.close_stream()
85
+
86
+ # Now we are ready to read the data
87
+ self.set_in_stream(stream_name)
88
+ self.n_bytes_read = 0
89
+
90
+ if int(self.header["header_present"].value) == 1:
91
+ # read 2 bytes if we need to
92
+ self.in_stream.read(2)
93
+ self.n_bytes_read += (
94
+ 2 # there are 2 bytes in the header, for a 16 bit number to read out
95
+ )
103
96
 
104
97
  # set up data loop variables
105
98
  self.packet_id = 0
@@ -171,16 +164,34 @@ class CompassStreamer(DataStreamer):
171
164
  if self.in_stream is None:
172
165
  raise RuntimeError("self.in_stream is None")
173
166
 
174
- if (self.packet_id == 0) and (self.n_bytes_read != 2):
167
+ if (
168
+ (self.packet_id == 0)
169
+ and (self.n_bytes_read != 2)
170
+ and (int(self.header["header_present"].value) == 1)
171
+ ):
175
172
  raise RuntimeError(
176
173
  f"The 2 byte filer header was not converted, instead read in {self.n_bytes_read} for the file header"
177
174
  )
175
+ if (
176
+ (self.packet_id == 0)
177
+ and (self.n_bytes_read != 0)
178
+ and (int(self.header["header_present"].value) == 0)
179
+ ):
180
+ raise RuntimeError(
181
+ f"The header was not converted, instead read in {self.n_bytes_read} for the file header"
182
+ )
178
183
 
179
- # packets have metadata of variable lengths, depending on if the header shows that energy_short is present in the metadata
184
+ # packets have metadata of variable lengths, depending on what the header shows
185
+ header_length = 12 # header always starts with 2-bytes of board, 2-bytes of channel, and 8-bytes of timestamp
186
+ if int(self.header["energy_channels"].value) == 1:
187
+ header_length += 2 # if the energy is recorded in ADC channels, then there are an extra 2 bytes in the metadata
188
+ if int(self.header["energy_calibrated"].value) == 1:
189
+ header_length += 8 # if the energy is recorded in keV/MeV, then there are an extra 8 bytes in the metadata
180
190
  if int(self.header["energy_short"].value) == 1:
181
- header_length = 25 # if the energy short is present, then there are an extra 2 bytes in the metadata
182
- else:
183
- header_length = 23 # the normal packet metadata is 23 bytes long
191
+ header_length += 2 # if the energy short is present, then there are an extra 2 bytes in the metadata
192
+ header_length += (
193
+ 4 + 1 + 4
194
+ ) # the flags, the waveform code bytes, and the waveform length
184
195
 
185
196
  # read the packet's metadata into the buffer
186
197
  pkt_hdr = self.buffer[:header_length]
@@ -190,16 +201,11 @@ class CompassStreamer(DataStreamer):
190
201
  # return None once we run out of file
191
202
  if n_bytes_read == 0:
192
203
  return None
193
- if (n_bytes_read != 25) and (n_bytes_read != 23):
204
+ if n_bytes_read not in [23, 25, 29, 31, 33]:
194
205
  raise RuntimeError(f"only got {n_bytes_read} bytes for packet header")
195
206
 
196
- # get the waveform length so we can read in the rest of the packet
197
- if n_bytes_read == 25:
198
- [num_samples] = np.frombuffer(pkt_hdr[21:25], dtype=np.uint32)
199
- pkt_length = header_length + 2 * num_samples
200
- if n_bytes_read == 23:
201
- [num_samples] = np.frombuffer(pkt_hdr[19:23], dtype=np.uint32)
202
- pkt_length = header_length + 2 * num_samples
207
+ [num_samples] = np.frombuffer(pkt_hdr[-4:], dtype=np.uint32)
208
+ pkt_length = header_length + 2 * num_samples
203
209
 
204
210
  # load into buffer, resizing as necessary
205
211
  if len(self.buffer) < pkt_length:
@@ -66,6 +66,7 @@ class FCConfigDecoder(DataDecoder):
66
66
  def __init__(self, *args, **kwargs) -> None:
67
67
  super().__init__(*args, **kwargs)
68
68
  self.decoded_values = copy.deepcopy(fc_config_decoded_values)
69
+ self.key_list = []
69
70
 
70
71
  def decode_packet(
71
72
  self,
@@ -126,7 +127,12 @@ class FCConfigDecoder(DataDecoder):
126
127
  ntraces = fcio.config.adcs + fcio.config.triggers
127
128
  tbl.add_field("tracemap", lgdo.Array(fcio.config.tracemap[:ntraces]))
128
129
 
130
+ self.key_list.append(f"fcid_{fcio.config.streamid & 0xFFFF}/config")
131
+
129
132
  return tbl
130
133
 
134
+ def get_key_lists(self) -> list[list[int | str]]:
135
+ return [copy.deepcopy(self.key_list)]
136
+
131
137
  def get_decoded_values(self, key: int | str = None) -> dict[str, dict[str, Any]]:
132
138
  return self.decoded_values
@@ -152,24 +152,41 @@ class FCEventDecoder(DataDecoder):
152
152
  tbl["board_id"].nda[loc] = fcio.event.card_address[ii]
153
153
  tbl["fc_input"].nda[loc] = fcio.event.card_channel[ii]
154
154
  tbl["event_type"].nda[loc] = fcio.event.type
155
- tbl["eventnumber"].nda[loc] = fcio.event.timestamp[0]
156
155
  tbl["numtraces"].nda[loc] = fcio.event.num_traces
157
- tbl["ts_pps"].nda[loc] = fcio.event.timestamp[1]
158
- tbl["ts_ticks"].nda[loc] = fcio.event.timestamp[2]
159
- tbl["ts_maxticks"].nda[loc] = fcio.event.timestamp[3]
160
- tbl["mu_offset_sec"].nda[loc] = fcio.event.timeoffset[0]
161
- tbl["mu_offset_usec"].nda[loc] = fcio.event.timeoffset[1]
162
- tbl["to_master_sec"].nda[loc] = fcio.event.timeoffset[2]
163
- tbl["delta_mu_usec"].nda[loc] = fcio.event.timeoffset[3]
164
- tbl["abs_delta_mu_usec"].nda[loc] = fcio.event.timeoffset[4]
165
- tbl["to_start_sec"].nda[loc] = fcio.event.timeoffset[5]
166
- tbl["to_start_usec"].nda[loc] = fcio.event.timeoffset[6]
167
- tbl["dr_start_pps"].nda[loc] = fcio.event.deadregion[0]
168
- tbl["dr_start_ticks"].nda[loc] = fcio.event.deadregion[1]
169
- tbl["dr_stop_pps"].nda[loc] = fcio.event.deadregion[2]
170
- tbl["dr_stop_ticks"].nda[loc] = fcio.event.deadregion[3]
171
- tbl["dr_maxticks"].nda[loc] = fcio.event.deadregion[4]
172
- # if event_type == 11: provides the same check
156
+
157
+ # the order of names is crucial here!
158
+ timestamp_names = [
159
+ "eventnumber",
160
+ "ts_pps",
161
+ "ts_ticks",
162
+ "ts_maxticks",
163
+ ]
164
+ for name, value in zip(timestamp_names, fcio.event.timestamp):
165
+ tbl[name].nda[loc] = value
166
+
167
+ timeoffset_names = [
168
+ "mu_offset_sec",
169
+ "mu_offset_usec",
170
+ "to_master_sec",
171
+ "delta_mu_usec",
172
+ "abs_delta_mu_usec",
173
+ "to_start_sec",
174
+ "to_start_usec",
175
+ ]
176
+ for name, value in zip(timeoffset_names, fcio.event.timeoffset):
177
+ tbl[name].nda[loc] = value
178
+
179
+ deadregion_names = [
180
+ "dr_start_pps",
181
+ "dr_start_ticks",
182
+ "dr_stop_pps",
183
+ "dr_stop_ticks",
184
+ "dr_maxticks",
185
+ ]
186
+ for name, value in zip(deadregion_names, fcio.event.deadregion[:5]):
187
+ tbl[name].nda[loc] = value
188
+
189
+ # if event_type == 11: would provide the same check
173
190
  # however, the usage of deadregion[5]/[6] must never change
174
191
  # and it will always be present if deadregion[7..] is ever used
175
192
  if fcio.event.deadregion_size >= 7:
@@ -179,7 +196,7 @@ class FCEventDecoder(DataDecoder):
179
196
  tbl["dr_ch_idx"].nda[loc] = 0
180
197
  tbl["dr_ch_len"].nda[loc] = fcio.config.adcs
181
198
 
182
- # The following values are calculated values by fcio-py
199
+ # The following values are calculated by fcio-py, derived from fields above.
183
200
  tbl["timestamp"].nda[loc] = fcio.event.unix_time_utc_sec
184
201
  tbl["deadinterval_nsec"].nda[loc] = fcio.event.dead_interval_nsec[ii]
185
202
  tbl["deadtime"].nda[loc] = fcio.event.dead_time_sec[ii]
@@ -14,9 +14,12 @@ log = logging.getLogger(__name__)
14
14
 
15
15
  def get_key(streamid: int, card_address: int, card_input: int, iwf: int = -1) -> int:
16
16
  if streamid > 0 or iwf < 0:
17
- # For backwards compatibility only the lower 16-bit of the streamid are
18
- # used.
19
- return (streamid & 0xFFFF) * 1000000 + card_address * 100 + card_input
17
+ # For backwards compatibility only the lower 16-bit of the streamid are used.
18
+ return (
19
+ (int(streamid) & 0xFFFF) * 1000000
20
+ + int(card_address) * 100
21
+ + int(card_input)
22
+ )
20
23
  else:
21
24
  return iwf
22
25
 
@@ -212,7 +215,7 @@ class FCEventHeaderDecoder(DataDecoder):
212
215
  self.decoded_values["baseline"]["length_guess"] = n_traces
213
216
  self.decoded_values["daqenergy"]["length_guess"] = n_traces
214
217
 
215
- self.key_list = [get_key(fcio_stream.config.streamid, 0, 0)]
218
+ self.key_list = [f"fcid_{fcio_stream.config.streamid & 0xFFFF}/evt_hdr"]
216
219
 
217
220
  def get_key_lists(self) -> list[list[int | str]]:
218
221
  return [copy.deepcopy(self.key_list)]
@@ -232,7 +235,7 @@ class FCEventHeaderDecoder(DataDecoder):
232
235
  ) -> bool:
233
236
 
234
237
  # only one key available: this streamid
235
- key = get_key(fcio.config.streamid, 0, 0)
238
+ key = f"fcid_{fcio.config.streamid & 0xFFFF}/evt_hdr"
236
239
  if key not in evt_hdr_rbkd:
237
240
  return False
238
241
 
@@ -8,7 +8,6 @@ import numpy as np
8
8
  from fcio import FCIO, Limits
9
9
 
10
10
  from ..data_decoder import DataDecoder
11
- from .fc_eventheader_decoder import get_key
12
11
 
13
12
  fsp_config_decoded_values = {
14
13
  "packet_id": {
@@ -183,7 +182,7 @@ class FSPConfigDecoder(DataDecoder):
183
182
  self.key_list = []
184
183
 
185
184
  def set_fcio_stream(self, fcio_stream: FCIO) -> None:
186
- self.key_list = [f"fsp_config_{get_key(fcio_stream.config.streamid, 0, 0)}"]
185
+ self.key_list = [f"swtid_{fcio_stream.config.streamid & 0xFFFF}/config"]
187
186
  if fcio_stream.fsp is not None:
188
187
  wps_n_traces = fcio_stream.fsp.config.wps["tracemap"]["n_mapped"]
189
188
  hwm_n_traces = fcio_stream.fsp.config.hwm["tracemap"]["n_mapped"]
@@ -229,6 +228,9 @@ class FSPConfigDecoder(DataDecoder):
229
228
  def get_decoded_values(self, key: int = None) -> dict[str, dict[str, Any]]:
230
229
  return self.decoded_values
231
230
 
231
+ def get_key_lists(self) -> list[list[int | str]]:
232
+ return [copy.deepcopy(self.key_list)]
233
+
232
234
  def decode_packet(
233
235
  self,
234
236
  fcio: FCIO,
@@ -659,6 +661,9 @@ class FSPConfigDecoder(DataDecoder):
659
661
  "dsp_ct_thresholds",
660
662
  lgdo.Array(np.array(ct["thresholds"], dtype="uint16")[:ct_n_traces]),
661
663
  )
664
+
665
+ self.key_list.append(f"swtid_{fcio.config.streamid & 0xFFFF}/config")
666
+
662
667
  return self.fsp_config
663
668
 
664
669
  def make_lgdo(self, key: int | str = None, size: int = None) -> lgdo.Struct:
@@ -694,7 +699,7 @@ class FSPStatusDecoder(DataDecoder):
694
699
  self.key_list = []
695
700
 
696
701
  def set_fcio_stream(self, fcio_stream: FCIO) -> None:
697
- self.key_list = [f"fsp_status_{get_key(fcio_stream.config.streamid, 0, 0)}"]
702
+ self.key_list = [f"swtid_{fcio_stream.config.streamid & 0xFFFF}/status"]
698
703
 
699
704
  def get_key_lists(self) -> list[list[int | str]]:
700
705
  return [copy.deepcopy(self.key_list)]
@@ -709,7 +714,7 @@ class FSPStatusDecoder(DataDecoder):
709
714
  packet_id: int,
710
715
  ) -> bool:
711
716
 
712
- key = f"fsp_status_{get_key(fcio.config.streamid, 0, 0)}"
717
+ key = f"swtid_{fcio.config.streamid & 0xFFFF}/status"
713
718
  if key not in fsp_status_rbkd:
714
719
  return False
715
720
  fsp_status_rb = fsp_status_rbkd[key]
@@ -796,8 +801,8 @@ class FSPEventDecoder(DataDecoder):
796
801
  def set_fcio_stream(self, fcio_stream: FCIO) -> None:
797
802
 
798
803
  self.key_list = [
799
- f"fsp_event_{get_key(fcio_stream.config.streamid, 0, 0)}",
800
- f"fsp_eventheader_{get_key(fcio_stream.config.streamid, 0, 0)}",
804
+ f"swtid_{fcio_stream.config.streamid & 0xFFFF}/event",
805
+ f"swtid_{fcio_stream.config.streamid & 0xFFFF}/evt_hdr",
801
806
  ]
802
807
 
803
808
  def get_decoded_values(self, key: int = None) -> dict[str, dict[str, Any]]:
@@ -815,9 +820,9 @@ class FSPEventDecoder(DataDecoder):
815
820
  ) -> bool:
816
821
 
817
822
  if is_header:
818
- key = f"fsp_eventheader_{get_key(fcio.config.streamid, 0, 0)}"
823
+ key = f"swtid_{fcio.config.streamid & 0xFFFF}/evt_hdr"
819
824
  else:
820
- key = f"fsp_event_{get_key(fcio.config.streamid, 0, 0)}"
825
+ key = f"swtid_{fcio.config.streamid & 0xFFFF}/event"
821
826
 
822
827
  if key not in fsp_evt_rbkd:
823
828
  return False
@@ -877,7 +882,7 @@ class FSPEventDecoder(DataDecoder):
877
882
  if lens > 0:
878
883
  tbl["obs_ps_hwm_prescaled_trace_idx"].flattened_data.nda[
879
884
  start:end
880
- ] = fcio.fsp.event.obs_ps_hwm_prescaled_trace_ix
885
+ ] = fcio.fsp.event.obs_ps_hwm_prescaled_trace_idx
881
886
  tbl["obs_ps_hwm_prescaled_trace_idx"].cumulative_length[loc] = end
882
887
 
883
888
  fsp_evt_rb.loc += 1
@@ -122,7 +122,7 @@ fc_status_decoded_values = {
122
122
 
123
123
 
124
124
  def get_key(streamid, reqid):
125
- return (streamid & 0xFFFF) * 1000000 + (reqid & 0xFFFF)
125
+ return f"fcid_{streamid & 0xFFFF}/status/card{(reqid & 0xFFFF)}"
126
126
 
127
127
 
128
128
  def get_fcid(key: int) -> int:
daq2lh5/fc/fc_streamer.py CHANGED
@@ -148,7 +148,8 @@ class FCStreamer(DataStreamer):
148
148
  if len(config_rb_list) != 1:
149
149
  log.warning(
150
150
  f"config_rb_list for {config_decoder} had length {len(config_rb_list)}, "
151
- "ignoring all but the first"
151
+ "ignoring all but the first. "
152
+ f"{config_rb_list}"
152
153
  )
153
154
  rb = config_rb_list[0]
154
155
  else:
daq2lh5/orca/orca_fcio.py CHANGED
@@ -15,7 +15,7 @@ from daq2lh5.fc.fc_fsp_decoder import (
15
15
  FSPStatusDecoder,
16
16
  )
17
17
  from daq2lh5.fc.fc_status_decoder import FCStatusDecoder
18
- from daq2lh5.fc.fc_status_decoder import get_key as get_status_key
18
+ from daq2lh5.fc.fc_status_decoder import get_key as get_fc_status_key
19
19
 
20
20
  from ..raw_buffer import RawBufferList
21
21
  from .orca_base import OrcaDecoder
@@ -126,23 +126,30 @@ class ORFCIOConfigDecoder(OrcaDecoder):
126
126
  def set_header(self, header: OrcaHeader) -> None:
127
127
  self.header = header
128
128
  self.fc_hdr_info = extract_header_information(header)
129
- self.decoded_values = copy.deepcopy(self.decoder.get_decoded_values())
129
+ self.decoded_values["fcid"] = copy.deepcopy(self.decoder.get_decoded_values())
130
130
 
131
131
  for fcid in self.fc_hdr_info["fsp_enabled"]:
132
- key = get_key(fcid, 0, 0)
133
- self.key_list["fc_config"].append(key)
132
+ self.key_list["fc_config"].append(f"fcid_{fcid}/config")
134
133
  if self.fc_hdr_info["fsp_enabled"][fcid]:
135
134
  self.fsp_decoder = FSPConfigDecoder()
136
- self.key_list["fsp_config"].append(f"fsp_config_{key}")
135
+ self.key_list["fsp_config"].append(f"swtid_{fcid}/config")
136
+ self.decoded_values["swtid"] = copy.deepcopy(
137
+ self.fsp_decoder.get_decoded_values()
138
+ )
137
139
  self.max_rows_in_packet = 1
138
140
 
139
- def get_key_lists(self) -> list[list[int, str]]:
141
+ def get_key_lists(self) -> list[list[str]]:
140
142
  return list(self.key_list.values())
141
143
 
142
- def get_decoded_values(self, key: int | str = None) -> dict[str, Any]:
143
- if isinstance(key, str) and key.startswith("fsp_config"):
144
- return copy.deepcopy(self.fsp_decoder.get_decoded_values())
145
- return self.decoded_values
144
+ def get_decoded_values(self, key: str = None) -> dict[str, Any]:
145
+ if (
146
+ isinstance(key, str)
147
+ and key.startswith("swtid_")
148
+ and self.fsp_decoder is not None
149
+ ):
150
+ return self.decoded_values["swtid"]
151
+ elif (isinstance(key, str) and key.startswith("fcid_")) or key is None:
152
+ return self.decoded_values["fcid"]
146
153
  raise KeyError(f"no decoded values for key {key}")
147
154
 
148
155
  def decode_packet(
@@ -159,18 +166,21 @@ class ORFCIOConfigDecoder(OrcaDecoder):
159
166
 
160
167
  if fcio_stream.config.streamid != packet[2]:
161
168
  log.warning(
162
- f"The expected stream id {packet[2]} does not match the contained stream id {fcio_stream.config.streamid}"
169
+ f"The expected stream id {packet[2]} does not match the contained stream id "
170
+ f"{fcio_stream.config.streamid}"
163
171
  )
164
172
 
165
173
  config_rbkd = rbl.get_keyed_dict()
166
174
 
175
+ fcid = fcio_stream.config.streamid & 0xFFFF
176
+
167
177
  # TODO: the decoders could fetch lgdo's using it's key_list
168
- fc_key = get_key(fcio_stream.config.streamid, 0, 0)
178
+ fc_key = f"fcid_{fcid}/config"
169
179
  any_full = self.decoder.decode_packet(
170
180
  fcio_stream, config_rbkd[fc_key], packet_id
171
181
  )
172
- if self.fsp_decoder is not None:
173
- fsp_key = f"fsp_config_{get_key(fcio_stream.config.streamid, 0, 0)}"
182
+ if self.fsp_decoder is not None and self.fc_hdr_info["fsp_enabled"][fcid]:
183
+ fsp_key = f"swtid_{fcid}/config"
174
184
  any_full |= self.fsp_decoder.decode_packet(
175
185
  fcio_stream, config_rbkd[fsp_key], packet_id
176
186
  )
@@ -184,7 +194,7 @@ class ORFCIOStatusDecoder(OrcaDecoder):
184
194
  self.decoder = FCStatusDecoder()
185
195
  self.fsp_decoder = None
186
196
  self.decoded_values = {}
187
- self.key_list = {"fc_status": [], "fsp_status": []}
197
+ self.key_list = []
188
198
  self.max_rows_in_packet = 0
189
199
  super().__init__(header=header, **kwargs)
190
200
 
@@ -192,44 +202,43 @@ class ORFCIOStatusDecoder(OrcaDecoder):
192
202
  """Setter for headers. Overload to set card parameters, etc."""
193
203
  self.header = header
194
204
  self.fc_hdr_info = extract_header_information(header)
195
- self.decoded_values = copy.deepcopy(self.decoder.get_decoded_values())
196
205
 
197
206
  for fcid in self.fc_hdr_info["n_card"]:
198
207
  # If the data was taken without a master distribution module,
199
208
  # i.e. only one ADC Module the decoder will just not write to the buffer.
200
209
 
201
210
  # MDB key
202
- self.key_list["fc_status"] = [get_status_key(fcid, 0)]
211
+ key_list_fcid = [get_fc_status_key(fcid, 0)]
203
212
  # ADC module keys
204
- self.key_list["fc_status"] += [
205
- get_status_key(fcid, 0x2000 + i)
213
+ key_list_fcid += [
214
+ get_fc_status_key(fcid, 0x2000 + i)
206
215
  for i in range(self.fc_hdr_info["n_card"][fcid])
207
216
  ]
208
-
217
+ self.key_list.append(key_list_fcid)
218
+ self.decoded_values["fcid"] = copy.deepcopy(
219
+ self.decoder.get_decoded_values()
220
+ )
209
221
  if self.fc_hdr_info["fsp_enabled"][fcid]:
210
- key = get_key(fcid, 0, 0)
211
- self.key_list["fsp_status"].append(f"fsp_status_{key}")
212
222
  self.fsp_decoder = FSPStatusDecoder()
223
+ self.key_list.append([f"swtid_{fcid}/status"])
224
+ self.decoded_values["swtid"] = copy.deepcopy(
225
+ self.fsp_decoder.get_decoded_values()
226
+ )
213
227
  self.max_rows_in_packet = max(self.fc_hdr_info["n_card"].values()) + 1
214
228
 
215
- def get_key_lists(self) -> list[list[int | str]]:
216
- return list(self.key_list.values())
229
+ def get_key_lists(self) -> list[list[str]]:
230
+ return copy.deepcopy(self.key_list)
217
231
 
218
- def get_decoded_values(self, key: int | str = None) -> dict[str, Any]:
219
- if key is None:
220
- dec_vals_list = list(self.decoded_values)
221
- if len(dec_vals_list) > 0:
222
- return {dec_vals_list[0]: self.decoded_values[dec_vals_list[0]]}
223
- raise RuntimeError("decoded_values not built")
232
+ def get_decoded_values(self, key: str = None) -> dict[str, Any]:
224
233
 
225
234
  if (
226
235
  isinstance(key, str)
227
- and key.startswith("fsp_status")
236
+ and key.startswith("swtid_")
228
237
  and self.fsp_decoder is not None
229
238
  ):
230
- return copy.deepcopy(self.fsp_decoder.get_decoded_values())
231
- elif isinstance(key, int):
232
- return copy.deepcopy(self.decoder.get_decoded_values())
239
+ return self.decoded_values["swtid"]
240
+ elif (isinstance(key, str) and key.startswith("fcid_")) or key is None:
241
+ return self.decoded_values["fcid"]
233
242
  else:
234
243
  raise KeyError(f"no decoded values for key {key}")
235
244
 
@@ -245,15 +254,25 @@ class ORFCIOStatusDecoder(OrcaDecoder):
245
254
  fcio_stream.set_mem_field(memoryview(packet[3:]))
246
255
 
247
256
  any_full = False
248
- while fcio_stream.get_record():
249
- if fcio_stream.tag == Tags.Status:
250
- any_full |= self.decoder.decode_packet(
257
+
258
+ if not fcio_stream.get_record():
259
+ raise OSError(
260
+ f"Missing record in FCIO stream {fcio_stream.config.streamid & 0xFFFF}."
261
+ )
262
+
263
+ if fcio_stream.tag == Tags.FSPStatus:
264
+ if self.fsp_decoder is not None:
265
+ any_full |= self.fsp_decoder.decode_packet(
251
266
  fcio_stream, status_rbkd, packet_id
252
267
  )
253
- if self.fsp_decoder is not None:
254
- any_full |= self.fsp_decoder.decode_packet(
255
- fcio_stream, status_rbkd, packet_id
256
- )
268
+
269
+ if not fcio_stream.get_record():
270
+ raise OSError(
271
+ f"Missing record in FCIO stream {fcio_stream.config.streamid & 0xFFFF}."
272
+ )
273
+
274
+ if fcio_stream.tag == Tags.Status:
275
+ any_full |= self.decoder.decode_packet(fcio_stream, status_rbkd, packet_id)
257
276
 
258
277
  return bool(any_full)
259
278
 
@@ -276,35 +295,31 @@ class ORFCIOEventHeaderDecoder(OrcaDecoder):
276
295
 
277
296
  key_list = self.fc_hdr_info["key_list"]
278
297
  for fcid in key_list:
279
- key = get_key(fcid, 0, 0)
280
- self.key_list["fc_eventheader"].append(key)
281
- self.decoded_values[fcid] = copy.deepcopy(self.decoder.get_decoded_values())
298
+ self.key_list["fc_eventheader"].append(f"fcid_{fcid}/evt_hdr")
299
+ self.decoded_values["fcid"] = copy.deepcopy(
300
+ self.decoder.get_decoded_values()
301
+ )
282
302
  if self.fc_hdr_info["fsp_enabled"][fcid]:
283
303
  self.fsp_decoder = FSPEventDecoder()
284
- self.key_list["fsp_eventheader"].append(f"fsp_eventheader_{key}")
304
+ self.key_list["fsp_eventheader"].append(f"swtid_{fcid}/evt_hdr")
305
+ self.decoded_values["swtid"] = copy.deepcopy(
306
+ self.fsp_decoder.get_decoded_values()
307
+ )
285
308
 
286
309
  self.max_rows_in_packet = 1
287
310
 
288
- def get_key_lists(self) -> list[list[int | str]]:
311
+ def get_key_lists(self) -> list[list[str]]:
289
312
  return list(self.key_list.values())
290
313
 
291
- def get_decoded_values(self, key: int | str = None) -> dict[str, Any]:
314
+ def get_decoded_values(self, key: str = None) -> dict[str, Any]:
292
315
  if (
293
316
  isinstance(key, str)
294
- and key.startswith("fsp_eventheader_")
317
+ and key.startswith("swtid_")
295
318
  and self.fsp_decoder is not None
296
319
  ):
297
- return copy.deepcopy(self.fsp_decoder.get_decoded_values())
298
- elif isinstance(key, int):
299
- fcid = get_fcid(key)
300
- if fcid in self.decoded_values:
301
- return self.decoded_values[fcid]
302
- elif key is None and self.fsp_decoder is None:
303
- dec_vals_list = list(self.decoded_values.values())
304
- if len(dec_vals_list) > 0:
305
- return dec_vals_list[0]
306
- raise RuntimeError("decoded_values not built")
307
-
320
+ return self.decoded_values["swtid"]
321
+ elif (isinstance(key, str) and key.startswith("fcid_")) or key is None:
322
+ return self.decoded_values["fcid"]
308
323
  raise KeyError(f"no decoded values for key {key}")
309
324
 
310
325
  def decode_packet(
@@ -315,15 +330,25 @@ class ORFCIOEventHeaderDecoder(OrcaDecoder):
315
330
  fcio_stream.set_mem_field(memoryview(packet[3:]))
316
331
 
317
332
  any_full = False
318
- while fcio_stream.get_record():
319
- if fcio_stream.tag == Tags.EventHeader:
320
- any_full |= self.decoder.decode_packet(
321
- fcio_stream, evthdr_rbkd, packet_id
333
+
334
+ if not fcio_stream.get_record():
335
+ raise OSError(
336
+ f"Missing record in FCIO stream {fcio_stream.config.streamid & 0xFFFF}."
337
+ )
338
+
339
+ if fcio_stream.tag == Tags.FSPEvent:
340
+ if self.fsp_decoder is not None:
341
+ any_full |= self.fsp_decoder.decode_packet(
342
+ fcio_stream, evthdr_rbkd, packet_id, is_header=True
322
343
  )
323
- if self.fsp_decoder is not None:
324
- any_full |= self.fsp_decoder.decode_packet(
325
- fcio_stream, evthdr_rbkd, packet_id, True
326
- )
344
+
345
+ if not fcio_stream.get_record():
346
+ raise OSError(
347
+ f"Missing record in FCIO stream {fcio_stream.config.streamid & 0xFFFF}."
348
+ )
349
+
350
+ if fcio_stream.tag == Tags.EventHeader:
351
+ any_full |= self.decoder.decode_packet(fcio_stream, evthdr_rbkd, packet_id)
327
352
 
328
353
  return bool(any_full)
329
354
 
@@ -335,7 +360,7 @@ class ORFCIOEventDecoder(OrcaDecoder):
335
360
  self.decoder = FCEventDecoder()
336
361
  self.fsp_decoder = None
337
362
 
338
- self.key_list = {"event": [], "fsp_event": []}
363
+ self.key_list = []
339
364
  self.decoded_values = {}
340
365
  self.max_rows_in_packet = 0
341
366
 
@@ -347,24 +372,26 @@ class ORFCIOEventDecoder(OrcaDecoder):
347
372
  self.fc_hdr_info = extract_header_information(header)
348
373
  key_list = self.fc_hdr_info["key_list"]
349
374
  for fcid in key_list:
350
- self.key_list["event"] += key_list[fcid]
375
+ self.key_list.append(key_list[fcid])
351
376
  self.decoded_values[fcid] = copy.deepcopy(self.decoder.get_decoded_values())
352
377
  self.decoded_values[fcid]["waveform"]["wf_len"] = self.fc_hdr_info[
353
378
  "wf_len"
354
379
  ][fcid]
355
380
  if self.fc_hdr_info["fsp_enabled"][fcid]:
356
- key = get_key(fcid, 0, 0)
357
- self.key_list["fsp_event"].append(f"fsp_event_{key}")
358
381
  self.fsp_decoder = FSPEventDecoder()
382
+ self.key_list.append([f"swtid_{fcid}/event"])
383
+ self.decoded_values["swtid"] = copy.deepcopy(
384
+ self.fsp_decoder.get_decoded_values()
385
+ )
359
386
  self.max_rows_in_packet = max(self.fc_hdr_info["n_adc"].values())
360
387
 
361
- def get_key_lists(self) -> list[list[int]]:
362
- return list(self.key_list.values())
388
+ def get_key_lists(self) -> list[list[int | str]]:
389
+ return copy.deepcopy(self.key_list)
363
390
 
364
391
  def get_max_rows_in_packet(self) -> int:
365
392
  return self.max_rows_in_packet
366
393
 
367
- def get_decoded_values(self, key: int = None) -> dict[str, Any]:
394
+ def get_decoded_values(self, key: int | str = None) -> dict[str, Any]:
368
395
  if key is None:
369
396
  dec_vals_list = list(self.decoded_values.values())
370
397
  if len(dec_vals_list) > 0:
@@ -373,10 +400,10 @@ class ORFCIOEventDecoder(OrcaDecoder):
373
400
 
374
401
  if (
375
402
  isinstance(key, str)
376
- and key.startswith("fsp_event_")
403
+ and key.startswith("swtid_")
377
404
  and self.fsp_decoder is not None
378
405
  ):
379
- return copy.deepcopy(self.fsp_decoder.get_decoded_values())
406
+ return self.decoded_values["swtid"]
380
407
  elif isinstance(key, int):
381
408
  fcid = get_fcid(key)
382
409
  if fcid in self.decoded_values:
@@ -394,12 +421,24 @@ class ORFCIOEventDecoder(OrcaDecoder):
394
421
  fcio_stream.set_mem_field(memoryview(packet[3:]))
395
422
 
396
423
  any_full = False
397
- while fcio_stream.get_record():
398
- if fcio_stream.tag == Tags.Event or fcio_stream.tag == Tags.SparseEvent:
399
- any_full |= self.decoder.decode_packet(fcio_stream, evt_rbkd, packet_id)
400
- if self.fsp_decoder is not None:
401
- any_full |= self.fsp_decoder.decode_packet(
402
- fcio_stream, evt_rbkd, packet_id, False
403
- )
424
+
425
+ if not fcio_stream.get_record():
426
+ raise OSError(
427
+ f"Missing record in FCIO stream {fcio_stream.config.streamid & 0xFFFF}."
428
+ )
429
+
430
+ if fcio_stream.tag == Tags.FSPEvent:
431
+ if self.fsp_decoder is not None:
432
+ any_full |= self.fsp_decoder.decode_packet(
433
+ fcio_stream, evt_rbkd, packet_id
434
+ )
435
+
436
+ if not fcio_stream.get_record():
437
+ raise OSError(
438
+ f"Missing record in FCIO stream {fcio_stream.config.streamid & 0xFFFF}."
439
+ )
440
+
441
+ if fcio_stream.tag == Tags.Event or fcio_stream.tag == Tags.SparseEvent:
442
+ any_full |= self.decoder.decode_packet(fcio_stream, evt_rbkd, packet_id)
404
443
 
405
444
  return bool(any_full)
@@ -8,7 +8,7 @@ import logging
8
8
  import numpy as np
9
9
 
10
10
  from ..data_streamer import DataStreamer
11
- from ..raw_buffer import RawBuffer, RawBufferLibrary
11
+ from ..raw_buffer import RawBuffer, RawBufferLibrary, RawBufferList
12
12
  from . import orca_packet
13
13
  from .orca_base import OrcaDecoder
14
14
  from .orca_digitizers import ( # noqa: F401
@@ -351,6 +351,18 @@ class OrcaStreamer(DataStreamer):
351
351
  if rb_lib is not None and "*" not in rb_lib:
352
352
  keep_decoders = []
353
353
  for name in decoder_names:
354
+ # Decoding ORFCIO streams requires decoding ORFCIOConfig packets,
355
+ # as it opens the wrapped fcio stream (in orca_fcio.py) and decodes the fields
356
+ # required for the other FCIO packets.
357
+ # With `out_stream == ''` the lgdo buffer will be allocated, and the packet
358
+ # decoded, but not written to the out_stream the user defined.
359
+ if name == "ORFCIOConfigDecoder" and name not in rb_lib:
360
+ rb_lib[name] = RawBufferList()
361
+ rb_lib[name].append(
362
+ RawBuffer(
363
+ lgdo=None, key_list=["*"], out_name="{key}", out_stream=""
364
+ )
365
+ )
354
366
  if name in rb_lib:
355
367
  keep_decoders.append(name)
356
368
  decoder_names = keep_decoders
@@ -366,8 +378,7 @@ class OrcaStreamer(DataStreamer):
366
378
  shift_data_id=False
367
379
  )
368
380
  instantiated_decoders = {"OrcaHeaderDecoder": self.header_decoder}
369
- for data_id in id_to_dec_name_dict.keys():
370
- name = id_to_dec_name_dict[data_id]
381
+ for data_id, name in id_to_dec_name_dict.items():
371
382
  if name not in instantiated_decoders:
372
383
  if name not in globals():
373
384
  self.missing_decoders.append(data_id)
@@ -384,8 +395,6 @@ class OrcaStreamer(DataStreamer):
384
395
  chunk_mode=chunk_mode,
385
396
  out_stream=out_stream,
386
397
  )
387
- if rb_lib is None:
388
- rb_lib = self.rb_lib
389
398
  good_buffers = []
390
399
  for data_id in self.decoder_id_dict.keys():
391
400
  name = id_to_dec_name_dict[data_id]
@@ -401,8 +410,8 @@ class OrcaStreamer(DataStreamer):
401
410
  log.debug(f"rb_lib = {self.rb_lib}")
402
411
 
403
412
  # return header raw buffer
404
- if "OrcaHeaderDecoder" in rb_lib:
405
- header_rb_list = rb_lib["OrcaHeaderDecoder"]
413
+ if "OrcaHeaderDecoder" in self.rb_lib:
414
+ header_rb_list = self.rb_lib["OrcaHeaderDecoder"]
406
415
  if len(header_rb_list) != 1:
407
416
  log.warning(
408
417
  f"header_rb_list had length {len(header_rb_list)}, ignoring all but the first"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: legend-daq2lh5
3
- Version: 1.6.1
3
+ Version: 1.6.3
4
4
  Summary: Convert digitizer data to LH5
5
5
  Author-email: Jason Detwiler <jasondet@uw.edu>
6
6
  Maintainer: The LEGEND collaboration
@@ -23,7 +23,7 @@ Requires-Python: >=3.9
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: dspeed>=1.3
26
- Requires-Dist: fcio>=0.7.8
26
+ Requires-Dist: fcio>=0.7.9
27
27
  Requires-Dist: h5py>=3.2.0
28
28
  Requires-Dist: hdf5plugin
29
29
  Requires-Dist: legend-pydataobj>=1.6
@@ -1,7 +1,7 @@
1
1
  daq2lh5/__init__.py,sha256=VPmwKuZSA0icpce05ojhnsKWhR4_QUgD0oVXUoN9wks,975
2
- daq2lh5/_version.py,sha256=vFpyllSQPpQuwzP7Cf743S6HuWgZRMOD31pPfh-WoZM,511
2
+ daq2lh5/_version.py,sha256=RrM0JkMSEquWJIGj8RCqoST__VpnXFzYf3k-uS1FCko,511
3
3
  daq2lh5/build_raw.py,sha256=wehR1jADL_ponguOMfAsMTxsebE-qdztFw4Txm76fpw,10716
4
- daq2lh5/cli.py,sha256=7bPfH1XbyAS48wZn_0unj4Y5MD5kF7V34Q5srn4jKVM,2913
4
+ daq2lh5/cli.py,sha256=yCLshhXZdXpeaVElqLYwGHR1uN7fcPDRnsrFQgGdwYM,3210
5
5
  daq2lh5/data_decoder.py,sha256=45ckhpfqKh6_FfPUn_iETZLjRFd4VtvQhet4l2KvldA,10606
6
6
  daq2lh5/data_streamer.py,sha256=-uns1Y9iReVSJTXGCXD05QQLfqjbQn-VodEmDj7uSJo,16016
7
7
  daq2lh5/logging.py,sha256=rYNeToaZBTCaIiC42a4CUroAo1PCOreTXbpEZyMO8Fo,987
@@ -12,16 +12,16 @@ daq2lh5/buffer_processor/buffer_processor.py,sha256=mG4kJXt8V9Jji_UTBfllRffIVqLP
12
12
  daq2lh5/buffer_processor/lh5_buffer_processor.py,sha256=dKND-18TTPDZH8VxdEaQNZPK7w_G-dC4fsBns1NIjMY,8409
13
13
  daq2lh5/compass/__init__.py,sha256=mOXHWp7kRDgNTPQty3E8k2KPSy_vAzjneKfAcCVaPyE,132
14
14
  daq2lh5/compass/compass_config_parser.py,sha256=zeAsOo1dOJPGLL8-zkAcdYRkqt8BodtOPi96n7fWsl4,12300
15
- daq2lh5/compass/compass_event_decoder.py,sha256=kiPOaEu8SgLD2wbSPbBahcbTBBRAIw35wtVLBcwPcXY,7386
16
- daq2lh5/compass/compass_header_decoder.py,sha256=AA-Md2FIT3nD4mXX9CrWvbbfmKiA436-BTmzcU3_XOY,2823
17
- daq2lh5/compass/compass_streamer.py,sha256=zSl7IqO0ID0wcixkLE9QVEG3bF9hfGVITVPomCeOFTM,8841
15
+ daq2lh5/compass/compass_event_decoder.py,sha256=hNEz4q45vDxsC6JnChW55rCsYaYtGaxZ4gXYrIEK8Iw,7463
16
+ daq2lh5/compass/compass_header_decoder.py,sha256=e2uVnx6o36K-BzPTTpl2ot8r4rwZp-qLniTXee3q2aI,10251
17
+ daq2lh5/compass/compass_streamer.py,sha256=JkR5HvX9_Vl0RgiTS26A4Ae0EelmrpbgG-GihDhUJ3w,9186
18
18
  daq2lh5/fc/__init__.py,sha256=bB1j6r-bDmylNi0iutQeAJGjsDSjLSoXMqFfXWwfb8I,141
19
- daq2lh5/fc/fc_config_decoder.py,sha256=Mlll1SI4HPmTo6EGubkyP-QcmEfmO-kWaiZCKDc4cxg,4448
20
- daq2lh5/fc/fc_event_decoder.py,sha256=c2CQSiNxlOEGOZNSgqM3eM-26EUW6MIfK48Am9CHfro,7887
21
- daq2lh5/fc/fc_eventheader_decoder.py,sha256=jAUxgWqgQmzNhAzMLubgFLMiGpxjqVIut6uhVS6JfyA,12313
22
- daq2lh5/fc/fc_fsp_decoder.py,sha256=-qQjDkXF7xjF3lYIp2085TY2hRrLwxzyXwQYAn1OAJw,34972
23
- daq2lh5/fc/fc_status_decoder.py,sha256=ChMxALx36caY7zfCOKozNgXYhLGX9HzBnkyOvQfiABk,8809
24
- daq2lh5/fc/fc_streamer.py,sha256=U1EPKr-xSlxTCqcgQL6SFlO11WDFT9kcna7gocDKLXo,9162
19
+ daq2lh5/fc/fc_config_decoder.py,sha256=f-WqmvfDDDnx4ho2q4w5Hmzu55yvweHsi1hgM2X4RbM,4654
20
+ daq2lh5/fc/fc_event_decoder.py,sha256=_g-bznhBHk8v-VSHYHJa5LlJYP69gWZ0LNXslYgEAlw,7897
21
+ daq2lh5/fc/fc_eventheader_decoder.py,sha256=m9XLpLl52Set1ITpCLp9_0L6wS6AaAN8uDUdDdqYG5Y,12390
22
+ daq2lh5/fc/fc_fsp_decoder.py,sha256=sCv3YfZ0QnN7LAS2msYo9XbMeEIFuKmyLp3SiB4fQ9M,35074
23
+ daq2lh5/fc/fc_status_decoder.py,sha256=o3DjpXtx3VIk3hWrodWopYczfXiJr-ekSUx_HqW2cyc,8818
24
+ daq2lh5/fc/fc_streamer.py,sha256=iJSd2OiOYAjFtBg-Yr9D64_kp3xO_9Jt5XpO9q0sFpE,9208
25
25
  daq2lh5/llama/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  daq2lh5/llama/llama_base.py,sha256=B-NCBjE_FQE1WxijWi4Z1XBy4rqLHm2XhkC40s7Sdms,290
27
27
  daq2lh5/llama/llama_event_decoder.py,sha256=DVULTX7JzlRe23HZYXn79VkoU2_z03o7nxWc33bGK8U,14981
@@ -30,17 +30,17 @@ daq2lh5/llama/llama_streamer.py,sha256=lrK73JIXjpogPrkP_tpFQQGSyWxFhivSpU1YEfRMH
30
30
  daq2lh5/orca/__init__.py,sha256=Xf6uOIOzk_QkKH_7VizGlCo3iuiAgLtUE3A07x_HXC0,175
31
31
  daq2lh5/orca/orca_base.py,sha256=-XIolXsHj-1EdewaGxyvJTZvRGZsDyZe-5PzVOd-LFY,1333
32
32
  daq2lh5/orca/orca_digitizers.py,sha256=Vu05xQO3XYqybjLhzk-XJG41wyeVFSIoXOjvdltDoUw,20865
33
- daq2lh5/orca/orca_fcio.py,sha256=v2kZWUmutd1HcWJW0h50zsVdAZorP8TG39PP9tf_GCg,15107
33
+ daq2lh5/orca/orca_fcio.py,sha256=WDaWp4JkUDOUvrMCxKWI-kXXrZNQNAakDCdNyFaqL3k,16091
34
34
  daq2lh5/orca/orca_flashcam.py,sha256=klGWxME2QS3eOBgb5mD3IrPHXXzb1Zz4B7YDvZvaGD8,33291
35
35
  daq2lh5/orca/orca_header.py,sha256=2WlB8rFXwzD89lX51JXYiOlop0-C4pWEEsIWKq2ouDM,4403
36
36
  daq2lh5/orca/orca_header_decoder.py,sha256=ORIIyfx22ybyKc-uyWy5ER49-dl3BGpHdfV8OCDmjIw,1632
37
37
  daq2lh5/orca/orca_packet.py,sha256=LtKEAcH2VGzY8AQEqAokZTHonShACsKisGdQR0AMDAM,2835
38
38
  daq2lh5/orca/orca_run_decoder.py,sha256=61gghgjqD1ovH3KoHFJuKJp0GLJn4X0MIuoYrsIzMfQ,2059
39
- daq2lh5/orca/orca_streamer.py,sha256=sLM5PPLOxJ_aJckJOl9UCjyOaPmNxUKDnrLw22fSNbE,16622
39
+ daq2lh5/orca/orca_streamer.py,sha256=a9iy1FfbN1niX4gcJQpPCwiv1fKLLEZMcaGs2lOnfFw,17284
40
40
  daq2lh5/orca/skim_orca_file.py,sha256=ESWfbv9yDRGaPf3Ptlw6Dxnc4uwhJoagb_aYlWNyrq0,1954
41
- legend_daq2lh5-1.6.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
42
- legend_daq2lh5-1.6.1.dist-info/METADATA,sha256=UbfgLNJWCAl6CquW11JoohphH6-rre7kC4UjGcH-5Hs,4005
43
- legend_daq2lh5-1.6.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
44
- legend_daq2lh5-1.6.1.dist-info/entry_points.txt,sha256=RPm2GPw2YADil9tWgvZsIysmJz9KuktBWH_1P38PWoc,119
45
- legend_daq2lh5-1.6.1.dist-info/top_level.txt,sha256=MJQVLyLqMgMKBdVfNXFaCKCjHKakAs19VLbC9ctXZ7A,8
46
- legend_daq2lh5-1.6.1.dist-info/RECORD,,
41
+ legend_daq2lh5-1.6.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
42
+ legend_daq2lh5-1.6.3.dist-info/METADATA,sha256=NaUXE8unZmqIsT_akldDSofULeVX3L6TsQV4b9XIQfc,4005
43
+ legend_daq2lh5-1.6.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
44
+ legend_daq2lh5-1.6.3.dist-info/entry_points.txt,sha256=RPm2GPw2YADil9tWgvZsIysmJz9KuktBWH_1P38PWoc,119
45
+ legend_daq2lh5-1.6.3.dist-info/top_level.txt,sha256=MJQVLyLqMgMKBdVfNXFaCKCjHKakAs19VLbC9ctXZ7A,8
46
+ legend_daq2lh5-1.6.3.dist-info/RECORD,,