DUNE-DAQ
DUNE Trigger and Data Acquisition software
Toggle main menu visibility
Loading...
Searching...
No Matches
dunedaq
sourcecode
hdf5libs
scripts
hdf5_dump.py
Go to the documentation of this file.
1
#!/usr/bin/env python3
2
3
import
daqdataformats
4
import
detdataformats
5
6
import
argparse
7
import
datetime
8
import
h5py
9
import
struct
10
import
os
11
import
sys
12
13
# Current allowed range of file layout versions
14
FILELAYOUT_MIN_VERSION = 4
15
FILELAYOUT_MAX_VERSION = 7
16
17
# Current header versions
18
TRIGGER_RECORD_HEADER_VERSION = 5
19
FRAGMENT_HEADER_VERSION = 6
20
TIME_SLICE_HEADER_VERSION = 2
21
22
DATA_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
'Status 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
'Status 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
62
class
DAQDataFile
:
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
76
self.
records
= []
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.
records
.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.
records
:
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.
records
:
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.
records
:
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
214
def
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
222
def
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
231
def
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
'Status bits'
in
ik:
242
print(
"{:<30}: {}"
.format(ik, hex(iv)))
243
elif
ik ==
'Detector'
:
244
subdet = detdataformats.DetID.Subdetector(iv)
245
det_name = detdataformats.DetID.subdetector_to_string(subdet)
246
print(
"{:<30}: {}"
.format(ik, det_name))
247
elif
ik ==
'Source ID subsystem'
in
ik:
248
subsys = daqdataformats.SourceID.Subsystem(iv)
249
subsys_name = daqdataformats.SourceID.subsystem_to_string(subsys)
250
print(
"{:<30}: {}"
.format(ik, subsys_name))
251
else
:
252
print(
"{:<30}: {}"
.format(ik, iv))
253
return
254
255
256
def
print_trigger_record_header
(data_array, clock_speed_hz, k_list_components):
257
print_header_dict
(
unpack_header
(data_array,
"TriggerRecord Header"
, TRIGGER_RECORD_HEADER_VERSION), clock_speed_hz)
258
259
if
k_list_components:
260
comp_keys = DATA_FORMAT[
"Component Request"
][
"keys"
]
261
comp_unpack_string = DATA_FORMAT[
"Component Request"
][
"unpack string"
]
262
for
i_values
in
struct.iter_unpack(comp_unpack_string, data_array[64:]):
263
i_comp = dict(zip(comp_keys, i_values))
264
print(80*
'-'
)
265
print_header_dict
(i_comp, clock_speed_hz)
266
return
267
268
269
def
print_fragment_header
(data_array, clock_speed_hz):
270
print_header_dict
(
unpack_header
(data_array,
"Fragment Header"
, FRAGMENT_HEADER_VERSION), clock_speed_hz)
271
return
272
273
274
def
print_header
(data_array, record_type, clock_speed_hz, k_list_components):
275
if
record_type ==
"TriggerRecord"
:
276
print_trigger_record_header
(data_array, clock_speed_hz,
277
k_list_components)
278
elif
record_type ==
"TimeSlice"
:
279
print_header_dict
(
unpack_header
(data_array,
"TimeSlice Header"
, TIME_SLICE_HEADER_VERSION), clock_speed_hz)
280
else
:
281
print(f
"Error: Record Type {record_type} is not supported."
)
282
return
283
284
285
def
parse_args
():
286
parser = argparse.ArgumentParser(
287
description=
'Python script to parse DUNE-DAQ HDF5 output files.'
)
288
289
parser.add_argument(
'-f'
,
'--file-name'
,
290
help=
'path to HDF5 file'
,
291
required=
True
)
292
293
parser.add_argument(
'-b'
,
'--binary-output'
,
294
help=
'convert to the specified binary file'
)
295
296
parser.add_argument(
'-p'
,
'--print-out'
, action=
'append'
,
297
choices=[
'header'
,
'fragment'
,
'both'
,
'attributes'
,
298
'all'
],
299
help=
'''select which part of data to be displayed, this
300
option can be repeated multiple times, "-p both" is
301
equivalent to "-p header -p fragment", "-p all" is
302
equivalent to "-p attributes -p header -p fragment"'''
)
303
304
parser.add_argument(
'-c'
,
'--check-fragments'
,
305
help=
'''check if fragments written in trigger record
306
matches expected number in trigger record header''',
307
action='store_true')
308
309
parser.add_argument('-l', '--list-components',
310
help='''
list components in trigger record header, used
311
with "--print-out header" or "--print-out both", not
312
applicable to TimeSlice data''', action='store_true')
313
314
parser.add_argument('-n', '--num-of-records', type=int,
315
help='specify number of records to be parsed',
316
default=0)
317
318
parser.add_argument('-s', '--speed-of-clock', type=float,
319
help='''specify clock speed in Hz, default is
320
62500000.0 (62.5MHz)''',
321
default=62500000.0)
322
323
parser.add_argument('-v', '--version', action='version',
324
version='%(prog)s 2.0')
325
return parser.parse_args()
326
327
328
def main():
329
args = parse_args()
330
if args.print_out is None and args.check_fragments is False and \
331
args.binary_output is None:
332
print("Error: use at least one of the two following options:")
333
print(" -p, --print-out {header, fragment, both}")
334
print(" -c, --check-fragments")
335
print(" -b, --binary-output")
336
return
337
338
h5 = DAQDataFile(args.file_name)
339
340
if args.binary_output is not None:
341
h5.convert_to_binary(args.binary_output, args.num_of_records)
342
if args.print_out is not None:
343
h5.set_clock_speed_hz(args.speed_of_clock)
344
h5.printout(args.print_out, args.num_of_records, args.list_components)
345
if args.check_fragments:
346
h5.check_fragments(args.num_of_records)
347
348
return
349
350
351
if __name__ == "__main__":
352
main()
hdf5_dump.DAQDataFile.Record
Definition
hdf5_dump.py:199
hdf5_dump.DAQDataFile.Record.fragments
list fragments
Definition
hdf5_dump.py:203
hdf5_dump.DAQDataFile.Record.header
str header
Definition
hdf5_dump.py:202
hdf5_dump.DAQDataFile.Record.__init__
__init__(self)
Definition
hdf5_dump.py:200
hdf5_dump.DAQDataFile.Record.__call__
__call__(self, name, dset)
Definition
hdf5_dump.py:205
hdf5_dump.DAQDataFile.Record.path
str path
Definition
hdf5_dump.py:201
hdf5_dump.DAQDataFile
Definition
hdf5_dump.py:62
hdf5_dump.DAQDataFile.name
name
Definition
hdf5_dump.py:64
hdf5_dump.DAQDataFile.printout
printout(self, k_header_type, k_nrecords, k_list_components=False)
Definition
hdf5_dump.py:117
hdf5_dump.DAQDataFile.h5file
h5file
Definition
hdf5_dump.py:67
hdf5_dump.DAQDataFile.record_type
str record_type
Definition
hdf5_dump.py:74
hdf5_dump.DAQDataFile.__init__
__init__(self, name)
Definition
hdf5_dump.py:63
hdf5_dump.DAQDataFile.convert_to_binary
convert_to_binary(self, binary_file, k_nrecords)
Definition
hdf5_dump.py:101
hdf5_dump.DAQDataFile.clock_speed_hz
float clock_speed_hz
Definition
hdf5_dump.py:75
hdf5_dump.DAQDataFile.records
list records
Definition
hdf5_dump.py:76
hdf5_dump.DAQDataFile.set_clock_speed_hz
set_clock_speed_hz(self, k_clock_speed_hz)
Definition
hdf5_dump.py:98
hdf5_dump.DAQDataFile.check_fragments
check_fragments(self, k_nrecords)
Definition
hdf5_dump.py:156
hdf5_dump.DAQDataFile.__del__
__del__(self)
Definition
hdf5_dump.py:92
hdf5_dump.print_fragment_header
print_fragment_header(data_array, clock_speed_hz)
Definition
hdf5_dump.py:269
hdf5_dump.tick_to_timestamp
tick_to_timestamp(ticks, clock_speed_hz)
Definition
hdf5_dump.py:214
hdf5_dump.print_header
print_header(data_array, record_type, clock_speed_hz, k_list_components)
Definition
hdf5_dump.py:274
hdf5_dump.unpack_header
unpack_header(data_array, entry_type, required_version=0)
Definition
hdf5_dump.py:222
hdf5_dump.parse_args
parse_args()
Definition
hdf5_dump.py:285
hdf5_dump.print_trigger_record_header
print_trigger_record_header(data_array, clock_speed_hz, k_list_components)
Definition
hdf5_dump.py:256
hdf5_dump.print_header_dict
print_header_dict(hdict, clock_speed_hz)
Definition
hdf5_dump.py:231
Generated on
for DUNE-DAQ by
1.17.0