DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
hdf5_dump.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
3import daqdataformats
4import detdataformats
5
6import argparse
7import datetime
8import h5py
9import struct
10import os
11import sys
12
13# Current allowed range of file layout versions
14FILELAYOUT_MIN_VERSION = 4
15FILELAYOUT_MAX_VERSION = 7
16
17# Current header versions
18TRIGGER_RECORD_HEADER_VERSION = 4
19FRAGMENT_HEADER_VERSION = 5
20TIME_SLICE_HEADER_VERSION = 2
21
22DATA_FORMAT = {
23 # daqdataformats/include/daqdataformats/TimeSliceHeader.hpp
24 "TimeSlice Header": {
25 "keys": ['Marker word', 'Version', 'TimeSlice number', # I I Q
26 'Run number', "Padding", # I I
27 'Source ID version', 'Source ID subsystem', 'Source ID'], # H H I
28 "size": 32,
29 "unpack string": '<2IQ2I2HI'
30 },
31 # daqdataformats/include/daqdataformats/TriggerRecordHeaderData.hpp
32 "TriggerRecord Header": {
33 "keys": ['Marker word', 'Version', 'Trigger number', # I I Q
34 'Trigger timestamp', 'No. of requested components', 'Run number', # Q Q I
35 'Error bits', 'Trigger type', 'Sequence number', # I Q H
36 'Max sequence num', 'Padding', # H I
37 'Source ID version', 'Source ID subsystem', 'Source ID'], # H H I
38 "size": 64,
39 "unpack string": '<2I3Q2IQ2HI2HI'
40 },
41 # daqdataformats/include/daqdataformats/FragmentHeader.hpp
42 "Fragment Header":{
43 "keys": ['Marker word', 'Version', 'Fragment size', 'Trigger number', # I I Q Q
44 'Trigger timestamp', 'Window begin', 'Window end', 'Run number', # Q Q Q I
45 'Error bits', 'Fragment type', 'Sequence number', # I I H
46 'Detector', # H
47 'Source ID version', 'Source ID subsystem', 'Source ID'], # H H I
48 "size": 72,
49 "unpack string": '<2I5Q3I4HI'
50 },
51 # daqdataformats/include/daqdataformats/ComponentRequest.hpp
52 "Component Request":{
53 "keys": ['Component request version', 'Padding', # I I
54 'Source ID version', 'Source ID subsystem', 'Source ID', # H H I
55 'Begin time', 'End time'], # Q Q
56 "size": 32,
57 "unpack string": "<2I2HI2Q"
58 }
59}
60
61
63 def __init__(self, name):
64 self.name = name
65 if os.path.exists(self.name):
66 try:
67 self.h5file = h5py.File(self.name, 'r')
68 except OSError:
69 sys.exit(f"ERROR: file \"{self.name}\" couldn't be opened; is it an HDF5 file?")
70 else:
71 sys.exit(f"ERROR: HDF5 file \"{self.name}\" is not found!")
72 # Assume HDf5 files without file attributes field "record_type"
73 # are old data files which only contain "TriggerRecord" data.
74 self.record_type = 'TriggerRecord'
75 self.clock_speed_hz = 50000000.0
77 observed_filelayout_version = self.h5file.attrs['filelayout_version']
78 if 'filelayout_version' in self.h5file.attrs.keys() and \
79 observed_filelayout_version >= FILELAYOUT_MIN_VERSION and \
80 observed_filelayout_version <= FILELAYOUT_MAX_VERSION:
81 print(f"INFO: input file \"{self.name}\" matches the supported file layout versions: {FILELAYOUT_MIN_VERSION} <= {observed_filelayout_version} <= {FILELAYOUT_MAX_VERSION}")
82 else:
83 sys.exit(f"ERROR: this script expects a file layout version between {FILELAYOUT_MIN_VERSION} and {FILELAYOUT_MAX_VERSION} but this wasn't confirmed in the HDF5 file \"{self.name}\", version={observed_filelayout_version}")
84 if 'record_type' in self.h5file.attrs.keys():
85 self.record_type = self.h5file.attrs['record_type']
86 for i in self.h5file.keys():
87 record = self.Record()
88 record.path = i
89 self.h5file[i].visititems(record)
90 self.recordsrecords.append(record)
91
92 def __del__(self):
93 try:
94 self.h5file.close()
95 except:
96 pass # OK if the file was never opened
97
98 def set_clock_speed_hz(self, k_clock_speed_hz):
99 self.clock_speed_hz = k_clock_speed_hz
100
101 def convert_to_binary(self, binary_file, k_nrecords):
102 with open(binary_file, 'wb') as bf:
103 n = 0
104 for i in self.recordsrecords:
105 if n >= k_nrecords and k_nrecords > 0:
106 break
107 dset = self.h5file[i.header]
108 idata_array = bytearray(dset[:])
109 bf.write(idata_array)
110 for j in i.fragments:
111 dset = self.h5file[j]
112 jdata_array = bytearray(dset[:])
113 bf.write(jdata_array)
114 n += 1
115 return
116
117 def printout(self, k_header_type, k_nrecords, k_list_components=False):
118 k_header_type = set(k_header_type)
119 if not {"attributes", "all"}.isdisjoint(k_header_type):
120 banner_str = " File Attributes "
121 print(banner_str.center(80, '='))
122 for k in self.h5file.attrs.keys():
123 print("{:<30}: {}".format(k, self.h5file.attrs[k]))
124 n = 0
125 for i in self.recordsrecords:
126 if n >= k_nrecords and k_nrecords > 0:
127 break
128 if not {"attributes", "all"}.isdisjoint(k_header_type):
129 banner_str = " Trigger Record Attributes "
130 print(banner_str.center(80, '='))
131 for k in self.h5file[i.path].attrs.keys():
132 print("{:<30}: {}".format(k, self.h5file[i.path].attrs[k]))
133 if not {"header", "both", "all"}.isdisjoint(k_header_type):
134 dset = self.h5file[i.header]
135 data_array = bytearray(dset[:])
136 banner_str = f" {self.record_type} Header "
137 print(banner_str.center(80, '='))
138 print('{:<30}:\t{}'.format("Path", i.path))
139 print('{:<30}:\t{}'.format("Size", dset.shape))
140 print('{:<30}:\t{}'.format("Data type", dset.dtype))
141 print_header(data_array, self.record_type, self.clock_speed_hz,
142 k_list_components)
143 if not {"fragment", "both", "all"}.isdisjoint(k_header_type):
144 for j in i.fragments:
145 dset = self.h5file[j]
146 data_array = bytearray(dset[:])
147 banner_str = " Fragment Header "
148 print(banner_str.center(80, '-'))
149 print('{:<30}:\t{}'.format("Path", j))
150 print('{:<30}:\t{}'.format("Size", dset.shape))
151 print('{:<30}:\t{}'.format("Data type", dset.dtype))
152 print_fragment_header(data_array, self.clock_speed_hz)
153 n += 1
154 return
155
156 def check_fragments(self, k_nrecords):
157 if self.record_type != "TriggerRecord":
158 print("Check fragments only works on TriggerRecord data.")
159 else:
160 report = []
161 n = 0
162 for i in self.recordsrecords:
163 if n >= k_nrecords and k_nrecords > 0:
164 break
165 dset = self.h5file[i.header]
166 data_array = bytearray(dset[:])
167 (trh_version, ) = struct.unpack('<I', data_array[4:8])
168 if trh_version != TRIGGER_RECORD_HEADER_VERSION:
169 raise ValueError(f"Invalid TriggerRecord Header format version: expected {TRIGGER_RECORD_HEADER_VERSION} and found {trh_version}")
170 (h, j, k) = struct.unpack('<3Q', data_array[8:32])
171 (s, ) = struct.unpack('<H', data_array[48:50])
172 nf = len(i.fragments)
173 empty_frag_count = 0
174 for frag in i.fragments:
175 frag_dset = self.h5file[frag]
176 frag_data = bytearray(frag_dset[:])
177 (frag_version, ) = struct.unpack('<I', frag_data[4:8])
178 if frag_version != FRAGMENT_HEADER_VERSION:
179 raise ValueError(f"Invalid Fragment Header format version: expected {FRAGMENT_HEADER_VERSION} and found {frag_version}")
180 (frag_size, ) = struct.unpack('<Q', frag_data[8:16])
181 if frag_size <= 72:
182 empty_frag_count += 1
183 report.append((h, s, k, nf, nf - k, empty_frag_count))
184 n += 1
185 print("{:-^80}".format("Column Definitions"))
186 print("i: Trigger record number;")
187 print("s: Sequence number;")
188 print("N_frag_exp: expected no. of fragments stored in header;")
189 print("N_frag_act: no. of fragments written in trigger record;")
190 print("N_diff: N_frag_act - N_frag_exp")
191 print("N_frag_empty: no. of empty fragments (size <= 72)")
192 print("{:-^80}".format("Column Definitions"))
193 print("{:^10}{:^10}{:^15}{:^15}{:^10}{:^12}".format(
194 "i", "s", "N_frag_exp", "N_frag_act", "N_diff", "N_frag_empty"))
195 for i in range(len(report)):
196 print("{:^10}{:^10}{:^15}{:^15}{:^10}{:^12}".format(*report[i]))
197 return
198
199 class Record:
200 def __init__(self):
201 self.path = ''
202 self.header = ''
203 self.fragments = []
204
205 def __call__(self, name, dset):
206 if isinstance(dset, h5py.Dataset):
207 if "TR_Builder" in name:
208 self.header = self.path + '/' + name
209 # set ncomponents here
210 else:
211 self.fragments.append(self.path + '/' + name)
212
213
214def tick_to_timestamp(ticks, clock_speed_hz):
215 ns = float(ticks)/clock_speed_hz
216 if ns < 3000000000:
217 return datetime.datetime.fromtimestamp(ns)
218 else:
219 return "InvalidDateString"
220
221
222def unpack_header(data_array, entry_type, required_version=0):
223 values = struct.unpack(DATA_FORMAT[entry_type]["unpack string"],
224 data_array[:DATA_FORMAT[entry_type]["size"]])
225 if required_version > 0 and len(values) >= 2 and values[1] != required_version:
226 raise ValueError(f"Invalid {entry_type} format version: expected {required_version} and found {values[1]}")
227 header = dict(zip(DATA_FORMAT[entry_type]["keys"], values))
228 return header
229
230
231def print_header_dict(hdict, clock_speed_hz):
232 filtered_list = ['Padding', 'Source ID version', 'Component request version']
233 for ik, iv in hdict.items():
234 if any(map(ik.__contains__, filtered_list)):
235 continue
236 elif "time" in ik or "begin" in ik or "end" in ik:
237 print("{:<30}: {} ({})".format(
238 ik, iv, tick_to_timestamp(iv, clock_speed_hz)))
239 elif 'Marker word' in ik:
240 print("{:<30}: {}".format(ik, hex(iv)))
241 elif ik == 'Detector':
242 subdet = detdataformats.DetID.Subdetector(iv)
243 det_name = detdataformats.DetID.subdetector_to_string(subdet)
244 print("{:<30}: {}".format(ik, det_name))
245 elif ik == 'Source ID subsystem' in ik:
246 subsys = daqdataformats.SourceID.Subsystem(iv)
247 subsys_name = daqdataformats.SourceID.subsystem_to_string(subsys)
248 print("{:<30}: {}".format(ik, subsys_name))
249 else:
250 print("{:<30}: {}".format(ik, iv))
251 return
252
253
254def print_trigger_record_header(data_array, clock_speed_hz, k_list_components):
255 print_header_dict(unpack_header(data_array, "TriggerRecord Header", TRIGGER_RECORD_HEADER_VERSION), clock_speed_hz)
256
257 if k_list_components:
258 comp_keys = DATA_FORMAT["Component Request"]["keys"]
259 comp_unpack_string = DATA_FORMAT["Component Request"]["unpack string"]
260 for i_values in struct.iter_unpack(comp_unpack_string, data_array[64:]):
261 i_comp = dict(zip(comp_keys, i_values))
262 print(80*'-')
263 print_header_dict(i_comp, clock_speed_hz)
264 return
265
266
267def print_fragment_header(data_array, clock_speed_hz):
268 print_header_dict(unpack_header(data_array, "Fragment Header", FRAGMENT_HEADER_VERSION), clock_speed_hz)
269 return
270
271
272def print_header(data_array, record_type, clock_speed_hz, k_list_components):
273 if record_type == "TriggerRecord":
274 print_trigger_record_header(data_array, clock_speed_hz,
275 k_list_components)
276 elif record_type == "TimeSlice":
277 print_header_dict(unpack_header(data_array, "TimeSlice Header", TIME_SLICE_HEADER_VERSION), clock_speed_hz)
278 else:
279 print(f"Error: Record Type {record_type} is not supported.")
280 return
281
282
284 parser = argparse.ArgumentParser(
285 description='Python script to parse DUNE-DAQ HDF5 output files.')
286
287 parser.add_argument('-f', '--file-name',
288 help='path to HDF5 file',
289 required=True)
290
291 parser.add_argument('-b', '--binary-output',
292 help='convert to the specified binary file')
293
294 parser.add_argument('-p', '--print-out', action='append',
295 choices=['header', 'fragment', 'both', 'attributes',
296 'all'],
297 help='''select which part of data to be displayed, this
298 option can be repeated multiple times, "-p both" is
299 equivalent to "-p header -p fragment", "-p all" is
300 equivalent to "-p attributes -p header -p fragment"''')
301
302 parser.add_argument('-c', '--check-fragments',
303 help='''check if fragments written in trigger record
304 matches expected number in trigger record header''',
305 action='store_true')
306
307 parser.add_argument('-l', '--list-components',
308 help='''list components in trigger record header, used
309 with "--print-out header" or "--print-out both", not
310 applicable to TimeSlice data''', action='store_true')
311
312 parser.add_argument('-n', '--num-of-records', type=int,
313 help='specify number of records to be parsed',
314 default=0)
315
316 parser.add_argument('-s', '--speed-of-clock', type=float,
317 help='''specify clock speed in Hz, default is
318 62500000.0 (62.5MHz)''',
319 default=62500000.0)
320
321 parser.add_argument('-v', '--version', action='version',
322 version='%(prog)s 2.0')
323 return parser.parse_args()
324
325
326def main():
327 args = parse_args()
328 if args.print_out is None and args.check_fragments is False and \
329 args.binary_output is None:
330 print("Error: use at least one of the two following options:")
331 print(" -p, --print-out {header, fragment, both}")
332 print(" -c, --check-fragments")
333 print(" -b, --binary-output")
334 return
335
336 h5 = DAQDataFile(args.file_name)
337
338 if args.binary_output is not None:
339 h5.convert_to_binary(args.binary_output, args.num_of_records)
340 if args.print_out is not None:
341 h5.set_clock_speed_hz(args.speed_of_clock)
342 h5.printout(args.print_out, args.num_of_records, args.list_components)
343 if args.check_fragments:
344 h5.check_fragments(args.num_of_records)
345
346 return
347
348
349if __name__ == "__main__":
350 main()
__call__(self, name, dset)
Definition hdf5_dump.py:205
printout(self, k_header_type, k_nrecords, k_list_components=False)
Definition hdf5_dump.py:117
__init__(self, name)
Definition hdf5_dump.py:63
convert_to_binary(self, binary_file, k_nrecords)
Definition hdf5_dump.py:101
set_clock_speed_hz(self, k_clock_speed_hz)
Definition hdf5_dump.py:98
check_fragments(self, k_nrecords)
Definition hdf5_dump.py:156
unpack_header(data_array, entry_type, required_version=0)
Definition hdf5_dump.py:222
print_header_dict(hdict, clock_speed_hz)
Definition hdf5_dump.py:231
print_header(data_array, record_type, clock_speed_hz, k_list_components)
Definition hdf5_dump.py:272
tick_to_timestamp(ticks, clock_speed_hz)
Definition hdf5_dump.py:214
print_fragment_header(data_array, clock_speed_hz)
Definition hdf5_dump.py:267
print_trigger_record_header(data_array, clock_speed_hz, k_list_components)
Definition hdf5_dump.py:254