fftekwfm 0.3.0.1__tar.gz
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.
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: fftekwfm
|
|
3
|
+
Version: 0.3.0.1
|
|
4
|
+
Summary: Read Tektronix WFM files with FastFrame support.
|
|
5
|
+
Author: Guillaume Le Goc
|
|
6
|
+
Author-email: Guillaume Le Goc <guillaume.le-goc@lncmi.cnrs.fr>
|
|
7
|
+
Requires-Dist: numpy>=2.2.0
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# FFTekWFM
|
|
12
|
+
|
|
13
|
+
Tektronix [Waveform file format](https://download.tek.com/manual/Waveform-File-Format-Manual-077022011.pdf) reader.
|
|
14
|
+
|
|
15
|
+
Features :
|
|
16
|
+
+ Support WFM file format version 1, 2 and 3
|
|
17
|
+
+ Support FastFrame
|
|
18
|
+
+ Support memory mapping
|
|
19
|
+
+ Support loading frames with or without pre- and post-charge data
|
|
20
|
+
|
|
21
|
+
Header reader structure inspired by [TekWFM2](https://github.com/vongostev/tekwfm2).
|
|
22
|
+
|
|
23
|
+
## Notice
|
|
24
|
+
Note this package works for *my* use-case but is not properly tested. Consider using [TekWFM2](https://github.com/vongostev/tekwfm2) for a more established solution.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
`pip install git+https://gitlab.in2p3.fr/himagnetos/tekwfm.git`
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
Read a file with memory mapping and plot some time series from fast frames, unscaled :
|
|
32
|
+
```python
|
|
33
|
+
import matplotlib.pyplot as plt
|
|
34
|
+
from fftekwfm import TekWFM
|
|
35
|
+
|
|
36
|
+
filename = "/path/to/tekfile.wfm"
|
|
37
|
+
tek = TekWFM(filename).load_frames().get_time_frame()
|
|
38
|
+
plt.figure()
|
|
39
|
+
plt.plot(tek.time_frame, tek.frames[:, [0, 8000, 15000]])
|
|
40
|
+
plt.xlabel("time (s)")
|
|
41
|
+
plt.ylabel("signal (a.u.)")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Load every frames in-memory and do some calculation :
|
|
45
|
+
```python
|
|
46
|
+
import matplotlib.pyplot as plt
|
|
47
|
+
import numpy as np
|
|
48
|
+
from fftekwfm import TekWFM
|
|
49
|
+
|
|
50
|
+
filename = "/path/to/tekfile.wfm"
|
|
51
|
+
tek = TekWFM(filename).load_frames(mmap=False)
|
|
52
|
+
Sn = np.abs(np.fft.rfft(tek.frames, axis=0))
|
|
53
|
+
f = np.fft.rfftfreq(sig.shape[0], d=tek.tscale) # tscale is the time between two samples
|
|
54
|
+
plt.figure()
|
|
55
|
+
plt.plot(f, S_mag)
|
|
56
|
+
plt.xlabel("frequency (Hz)")
|
|
57
|
+
plt.ylabel("magnitude")
|
|
58
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# FFTekWFM
|
|
2
|
+
|
|
3
|
+
Tektronix [Waveform file format](https://download.tek.com/manual/Waveform-File-Format-Manual-077022011.pdf) reader.
|
|
4
|
+
|
|
5
|
+
Features :
|
|
6
|
+
+ Support WFM file format version 1, 2 and 3
|
|
7
|
+
+ Support FastFrame
|
|
8
|
+
+ Support memory mapping
|
|
9
|
+
+ Support loading frames with or without pre- and post-charge data
|
|
10
|
+
|
|
11
|
+
Header reader structure inspired by [TekWFM2](https://github.com/vongostev/tekwfm2).
|
|
12
|
+
|
|
13
|
+
## Notice
|
|
14
|
+
Note this package works for *my* use-case but is not properly tested. Consider using [TekWFM2](https://github.com/vongostev/tekwfm2) for a more established solution.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
`pip install git+https://gitlab.in2p3.fr/himagnetos/tekwfm.git`
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Read a file with memory mapping and plot some time series from fast frames, unscaled :
|
|
22
|
+
```python
|
|
23
|
+
import matplotlib.pyplot as plt
|
|
24
|
+
from fftekwfm import TekWFM
|
|
25
|
+
|
|
26
|
+
filename = "/path/to/tekfile.wfm"
|
|
27
|
+
tek = TekWFM(filename).load_frames().get_time_frame()
|
|
28
|
+
plt.figure()
|
|
29
|
+
plt.plot(tek.time_frame, tek.frames[:, [0, 8000, 15000]])
|
|
30
|
+
plt.xlabel("time (s)")
|
|
31
|
+
plt.ylabel("signal (a.u.)")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Load every frames in-memory and do some calculation :
|
|
35
|
+
```python
|
|
36
|
+
import matplotlib.pyplot as plt
|
|
37
|
+
import numpy as np
|
|
38
|
+
from fftekwfm import TekWFM
|
|
39
|
+
|
|
40
|
+
filename = "/path/to/tekfile.wfm"
|
|
41
|
+
tek = TekWFM(filename).load_frames(mmap=False)
|
|
42
|
+
Sn = np.abs(np.fft.rfft(tek.frames, axis=0))
|
|
43
|
+
f = np.fft.rfftfreq(sig.shape[0], d=tek.tscale) # tscale is the time between two samples
|
|
44
|
+
plt.figure()
|
|
45
|
+
plt.plot(f, S_mag)
|
|
46
|
+
plt.xlabel("frequency (Hz)")
|
|
47
|
+
plt.ylabel("magnitude")
|
|
48
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fftekwfm"
|
|
3
|
+
version = "0.3.0.1"
|
|
4
|
+
description = "Read Tektronix WFM files with FastFrame support."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Guillaume Le Goc", email = "guillaume.le-goc@lncmi.cnrs.fr" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"numpy>=2.2.0",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["uv_build>=0.9.9,<0.10.0"]
|
|
16
|
+
build-backend = "uv_build"
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Initialize class to set its attributes types.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class tTekWFM:
|
|
7
|
+
byte_order: str
|
|
8
|
+
version: bytes
|
|
9
|
+
bps: int
|
|
10
|
+
curve_offset: int
|
|
11
|
+
nframes: int
|
|
12
|
+
fastframe: int
|
|
13
|
+
imp_dim_count: int
|
|
14
|
+
exp_dim_count: int
|
|
15
|
+
vscale: float
|
|
16
|
+
voffset: float
|
|
17
|
+
vunits: bytes
|
|
18
|
+
type_code: int
|
|
19
|
+
dformat: str
|
|
20
|
+
tscale: float
|
|
21
|
+
toffset: float
|
|
22
|
+
tunits: bytes
|
|
23
|
+
npoints: int
|
|
24
|
+
tsfrac: float
|
|
25
|
+
tsunix: int
|
|
26
|
+
pre_start_offset: int
|
|
27
|
+
data_start_offset: int
|
|
28
|
+
post_start_offset: int
|
|
29
|
+
post_stop_offset: int
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TekWFM class to read Tektronix .wfm file.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from struct import unpack_from as unpk
|
|
7
|
+
from typing import Self
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from .ttypes import tTekWFM
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TekWFM(tTekWFM):
|
|
15
|
+
def __init__(self, filename: str | Path | None = None) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Read Tektronix WFM file format with metadata and FastFrames support.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
filename : str | Path | None
|
|
22
|
+
Full path to the .wfm file. Can be None to access methods.
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
if filename:
|
|
27
|
+
self.filename = filename # store file path
|
|
28
|
+
|
|
29
|
+
# Read metadata
|
|
30
|
+
header = self._load_header() # get header
|
|
31
|
+
meta = self.decode_header(header) # decode relevant information from header
|
|
32
|
+
|
|
33
|
+
# Store as attributes for convenience
|
|
34
|
+
for key, value in meta.items():
|
|
35
|
+
setattr(self, key, value)
|
|
36
|
+
|
|
37
|
+
# Get timestamps
|
|
38
|
+
self.get_timestamps()
|
|
39
|
+
|
|
40
|
+
def _load_header(self, n: int = 838) -> bytes:
|
|
41
|
+
"""
|
|
42
|
+
Read bytes header.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
n : int, optional
|
|
47
|
+
Header size in bytes. Default is 838.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
header : bytes
|
|
52
|
+
Header ready to be decoded.
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
with open(self.filename, "rb") as f:
|
|
56
|
+
header = f.read(n)
|
|
57
|
+
|
|
58
|
+
return header
|
|
59
|
+
|
|
60
|
+
def _load_timestamps(self, n: int = 838) -> bytes:
|
|
61
|
+
"""
|
|
62
|
+
Read bytes FastFrame timestamps.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
n : int, optional
|
|
67
|
+
Header size in bytes, by default 838.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
ts : bytes
|
|
72
|
+
Timestamps ready to be decoded.
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
with open(self.filename, "rb") as f:
|
|
76
|
+
f.seek(n) # skip header
|
|
77
|
+
bts = f.read((self.nframes - 1) * 24) # size of the WfmUpdateSpec objects
|
|
78
|
+
|
|
79
|
+
return bts
|
|
80
|
+
|
|
81
|
+
def decode_header(self, header: bytes) -> dict:
|
|
82
|
+
"""
|
|
83
|
+
Decode WFM file header to obtain metadata.
|
|
84
|
+
|
|
85
|
+
Based on TekWFM2 [1] by Pavel Gostev (MIT Licence) and the Tektronix WFM file
|
|
86
|
+
format spec [2].
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
header : bytes
|
|
91
|
+
Header bytes of the file.
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
meta : dict
|
|
96
|
+
Metadata of the WFM file.
|
|
97
|
+
|
|
98
|
+
References
|
|
99
|
+
----------
|
|
100
|
+
.. [1] https://github.com/vongostev/TekWFM2
|
|
101
|
+
.. [2] https://download.tek.com/manual/Waveform-File-Format-Manual-077022011.pdf
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
meta = {}
|
|
105
|
+
|
|
106
|
+
if len(header) != 838:
|
|
107
|
+
raise ValueError("WFM header bytes not 838")
|
|
108
|
+
byte_order = unpk("H", header, offset=0)[0]
|
|
109
|
+
if byte_order == 0x0F0F:
|
|
110
|
+
meta["byte_order"] = "<" # little-endian
|
|
111
|
+
|
|
112
|
+
elif meta["byte_order"] == 0xF0F0:
|
|
113
|
+
meta["byte_order"] = ">" # big-endian
|
|
114
|
+
else:
|
|
115
|
+
raise ValueError("Could not determine endianness.")
|
|
116
|
+
|
|
117
|
+
meta["version"] = unpk("8s", header, offset=2)[0]
|
|
118
|
+
if meta["version"] == b":WFM#001":
|
|
119
|
+
v1_offset = 2
|
|
120
|
+
elif meta["version"] == b":WFM#002":
|
|
121
|
+
v1_offset = 0
|
|
122
|
+
elif meta["version"] == b":WFM#003":
|
|
123
|
+
v1_offset = 0
|
|
124
|
+
else:
|
|
125
|
+
raise ValueError(
|
|
126
|
+
f"Only version 1, 2 and 3 of WFM supported, got {meta['version']}."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
endianness = meta["byte_order"]
|
|
130
|
+
char_format = endianness + "c"
|
|
131
|
+
int_format = endianness + "i"
|
|
132
|
+
uint_format = endianness + "I"
|
|
133
|
+
byte_format = endianness + "b"
|
|
134
|
+
ulong_format = endianness + "L"
|
|
135
|
+
double_format = endianness + "d"
|
|
136
|
+
|
|
137
|
+
## Waveform static file information
|
|
138
|
+
# Number of bytes per sample
|
|
139
|
+
meta["bps"] = unpk(byte_format, header, offset=15)[0]
|
|
140
|
+
# Byte offset to beginning of curve buffer
|
|
141
|
+
meta["curve_offset"] = unpk(int_format, header, offset=16)[0]
|
|
142
|
+
# Number of FastFrames
|
|
143
|
+
meta["nframes"] = unpk(uint_format, header, offset=72)[0] + 1
|
|
144
|
+
|
|
145
|
+
## Waveform header
|
|
146
|
+
# 0 : Single waveform set, 1 : FastFrame set
|
|
147
|
+
meta["fastframe"] = unpk(uint_format, header, offset=78)[0]
|
|
148
|
+
# Number of implicit dimension (time)
|
|
149
|
+
meta["imp_dim_count"] = unpk(ulong_format, header, offset=114)[0]
|
|
150
|
+
# Number of explicit dimension (voltage)
|
|
151
|
+
meta["exp_dim_count"] = unpk(ulong_format, header, offset=118)[0]
|
|
152
|
+
|
|
153
|
+
## Explicit dimension (vertical : voltage)
|
|
154
|
+
# Scaling to volts, Voltage = (wfmCurveData*vscale) + offset
|
|
155
|
+
meta["vscale"] = unpk(double_format, header, offset=168 - v1_offset)[0]
|
|
156
|
+
# offset
|
|
157
|
+
meta["voffset"] = unpk(double_format, header, offset=176 - v1_offset)[0]
|
|
158
|
+
# units
|
|
159
|
+
meta["vunits"] = unpk(char_format, header, offset=188 - v1_offset)[0]
|
|
160
|
+
# sample data type detection
|
|
161
|
+
meta["type_code"] = unpk(int_format, header, offset=240 - v1_offset)[0]
|
|
162
|
+
if meta["type_code"] == 0 and meta["bps"] == 2:
|
|
163
|
+
meta["dformat"] = endianness + "i2"
|
|
164
|
+
elif meta["type_code"] == 4 and meta["bps"] == 4:
|
|
165
|
+
meta["dformat"] = endianness + "f32"
|
|
166
|
+
else:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
f"Data type code {meta['type_code']} or "
|
|
169
|
+
f"bytes-per-sample {meta['bps']} is not supported."
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
## Implicit dimension (horizontal : time)
|
|
173
|
+
# scaling
|
|
174
|
+
meta["tscale"] = unpk(double_format, header, offset=488 - v1_offset)[0]
|
|
175
|
+
# offset
|
|
176
|
+
meta["toffset"] = unpk(double_format, header, offset=496 - v1_offset)[0]
|
|
177
|
+
# units
|
|
178
|
+
meta["tunits"] = unpk(char_format, header, 508 - v1_offset)[0]
|
|
179
|
+
# number of points per frame
|
|
180
|
+
meta["npoints"] = unpk(ulong_format, header, offset=504 - v1_offset)[0]
|
|
181
|
+
|
|
182
|
+
## Wfm Update specification : trigger timestamp for first frame
|
|
183
|
+
meta["tsfrac"] = unpk(double_format, header, offset=796 - v1_offset)[0]
|
|
184
|
+
meta["tsunix"] = unpk(uint_format, header, offset=804 - v1_offset)[0]
|
|
185
|
+
|
|
186
|
+
## Wfm Curve information
|
|
187
|
+
# Precharge start offset
|
|
188
|
+
meta["pre_start_offset"] = unpk(ulong_format, header, offset=818 - v1_offset)[0]
|
|
189
|
+
# Data start offset : bytes from beginning of curve buffer to first point
|
|
190
|
+
meta["data_start_offset"] = unpk(ulong_format, header, offset=822 - v1_offset)[
|
|
191
|
+
0
|
|
192
|
+
]
|
|
193
|
+
# Postcharge start offset
|
|
194
|
+
meta["post_start_offset"] = unpk(ulong_format, header, offset=826 - v1_offset)[
|
|
195
|
+
0
|
|
196
|
+
]
|
|
197
|
+
# Postcharge stop offset
|
|
198
|
+
meta["post_stop_offset"] = unpk(ulong_format, header, offset=830 - v1_offset)[0]
|
|
199
|
+
|
|
200
|
+
return meta
|
|
201
|
+
|
|
202
|
+
def _decode_fastframe_timestamps(
|
|
203
|
+
self, tsbytes: bytes, k: int, endianness: None | str = None
|
|
204
|
+
) -> float:
|
|
205
|
+
"""
|
|
206
|
+
Decode timestamp for the (k + 1)th frame.
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
tsbytes : bytes
|
|
211
|
+
Binary data from WfmUpdateSpec object.
|
|
212
|
+
k : int
|
|
213
|
+
Index of the frame. 1-based because the first (0th) frame is stored
|
|
214
|
+
elsewhere in memory.
|
|
215
|
+
endianness : None or str, optionnal
|
|
216
|
+
Endianness. If None (default), read from self.
|
|
217
|
+
|
|
218
|
+
Returns
|
|
219
|
+
-------
|
|
220
|
+
ts : float
|
|
221
|
+
Absolute timestamp in second of the start trigger of the (k + 1)th frame.
|
|
222
|
+
|
|
223
|
+
"""
|
|
224
|
+
if not endianness:
|
|
225
|
+
endianness = self.meta["byte_order"]
|
|
226
|
+
|
|
227
|
+
# fraction of second
|
|
228
|
+
tsfrac = unpk(f"{endianness}d", tsbytes, offset=12 + k * 24)[0]
|
|
229
|
+
# unix epoch
|
|
230
|
+
tsunix = unpk(f"{endianness}l", tsbytes, offset=20 + k * 24)[0]
|
|
231
|
+
|
|
232
|
+
return tsfrac + tsunix
|
|
233
|
+
|
|
234
|
+
def _load_frames(
|
|
235
|
+
self, pre: bool = False, post: bool = False, mmap: bool = True
|
|
236
|
+
) -> np.ndarray | np.memmap:
|
|
237
|
+
"""
|
|
238
|
+
Load all available frames. Optionaly, include pre- and post- charge data. By
|
|
239
|
+
default, memory-mapping is used instead of loading the whole array in RAM.
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
pre, post : bool, optionnal
|
|
244
|
+
If True, include pre- and post-charge data. Default is False.
|
|
245
|
+
mmap : bool, optional
|
|
246
|
+
Use memory-mapping. Default is True.
|
|
247
|
+
|
|
248
|
+
Returns
|
|
249
|
+
-------
|
|
250
|
+
data : np.ndarray
|
|
251
|
+
Frames raw data with time series corresponding to one frame on columns.
|
|
252
|
+
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
# Number of samples : nframes * number of datapoints with pre/post charge
|
|
256
|
+
nsamples = self.nframes * self.post_stop_offset // self.bps
|
|
257
|
+
|
|
258
|
+
# Load data
|
|
259
|
+
if mmap:
|
|
260
|
+
data = np.memmap(
|
|
261
|
+
self.filename,
|
|
262
|
+
mode="r",
|
|
263
|
+
dtype=self.dformat,
|
|
264
|
+
offset=self.curve_offset,
|
|
265
|
+
shape=nsamples,
|
|
266
|
+
)
|
|
267
|
+
else:
|
|
268
|
+
data = np.fromfile(
|
|
269
|
+
self.filename,
|
|
270
|
+
dtype=self.dformat,
|
|
271
|
+
offset=self.curve_offset,
|
|
272
|
+
count=nsamples,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Reshape with frames on columns
|
|
276
|
+
data = data.reshape(self.post_stop_offset // self.bps, self.nframes, order="F")
|
|
277
|
+
|
|
278
|
+
# Remove pre-charge data
|
|
279
|
+
if not pre:
|
|
280
|
+
start = self.data_start_offset // self.bps
|
|
281
|
+
else:
|
|
282
|
+
start = 0
|
|
283
|
+
# Remove post-charge data
|
|
284
|
+
if not post:
|
|
285
|
+
stop = start + self.npoints
|
|
286
|
+
else:
|
|
287
|
+
stop = None
|
|
288
|
+
|
|
289
|
+
return data[start:stop, :]
|
|
290
|
+
|
|
291
|
+
def _get_time_frame(self, start: float, dt: float, nsamples: int) -> np.ndarray:
|
|
292
|
+
"""
|
|
293
|
+
Generate a time vector.
|
|
294
|
+
|
|
295
|
+
Parameters
|
|
296
|
+
----------
|
|
297
|
+
start : float
|
|
298
|
+
Value of first sample.
|
|
299
|
+
dt : float
|
|
300
|
+
Time between to samples.
|
|
301
|
+
nsamples : int
|
|
302
|
+
Number of time points.
|
|
303
|
+
|
|
304
|
+
Returns
|
|
305
|
+
-------
|
|
306
|
+
time_frame : np.ndarray
|
|
307
|
+
Time vector.
|
|
308
|
+
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
tstop = start + nsamples * dt
|
|
312
|
+
return np.linspace(start, tstop, nsamples, endpoint=False)
|
|
313
|
+
|
|
314
|
+
def scale_data(self, data: np.ndarray) -> np.ndarray:
|
|
315
|
+
"""
|
|
316
|
+
Convert input data to physical units (usually, volts)) given conversion factor
|
|
317
|
+
in metadata.
|
|
318
|
+
|
|
319
|
+
Note that if converting all the data at once, if it was in INT16, it will be
|
|
320
|
+
broadcasted to float and will be very slow. It is advised to perform all
|
|
321
|
+
necessary computations and reduction before conversion.
|
|
322
|
+
|
|
323
|
+
Parameters
|
|
324
|
+
----------
|
|
325
|
+
data : np.ndarray
|
|
326
|
+
Input data to convert.
|
|
327
|
+
|
|
328
|
+
Returns
|
|
329
|
+
-------
|
|
330
|
+
data_scale : np.ndarray
|
|
331
|
+
Data scaled to physical units.
|
|
332
|
+
|
|
333
|
+
"""
|
|
334
|
+
return data * self.vscale + self.voffset
|
|
335
|
+
|
|
336
|
+
def get_timestamps(self) -> Self:
|
|
337
|
+
"""
|
|
338
|
+
Load, decode and store FastFrames onsets as an numpy array.
|
|
339
|
+
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
# Get timestamps bytes
|
|
343
|
+
bts = self._load_timestamps()
|
|
344
|
+
|
|
345
|
+
# Preallocate array
|
|
346
|
+
frame_onsets = np.empty(self.nframes, dtype=float)
|
|
347
|
+
|
|
348
|
+
# Get first frame timestamps
|
|
349
|
+
frame_onsets[0] = self.tsunix + self.tsfrac
|
|
350
|
+
|
|
351
|
+
# Get the rest
|
|
352
|
+
frame_onsets[1:] = [
|
|
353
|
+
self._decode_fastframe_timestamps(bts, k, endianness=self.byte_order)
|
|
354
|
+
for k in range(self.nframes - 1)
|
|
355
|
+
]
|
|
356
|
+
|
|
357
|
+
# Start at 0
|
|
358
|
+
frame_onsets -= frame_onsets[0]
|
|
359
|
+
|
|
360
|
+
# Store
|
|
361
|
+
self.frame_onsets = frame_onsets
|
|
362
|
+
|
|
363
|
+
return self
|
|
364
|
+
|
|
365
|
+
def load_frames(self, **kwargs) -> Self:
|
|
366
|
+
"""
|
|
367
|
+
Wrap the `_load_frames()` method and stores the loaded array as an attribute.
|
|
368
|
+
|
|
369
|
+
"""
|
|
370
|
+
self.frames = self._load_frames(**kwargs)
|
|
371
|
+
|
|
372
|
+
return self
|
|
373
|
+
|
|
374
|
+
def get_time_frame(self) -> Self:
|
|
375
|
+
"""
|
|
376
|
+
Generate a time vector corresponding to one frame. Require the frames to be
|
|
377
|
+
loaded so we know how many points there are (eg. if pre- and post-charge data
|
|
378
|
+
was included), otherwise assume pre- and post-charge data is not included.
|
|
379
|
+
|
|
380
|
+
"""
|
|
381
|
+
start = self.toffset
|
|
382
|
+
dt = self.tscale
|
|
383
|
+
if hasattr(self, "frames"):
|
|
384
|
+
nsamples = self.frames.shape[0]
|
|
385
|
+
else:
|
|
386
|
+
print(
|
|
387
|
+
"[Warn] Frames not loaded, assuming time vector without pre- and "
|
|
388
|
+
"post-charge."
|
|
389
|
+
)
|
|
390
|
+
nsamples = self.npoints
|
|
391
|
+
|
|
392
|
+
self.time_frame = self._get_time_frame(start, dt, nsamples)
|
|
393
|
+
|
|
394
|
+
return self
|