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 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))