phconvert 0.9.1__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.
- phconvert/__init__.py +20 -0
- phconvert/_version.py +16 -0
- phconvert/bhreader.py +270 -0
- phconvert/hdf5.py +1283 -0
- phconvert/loader.py +387 -0
- phconvert/metadata.py +42 -0
- phconvert/plotter.py +134 -0
- phconvert/pqreader.py +1164 -0
- phconvert/smreader.py +133 -0
- phconvert/specs/photon-hdf5_specs.json +358 -0
- phconvert/v04/__init__.py +1 -0
- phconvert/v04/hdf5.py +1029 -0
- phconvert/v04/metadata.py +41 -0
- phconvert/v04/specs/photon-hdf5_specs.json +298 -0
- phconvert-0.9.1.dist-info/LICENSE.txt +22 -0
- phconvert-0.9.1.dist-info/METADATA +216 -0
- phconvert-0.9.1.dist-info/RECORD +19 -0
- phconvert-0.9.1.dist-info/WHEEL +5 -0
- phconvert-0.9.1.dist-info/top_level.txt +1 -0
phconvert/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#
|
|
2
|
+
# phconvert - Reference library to read and save Photon-HDF5 files
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2015 Antonino Ingargiola <tritemio@gmail.com>
|
|
5
|
+
#
|
|
6
|
+
import phconvert.loader
|
|
7
|
+
import phconvert.hdf5
|
|
8
|
+
import phconvert.v04
|
|
9
|
+
|
|
10
|
+
has_matplotlib = True
|
|
11
|
+
try:
|
|
12
|
+
import matplotlib
|
|
13
|
+
except ImportError:
|
|
14
|
+
has_matplotlib = False
|
|
15
|
+
if has_matplotlib:
|
|
16
|
+
import phconvert.plotter
|
|
17
|
+
del matplotlib
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
from phconvert._version import version as __version__
|
phconvert/_version.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '0.9.1'
|
|
16
|
+
__version_tuple__ = version_tuple = (0, 9, 1)
|
phconvert/bhreader.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#
|
|
2
|
+
# phconvert - Reference library to read and save Photon-HDF5 files
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2014-2015 Antonino Ingargiola <tritemio@gmail.com>
|
|
5
|
+
#
|
|
6
|
+
"""
|
|
7
|
+
This module contains functions to load and decode files from Becker & Hickl
|
|
8
|
+
hardware.
|
|
9
|
+
|
|
10
|
+
The high-level function in this module are:
|
|
11
|
+
|
|
12
|
+
- :func:`load_spc` which loads and decoded the photon data from SPC files.
|
|
13
|
+
- :func:`load_set` which returns a dictionary of metadata from SET files.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Becker & Hickl SPC Format
|
|
17
|
+
-------------------------
|
|
18
|
+
|
|
19
|
+
The structure of the SPC format is here described.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
SPC-600/630
|
|
23
|
+
~~~~~~~~~~~
|
|
24
|
+
|
|
25
|
+
SPC-600/630 files have a record of 48-bit (6 bytes)
|
|
26
|
+
in little endian (<) format.
|
|
27
|
+
The first 6 bytes of the file are an header containing
|
|
28
|
+
the `timestamps_unit` (in 0.1ns units) in the two central bytes
|
|
29
|
+
(i.e. bytes 2 and 3).
|
|
30
|
+
In the following drawing each char represents 2 bits::
|
|
31
|
+
|
|
32
|
+
bit: 64 48 0
|
|
33
|
+
0000 0000 XXXX XXXX XXXX XXXX XXXX XXXX
|
|
34
|
+
'-------' '--' '--' '-----'
|
|
35
|
+
field names: a c b d
|
|
36
|
+
|
|
37
|
+
0000 0000 XXXX XXXX XXXX XXXX XXXX XXXX
|
|
38
|
+
'-------' '--' '--' '-------'
|
|
39
|
+
numpy dtype: a c b field0
|
|
40
|
+
|
|
41
|
+
macrotime = [ b ] [ a ] (24 bit)
|
|
42
|
+
detector = [ c ] (8 bit)
|
|
43
|
+
nanotime = [ d ] (12 bit)
|
|
44
|
+
|
|
45
|
+
overflow bit: 13, bit_mask = 2^(13-1) = 4096
|
|
46
|
+
|
|
47
|
+
SPC-134/144/154/830
|
|
48
|
+
~~~~~~~~~~~~~~~~~~~
|
|
49
|
+
|
|
50
|
+
SPC-134/144/154/830 files have a record of 32-bits (4 bytes) in
|
|
51
|
+
little endian (<) format.
|
|
52
|
+
The first 4 bytes of the file are an header containing the
|
|
53
|
+
`timestamps_unit` (in 0.1ns units) in first two bytes.
|
|
54
|
+
In the following drawing each char represents 2 bits::
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
bit: 32 0
|
|
58
|
+
XXXX XXXX XXXX XXXX
|
|
59
|
+
'''-----' '''-----'
|
|
60
|
+
field names: a b c d
|
|
61
|
+
|
|
62
|
+
XXXX XXXX XXXX XXXX
|
|
63
|
+
'-------' '-------'
|
|
64
|
+
numpy dtype: field1 field0
|
|
65
|
+
|
|
66
|
+
macrotime = [ d ] (12 bit)
|
|
67
|
+
detector = [ c ] (4 bit)
|
|
68
|
+
nanotime = [ b ] (12 bit)
|
|
69
|
+
aux = [ a ] (4 bit)
|
|
70
|
+
|
|
71
|
+
aux = [invalid, overflow, gap, mark]
|
|
72
|
+
|
|
73
|
+
If overflow == 1 and invalid == 1 --> number of overflows = [ b ][ c ][ d ]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# TODO: automatic board model identification (in a new function?)
|
|
79
|
+
|
|
80
|
+
import numpy as np
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def load_spc(fname, spc_model='SPC-630'):
|
|
84
|
+
"""Load data from Becker & Hickl SPC files.
|
|
85
|
+
|
|
86
|
+
Arguments:
|
|
87
|
+
spc_model (string): name of the board model. Valid values are
|
|
88
|
+
'SPC-630', 'SPC-134', 'SPC-144', 'SPC-154' and 'SPC-830'.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
3 numpy arrays (timestamps, detector, nanotime) and a float
|
|
92
|
+
(timestamps_unit).
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
with open(fname, 'rb') as f:
|
|
96
|
+
|
|
97
|
+
if ('630' in spc_model) or ('600' in spc_model):
|
|
98
|
+
|
|
99
|
+
# We first decode the first 6 bytes which is a header...
|
|
100
|
+
header = np.fromfile(f, dtype='u2', count=3)
|
|
101
|
+
timestamps_unit = header[1] * 0.1e-9
|
|
102
|
+
num_routing_bits = np.bitwise_and(header[0], 0x000F) # unused
|
|
103
|
+
|
|
104
|
+
# ...and then the remaining records containing the photon data
|
|
105
|
+
spc_dtype = np.dtype([('field0', '<u2'), ('b', '<u1'), ('c', '<u1'),
|
|
106
|
+
('a', '<u2')])
|
|
107
|
+
data = np.fromfile(f, dtype=spc_dtype)
|
|
108
|
+
|
|
109
|
+
nanotime = 4095 - np.bitwise_and(data['field0'], 0x0FFF)
|
|
110
|
+
detector = data['c']
|
|
111
|
+
|
|
112
|
+
# Build the macrotime (timestamps) using in-place operation for efficiency
|
|
113
|
+
timestamps = data['b'].astype('int64')
|
|
114
|
+
np.left_shift(timestamps, 16, out=timestamps)
|
|
115
|
+
timestamps += data['a']
|
|
116
|
+
|
|
117
|
+
# extract the 13-th bit from data['field0']
|
|
118
|
+
overflow = np.bitwise_and(np.right_shift(data['field0'], 13), 1)
|
|
119
|
+
overflow = np.cumsum(overflow, dtype='int64')
|
|
120
|
+
|
|
121
|
+
# Add the overflow bits
|
|
122
|
+
timestamps += np.left_shift(overflow, 24)
|
|
123
|
+
|
|
124
|
+
elif ('SPC-1' in spc_model) or ('SPC-830' in spc_model):
|
|
125
|
+
# We first decode the first 4 bytes which is a header...
|
|
126
|
+
header = np.fromfile(f, dtype='u4', count=1)[0]
|
|
127
|
+
timestamps_unit = np.bitwise_and(header, 0x00FFFFFF) * 0.1e-9
|
|
128
|
+
num_routing_bits = np.bitwise_and(np.right_shift(header, 32), 0x78) # unused
|
|
129
|
+
|
|
130
|
+
# ...and then the remaining records containing the photon data
|
|
131
|
+
spc_dtype = np.dtype([('field0', '<u2'), ('field1', '<u2')])
|
|
132
|
+
data = np.fromfile(f, dtype=spc_dtype)
|
|
133
|
+
|
|
134
|
+
nanotime = 4095 - np.bitwise_and(data['field1'], 0x0FFF)
|
|
135
|
+
detector = np.bitwise_and(np.right_shift(data['field0'], 12), 0x0F)
|
|
136
|
+
|
|
137
|
+
# Build the macrotime
|
|
138
|
+
timestamps = np.bitwise_and(data['field0'], 0x0FFF).astype(dtype='int64')
|
|
139
|
+
|
|
140
|
+
# Extract the various status bits
|
|
141
|
+
mark = np.bitwise_and(np.right_shift(data['field1'], 12), 0x01)
|
|
142
|
+
gap = np.bitwise_and(np.right_shift(data['field1'], 13), 0x01)
|
|
143
|
+
overflow = np.bitwise_and(np.right_shift(data['field1'], 14), 0x01).\
|
|
144
|
+
astype(dtype='int64')
|
|
145
|
+
invalid = np.bitwise_and(np.right_shift(data['field1'], 15), 0x01)
|
|
146
|
+
|
|
147
|
+
# Invalid bytes: number of overflows from the last detected photon
|
|
148
|
+
for i_ovf in np.nonzero(overflow)[0].tolist():
|
|
149
|
+
if invalid[i_ovf]:
|
|
150
|
+
overflow[i_ovf] = np.left_shift(np.bitwise_and(
|
|
151
|
+
data['field1'][i_ovf], 0x0FFF), 16)\
|
|
152
|
+
+ data['field0'][i_ovf]
|
|
153
|
+
|
|
154
|
+
# Each overflow occurs every 2^12 macrotimes
|
|
155
|
+
overflow = np.left_shift(np.cumsum(overflow), 12)
|
|
156
|
+
|
|
157
|
+
# Add the overflow bits
|
|
158
|
+
timestamps += overflow
|
|
159
|
+
|
|
160
|
+
# Delete invalid entries
|
|
161
|
+
nanotime = np.delete(nanotime, invalid.nonzero())
|
|
162
|
+
timestamps = np.delete(timestamps, invalid.nonzero())
|
|
163
|
+
detector = np.delete(detector, invalid.nonzero())
|
|
164
|
+
|
|
165
|
+
return timestamps, detector, nanotime, timestamps_unit
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def load_set(fname_set):
|
|
169
|
+
"""Return a dict with data from the Becker & Hickl .SET file.
|
|
170
|
+
"""
|
|
171
|
+
identification = bh_set_identification(fname_set)
|
|
172
|
+
sys_params = bh_set_sys_params(fname_set)
|
|
173
|
+
return dict(identification=identification, sys_params=sys_params)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def bh_set_identification(fname_set):
|
|
177
|
+
"""Return a dict containing the IDENTIFICATION section of .SET files.
|
|
178
|
+
|
|
179
|
+
The both keys and values are native strings (binary strings on py2
|
|
180
|
+
and unicode strings on py3).
|
|
181
|
+
"""
|
|
182
|
+
with open(fname_set, 'rb') as f:
|
|
183
|
+
line = f.readline()
|
|
184
|
+
assert line.strip().endswith(b'IDENTIFICATION')
|
|
185
|
+
identification = {}
|
|
186
|
+
# .decode() returns a unicode string and str() a native string
|
|
187
|
+
# on both py2 and py3
|
|
188
|
+
line = str(f.readline().strip().decode('utf8'))
|
|
189
|
+
while not line.startswith('*END'):
|
|
190
|
+
item = [s.strip() for s in line.split(':')]
|
|
191
|
+
if len(item) > 1:
|
|
192
|
+
# found ':' -> retrive key and value
|
|
193
|
+
key = item[0]
|
|
194
|
+
value = ':'.join(item[1:])
|
|
195
|
+
else:
|
|
196
|
+
# no ':' found -> it's a new line continuing the previous key
|
|
197
|
+
value = ' '.join([identification[key], item[0]])
|
|
198
|
+
identification[key] = value
|
|
199
|
+
line = str(f.readline().strip().decode('utf8'))
|
|
200
|
+
return identification
|
|
201
|
+
|
|
202
|
+
def bh_set_sys_params(fname_set):
|
|
203
|
+
"""Return a dict containing the SYS_PARAMS section of .SET files.
|
|
204
|
+
|
|
205
|
+
The keys are native strings (traditional strings on py2
|
|
206
|
+
and unicode strings on py3) while values are numerical type or
|
|
207
|
+
byte strings.
|
|
208
|
+
"""
|
|
209
|
+
with open(fname_set, 'rb') as f:
|
|
210
|
+
## Make a dictionary of system parameters
|
|
211
|
+
start = False
|
|
212
|
+
sys_params = {}
|
|
213
|
+
for line in f.readlines():
|
|
214
|
+
# line can contain byte garbage, so don't convert to str
|
|
215
|
+
line = line.strip()
|
|
216
|
+
if line == b'SYS_PARA_BEGIN:':
|
|
217
|
+
start = True
|
|
218
|
+
continue
|
|
219
|
+
if line == b'SYS_PARA_END:':
|
|
220
|
+
break
|
|
221
|
+
if start and line.startswith(b'#'):
|
|
222
|
+
# Still there can be unknown fields, keep it binary
|
|
223
|
+
fields = line[5:-1].split(b',')
|
|
224
|
+
|
|
225
|
+
if fields[1] == b'B':
|
|
226
|
+
value = bool(fields[2])
|
|
227
|
+
elif fields[1] in [b'I', b'U', b'L']:
|
|
228
|
+
value = int(fields[2])
|
|
229
|
+
elif fields[1] == b'F':
|
|
230
|
+
value = float(fields[2])
|
|
231
|
+
elif fields[1] == b'S':
|
|
232
|
+
value = fields[2] # binary string
|
|
233
|
+
else:
|
|
234
|
+
value = b','.join(fields[1:]) # unknown, recomposing it
|
|
235
|
+
|
|
236
|
+
sys_params[str(fields[0].decode())] = value
|
|
237
|
+
return sys_params
|
|
238
|
+
|
|
239
|
+
def bh_decode(s):
|
|
240
|
+
"""Replace code strings from .SET files with human readable label strings.
|
|
241
|
+
"""
|
|
242
|
+
s = s.replace('SP_', '')
|
|
243
|
+
s = s.replace('_ZC', ' ZC Thresh.')
|
|
244
|
+
s = s.replace('_LL', ' Limit Low')
|
|
245
|
+
s = s.replace('_LH', ' Limit High')
|
|
246
|
+
s = s.replace('_FD', ' Freq. Div.')
|
|
247
|
+
s = s.replace('_OF', ' Offset')
|
|
248
|
+
s = s.replace('_HF', ' Holdoff')
|
|
249
|
+
s = s.replace('TAC_G', 'TAC Gain')
|
|
250
|
+
s = s.replace('TAC_R', 'TAC Range')
|
|
251
|
+
s = s.replace('_TC', ' Time/Chan')
|
|
252
|
+
s = s.replace('_TD', ' Time/Div')
|
|
253
|
+
s = s.replace('_FQ', ' Threshold')
|
|
254
|
+
return s
|
|
255
|
+
|
|
256
|
+
def bh_print_sys_params(sys_params):
|
|
257
|
+
"""Print a summary of the Becker & Hickl system parameters (.SET file).
|
|
258
|
+
"""
|
|
259
|
+
if 'sys_params' in sys_params:
|
|
260
|
+
# Passed output of load_set(). Extract sys_params and retry
|
|
261
|
+
bh_print_sys_params(sys_params['sys_params'])
|
|
262
|
+
else:
|
|
263
|
+
for k, v in sys_params.items():
|
|
264
|
+
if 'TAC' in k: print('%s\t %g' % (bh_decode(k), v))
|
|
265
|
+
print()
|
|
266
|
+
for k, v in sys_params.items():
|
|
267
|
+
if 'CFD' in k: print('%s\t %g' % (bh_decode(k), v))
|
|
268
|
+
print()
|
|
269
|
+
for k, v in sys_params.items():
|
|
270
|
+
if 'SYN' in k: print('%s\t %g' % (bh_decode(k), v))
|