pyheka 1.0.0__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.
- pyheka/FileFormat/FileFormat_common.py +690 -0
- pyheka/FileFormat/FileFormat_v1000.py +386 -0
- pyheka/FileFormat/FileFormat_v2000.py +316 -0
- pyheka/FileFormat/FileFormat_v9.py +233 -0
- pyheka/FileFormat/__init__.py +2 -0
- pyheka/__init__.py +3 -0
- pyheka/pyHeka.py +349 -0
- pyheka/util.py +35 -0
- pyheka-1.0.0.dist-info/METADATA +165 -0
- pyheka-1.0.0.dist-info/RECORD +13 -0
- pyheka-1.0.0.dist-info/WHEEL +5 -0
- pyheka-1.0.0.dist-info/licenses/LICENSE +661 -0
- pyheka-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
import dataclasses
|
|
3
|
+
import datetime
|
|
4
|
+
import re
|
|
5
|
+
import struct
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def struct_field(fmt, func=True):
|
|
12
|
+
"""Helper to create a dataclass field with struct format metadata."""
|
|
13
|
+
return field(metadata={"fmt": fmt, "func": func})
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def cstr(byt):
|
|
17
|
+
"""Convert C string bytes to python string."""
|
|
18
|
+
ind = byt.find(b"\0")
|
|
19
|
+
if ind == -1:
|
|
20
|
+
return byt
|
|
21
|
+
return byt[:ind].decode("utf-8", errors="ignore")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def cbyte(byt):
|
|
25
|
+
"""Convert C string byte to python integer."""
|
|
26
|
+
if isinstance(byt, int):
|
|
27
|
+
return byt
|
|
28
|
+
try:
|
|
29
|
+
return byt[0]
|
|
30
|
+
except (ValueError, IndexError):
|
|
31
|
+
return byt
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def cchar(byt):
|
|
35
|
+
"""Convert C char byte to python string."""
|
|
36
|
+
return byt.decode("utf-8", errors="ignore")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_UTC = datetime.timezone.utc
|
|
40
|
+
_HEKA_EPOCH = datetime.datetime(1990, 1, 1, tzinfo=_UTC)
|
|
41
|
+
_LOWER_BOUND = datetime.datetime(1990, 1, 1, tzinfo=_UTC)
|
|
42
|
+
_WIN_EPOCH = datetime.datetime(1601, 1, 1, tzinfo=_UTC)
|
|
43
|
+
_MAC_EPOCH = datetime.datetime(1904, 1, 1, tzinfo=_UTC)
|
|
44
|
+
_WINDOWS_OFFSET = 7980681600.0
|
|
45
|
+
_SECONDS_1904_TO_1990 = 2713910400.0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def heka_time_to_datetime(stored_time) -> datetime.datetime:
|
|
49
|
+
"""convert HEKA time to a datetime object"""
|
|
50
|
+
try:
|
|
51
|
+
# Fast path for standard HEKA timestamps (seconds since 1990-01-01)
|
|
52
|
+
if isinstance(stored_time, (int, float)) and 0 <= stored_time < 3500000000:
|
|
53
|
+
return (_HEKA_EPOCH + datetime.timedelta(seconds=stored_time)).replace(
|
|
54
|
+
tzinfo=None
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
c = _HEKA_EPOCH + datetime.timedelta(seconds=stored_time)
|
|
58
|
+
|
|
59
|
+
now = datetime.datetime.now(tz=_UTC)
|
|
60
|
+
upper = now + datetime.timedelta(days=7)
|
|
61
|
+
|
|
62
|
+
if _LOWER_BOUND < c < upper:
|
|
63
|
+
return c.replace(tzinfo=None)
|
|
64
|
+
|
|
65
|
+
candidates = []
|
|
66
|
+
wrap = 2**32
|
|
67
|
+
if stored_time > wrap:
|
|
68
|
+
t = stored_time + _WINDOWS_OFFSET
|
|
69
|
+
candidates.append(_WIN_EPOCH + datetime.timedelta(seconds=t))
|
|
70
|
+
|
|
71
|
+
candidates.append(c)
|
|
72
|
+
|
|
73
|
+
t = stored_time - _SECONDS_1904_TO_1990
|
|
74
|
+
candidates.append(_MAC_EPOCH + datetime.timedelta(seconds=t))
|
|
75
|
+
|
|
76
|
+
for c in candidates:
|
|
77
|
+
if _LOWER_BOUND < c < upper:
|
|
78
|
+
return c.replace(tzinfo=None)
|
|
79
|
+
|
|
80
|
+
return candidates[0].replace(tzinfo=None)
|
|
81
|
+
|
|
82
|
+
except OverflowError:
|
|
83
|
+
return f"Invalid time value: {stored_time}"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def timer_timestamp(total_seconds: float) -> datetime.timedelta:
|
|
87
|
+
"""Converts seconds to a datetime timedelta object"""
|
|
88
|
+
return datetime.timedelta(seconds=total_seconds)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def getFromList(lst, index):
|
|
92
|
+
try:
|
|
93
|
+
return lst[index]
|
|
94
|
+
except IndexError:
|
|
95
|
+
return f"Unknown (value: {index})"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def getAmplifierType(byte):
|
|
99
|
+
return getFromList(
|
|
100
|
+
["EPC7", "EPC8", "EPC9", "EPC10", "EPC10Plus", "EPC10_USB"], byte
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def getADBoard(byte):
|
|
105
|
+
return getFromList(["ITC16", "ITC18", "LIH1600", "LIH 8+8"], byte)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def getRecordingMode(byte):
|
|
109
|
+
return getFromList(
|
|
110
|
+
["InOut", "OnCell", "OutOut", "WholeCell", "CClamp", "VClamp", "NoMode"], byte
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def getDataFormat(byte):
|
|
115
|
+
return getFromList(["int16", "int32", "real32", "real64"], byte)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def getSegmentClass(byte):
|
|
119
|
+
return getFromList(
|
|
120
|
+
["Constant", "Ramp", "Continuous", "ConstSine", "Squarewave", "Chirpwave"], byte
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def getStoreType(byte):
|
|
125
|
+
return getFromList(["NoStore", "Store", "StoreStart", "StoreEnd"], byte)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def getIncrementMode(byte):
|
|
129
|
+
return getFromList(
|
|
130
|
+
[
|
|
131
|
+
"Inc",
|
|
132
|
+
"Dec",
|
|
133
|
+
"IncInterleaved",
|
|
134
|
+
"DecInterleaved",
|
|
135
|
+
"Alternate",
|
|
136
|
+
"LogInc",
|
|
137
|
+
"LogDec",
|
|
138
|
+
"LogIncInterleaved",
|
|
139
|
+
"LogDecInterleaved",
|
|
140
|
+
"LogAlternate",
|
|
141
|
+
"Toggle",
|
|
142
|
+
],
|
|
143
|
+
byte,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def getSourceType(byte):
|
|
148
|
+
return getFromList(["Constant", "Hold", "Parameter"], byte)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def getAmplifierGain(byte):
|
|
152
|
+
"""Units: V/A"""
|
|
153
|
+
return getFromList(
|
|
154
|
+
[
|
|
155
|
+
1e-3 / 1e-12 * x
|
|
156
|
+
for x in [
|
|
157
|
+
0.005,
|
|
158
|
+
0.010,
|
|
159
|
+
0.020,
|
|
160
|
+
0.050,
|
|
161
|
+
0.1,
|
|
162
|
+
0.2,
|
|
163
|
+
0.5,
|
|
164
|
+
1,
|
|
165
|
+
2,
|
|
166
|
+
5,
|
|
167
|
+
10,
|
|
168
|
+
20,
|
|
169
|
+
50,
|
|
170
|
+
100,
|
|
171
|
+
200,
|
|
172
|
+
500,
|
|
173
|
+
1000,
|
|
174
|
+
2000,
|
|
175
|
+
]
|
|
176
|
+
],
|
|
177
|
+
byte,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def convertDataFormatToNP(dataFormat):
|
|
182
|
+
d = {
|
|
183
|
+
"int16": np.int16,
|
|
184
|
+
"int32": np.int32,
|
|
185
|
+
"real32": np.float32,
|
|
186
|
+
"real64": np.float64,
|
|
187
|
+
}
|
|
188
|
+
return d[dataFormat]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def getCSlowRange(byte):
|
|
192
|
+
return getFromList(["Off", "30 pF", "100 pF", "1000 pF"], byte)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def getClampMode(byte):
|
|
196
|
+
return getFromList(["TestMode", "VCMode", "CCMode", "NoMode"], byte)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def getAmplMode(byte):
|
|
200
|
+
return getFromList(["Any", "VCMode", "CCMode", "IDensityMode"], byte)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def getLeakHoldMode(byte):
|
|
204
|
+
return getFromList(["Labs", "Lrel", "LabsLH", "LrelLH"], byte)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def getLeakStoreType(byte):
|
|
208
|
+
return getFromList(["None", "StoreAvg", "StoreEach", "NoStore"], byte)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def getADCMode(byte):
|
|
212
|
+
return getFromList(["AdcOff", "Analog", "Digitals", "Digital", "AdcVirtual"], byte)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def convertDataKind(byte):
|
|
216
|
+
return {
|
|
217
|
+
"IsLittleEndian": bool(byte & 1),
|
|
218
|
+
"IsLeak": bool(byte & 2),
|
|
219
|
+
"IsVirtual": bool(byte & 4),
|
|
220
|
+
"IsImon": bool(byte & 8),
|
|
221
|
+
"IsVmon": bool(byte & 16),
|
|
222
|
+
"Clip": bool(byte & 32),
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def convertStimToDacID(byte):
|
|
227
|
+
return {
|
|
228
|
+
"UseStimScale": bool(byte & 1),
|
|
229
|
+
"UseRelative": bool(byte & 2),
|
|
230
|
+
"UseFileTemplate": bool(byte & 4),
|
|
231
|
+
"UseForLockIn": bool(byte & 8),
|
|
232
|
+
"UseForWavelength": bool(byte & 16),
|
|
233
|
+
"UseScaling": bool(byte & 32),
|
|
234
|
+
"UseForChirp": bool(byte & 64),
|
|
235
|
+
"UseForImaging": bool(byte & 128),
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def getSquareKind(byte):
|
|
240
|
+
return getFromList(["Common Frequency"], byte)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def getChirpKind(byte):
|
|
244
|
+
return getFromList(["Linear", "Exponential", "Spectroscopic"], byte)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def getTriggerKind(byte):
|
|
248
|
+
return getFromList(["None", "Series", "Sweep", "SweepNoLeak"], byte)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class Struct:
|
|
252
|
+
_unpack_plan = None
|
|
253
|
+
|
|
254
|
+
def __init__(self, data, endian="<"):
|
|
255
|
+
cls = self.__class__
|
|
256
|
+
if cls.__dict__.get("_unpack_plan") is None:
|
|
257
|
+
cls._init_struct_formats()
|
|
258
|
+
|
|
259
|
+
if not isinstance(data, (bytes, bytearray, memoryview)):
|
|
260
|
+
data = data.read(cls._le_size)
|
|
261
|
+
|
|
262
|
+
items = (cls._le_struct if endian == "<" else cls._be_struct).unpack(data)
|
|
263
|
+
|
|
264
|
+
d = self.__dict__
|
|
265
|
+
for name, getter, transform in cls._unpack_plan:
|
|
266
|
+
if isinstance(getter, tuple):
|
|
267
|
+
val = items[getter[0] : getter[1]]
|
|
268
|
+
else:
|
|
269
|
+
val = items[getter]
|
|
270
|
+
|
|
271
|
+
if transform is not None:
|
|
272
|
+
if isinstance(transform, tuple):
|
|
273
|
+
substr, func = transform
|
|
274
|
+
val = substr(val, endian)
|
|
275
|
+
if func is not True and callable(func):
|
|
276
|
+
val = func(val)
|
|
277
|
+
elif callable(transform):
|
|
278
|
+
val = transform(val)
|
|
279
|
+
|
|
280
|
+
d[name] = val
|
|
281
|
+
|
|
282
|
+
@classmethod
|
|
283
|
+
def _init_struct_formats(cls):
|
|
284
|
+
# Prevent subclass caching conflicts
|
|
285
|
+
if cls.__dict__.get("_unpack_plan") is not None:
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
if not dataclasses.is_dataclass(cls):
|
|
289
|
+
raise TypeError(
|
|
290
|
+
f"Class '{cls.__name__}' must be decorated with @dataclass."
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
fmt = ""
|
|
294
|
+
cls._fields_parsed = []
|
|
295
|
+
plan = []
|
|
296
|
+
|
|
297
|
+
item_idx = 0
|
|
298
|
+
for f in dataclasses.fields(cls):
|
|
299
|
+
name = f.name
|
|
300
|
+
ifmt = f.metadata.get("fmt")
|
|
301
|
+
if ifmt is None:
|
|
302
|
+
continue
|
|
303
|
+
func = f.metadata.get("func", True)
|
|
304
|
+
|
|
305
|
+
if isinstance(ifmt, type) and issubclass(ifmt, Struct):
|
|
306
|
+
func = (ifmt, func)
|
|
307
|
+
ifmt = f"{ifmt.size()}s"
|
|
308
|
+
elif re.match(r"\d*[xcbB?hHiIlLqQfdspP]", ifmt) is None:
|
|
309
|
+
raise TypeError(f'Unsupported format string "{ifmt}"')
|
|
310
|
+
|
|
311
|
+
cls._fields_parsed.append((name, ifmt, func))
|
|
312
|
+
fmt += ifmt
|
|
313
|
+
|
|
314
|
+
if len(ifmt) == 1 or ifmt[-1] == "s":
|
|
315
|
+
getter = item_idx
|
|
316
|
+
item_idx += 1
|
|
317
|
+
else:
|
|
318
|
+
n = int(ifmt[:-1])
|
|
319
|
+
getter = (item_idx, item_idx + n)
|
|
320
|
+
item_idx += n
|
|
321
|
+
|
|
322
|
+
if func is None or func is False:
|
|
323
|
+
continue
|
|
324
|
+
|
|
325
|
+
transform = None if func is True else func
|
|
326
|
+
plan.append((name, getter, transform))
|
|
327
|
+
|
|
328
|
+
cls._unpack_plan = plan
|
|
329
|
+
cls._le_struct = struct.Struct("<" + fmt)
|
|
330
|
+
cls._be_struct = struct.Struct(">" + fmt)
|
|
331
|
+
cls._le_size = cls._le_struct.size
|
|
332
|
+
|
|
333
|
+
if hasattr(cls, "size_check") and cls.size_check is not None:
|
|
334
|
+
assert cls._le_struct.size == cls.size_check, (
|
|
335
|
+
f"{cls.size_check} expected vs. {cls._le_struct.size}"
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
@classmethod
|
|
339
|
+
def size(cls):
|
|
340
|
+
if cls.__dict__.get("_unpack_plan") is None:
|
|
341
|
+
cls._init_struct_formats()
|
|
342
|
+
return cls._le_size
|
|
343
|
+
|
|
344
|
+
@classmethod
|
|
345
|
+
def array(cls, x):
|
|
346
|
+
return type(
|
|
347
|
+
f"{cls.__name__}[{x}]",
|
|
348
|
+
(StructArray,),
|
|
349
|
+
{"item_struct": cls, "array_size": x},
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
def __str__(self, indent=0):
|
|
353
|
+
cls = self.__class__
|
|
354
|
+
cls._init_struct_formats()
|
|
355
|
+
indent_str = " " * indent
|
|
356
|
+
r = indent_str + f"{self.__class__.__name__}(\n"
|
|
357
|
+
for name, _, _ in cls._fields_parsed:
|
|
358
|
+
if hasattr(self, name):
|
|
359
|
+
v = getattr(self, name)
|
|
360
|
+
if isinstance(v, Struct):
|
|
361
|
+
r += (
|
|
362
|
+
indent_str
|
|
363
|
+
+ f" {name} = {v.__str__(indent=indent + 1).lstrip()}\n"
|
|
364
|
+
)
|
|
365
|
+
else:
|
|
366
|
+
r += indent_str + f" {name} = {v!r}\n"
|
|
367
|
+
r += indent_str + ")"
|
|
368
|
+
return r
|
|
369
|
+
|
|
370
|
+
def __repr__(self, indent=0):
|
|
371
|
+
return self.__str__(indent)
|
|
372
|
+
|
|
373
|
+
def get_fields(self):
|
|
374
|
+
cls = self.__class__
|
|
375
|
+
cls._init_struct_formats()
|
|
376
|
+
fields_dict = collections.OrderedDict()
|
|
377
|
+
for name, _, _ in cls._fields_parsed:
|
|
378
|
+
if hasattr(self, name):
|
|
379
|
+
v = getattr(self, name)
|
|
380
|
+
if isinstance(v, StructArray):
|
|
381
|
+
fields_dict[name] = [x.get_fields() for x in v.array]
|
|
382
|
+
elif isinstance(v, Struct):
|
|
383
|
+
fields_dict[name] = v.get_fields()
|
|
384
|
+
else:
|
|
385
|
+
fields_dict[name] = v
|
|
386
|
+
return fields_dict
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class StructArray(Struct):
|
|
390
|
+
item_struct = None
|
|
391
|
+
array_size = None
|
|
392
|
+
|
|
393
|
+
def __init__(self, data, endian="<"):
|
|
394
|
+
if not isinstance(data, (str, bytes)):
|
|
395
|
+
data = data.read(self.size())
|
|
396
|
+
items = []
|
|
397
|
+
isize = self.item_struct.size()
|
|
398
|
+
for i in range(self.array_size):
|
|
399
|
+
d = data[i * isize : (i + 1) * isize]
|
|
400
|
+
items.append(self.item_struct(d, endian))
|
|
401
|
+
self.array = items
|
|
402
|
+
|
|
403
|
+
def __getitem__(self, i):
|
|
404
|
+
return self.array[i]
|
|
405
|
+
|
|
406
|
+
def __len__(self):
|
|
407
|
+
return len(self.array)
|
|
408
|
+
|
|
409
|
+
def __iter__(self):
|
|
410
|
+
return iter(self.array)
|
|
411
|
+
|
|
412
|
+
@classmethod
|
|
413
|
+
def size(cls):
|
|
414
|
+
return cls.item_struct.size() * cls.array_size
|
|
415
|
+
|
|
416
|
+
def __str__(self, indent=0):
|
|
417
|
+
return self.__repr__(indent)
|
|
418
|
+
|
|
419
|
+
def __repr__(self, indent=0):
|
|
420
|
+
r = " " * indent + f"{self.__class__.__name__}(\n"
|
|
421
|
+
for item in self.array:
|
|
422
|
+
r += item.__repr__(indent=indent + 1) + ",\n"
|
|
423
|
+
r += " " * indent + ")"
|
|
424
|
+
return r
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class TreeNode(Struct):
|
|
428
|
+
def __init__(self, fh, pul, level=0):
|
|
429
|
+
self.level = level
|
|
430
|
+
self.children = []
|
|
431
|
+
endian = pul.endian
|
|
432
|
+
|
|
433
|
+
realsize = pul.level_sizes[level]
|
|
434
|
+
structsize = self.size()
|
|
435
|
+
data = fh.read(realsize)
|
|
436
|
+
if len(data) < structsize:
|
|
437
|
+
data = data + b"\0" * (structsize - len(data))
|
|
438
|
+
else:
|
|
439
|
+
data = data[:structsize]
|
|
440
|
+
|
|
441
|
+
Struct.__init__(self, data, endian)
|
|
442
|
+
|
|
443
|
+
nchild = struct.unpack(endian + "i", fh.read(4))[0]
|
|
444
|
+
|
|
445
|
+
level += 1
|
|
446
|
+
if level >= len(pul.rectypes):
|
|
447
|
+
return
|
|
448
|
+
child_rectype = pul.rectypes[level]
|
|
449
|
+
for i in range(nchild):
|
|
450
|
+
self.children.append(child_rectype(fh, pul, level))
|
|
451
|
+
|
|
452
|
+
def __getitem__(self, i):
|
|
453
|
+
return self.children[i]
|
|
454
|
+
|
|
455
|
+
def __len__(self):
|
|
456
|
+
return len(self.children)
|
|
457
|
+
|
|
458
|
+
def __iter__(self):
|
|
459
|
+
return self.children.__iter__()
|
|
460
|
+
|
|
461
|
+
def __repr__(self, indent=0):
|
|
462
|
+
ind = " " * indent
|
|
463
|
+
srep = Struct.__repr__(self, indent)[:-1]
|
|
464
|
+
srep += ind + f" children = {len(self)},\n"
|
|
465
|
+
srep += ind + ")"
|
|
466
|
+
return srep
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class RootNode(TreeNode):
|
|
470
|
+
"""Base class for root tree nodes (Pulsed, Amplifier, Stimulus)."""
|
|
471
|
+
|
|
472
|
+
def __init__(self, bundle, offset=0, size=None):
|
|
473
|
+
fh = bundle.fh
|
|
474
|
+
fh.seek(offset)
|
|
475
|
+
|
|
476
|
+
magic = fh.read(4)
|
|
477
|
+
if magic == b"eerT":
|
|
478
|
+
self.endian = "<"
|
|
479
|
+
elif magic == b"Tree":
|
|
480
|
+
self.endian = ">"
|
|
481
|
+
else:
|
|
482
|
+
raise RuntimeError(f"Bad file magic: {magic}")
|
|
483
|
+
|
|
484
|
+
levels = struct.unpack(self.endian + "i", fh.read(4))[0]
|
|
485
|
+
|
|
486
|
+
self.level_sizes = [
|
|
487
|
+
struct.unpack(self.endian + "i", fh.read(4))[0] for _ in range(levels)
|
|
488
|
+
]
|
|
489
|
+
|
|
490
|
+
super().__init__(fh, self)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
@dataclass(init=False, repr=False)
|
|
494
|
+
class UserParamDescrType(Struct):
|
|
495
|
+
Name: str = struct_field("32s", cstr)
|
|
496
|
+
Unit: str = struct_field("8s", cstr)
|
|
497
|
+
size_check = 40
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
@dataclass(init=False, repr=False)
|
|
501
|
+
class LockInParams(Struct):
|
|
502
|
+
ExtCalPhase: float = struct_field("d")
|
|
503
|
+
ExtCalAtten: float = struct_field("d")
|
|
504
|
+
PLPhase: float = struct_field("d")
|
|
505
|
+
PLPhaseY1: float = struct_field("d")
|
|
506
|
+
PLPhaseY2: float = struct_field("d")
|
|
507
|
+
UsedPhaseShift: float = struct_field("d")
|
|
508
|
+
UsedAttenuation: float = struct_field("d")
|
|
509
|
+
Spares2: str = struct_field("8s", None)
|
|
510
|
+
ExtCalValid: bool = struct_field("?")
|
|
511
|
+
PLPhaseValid: bool = struct_field("?")
|
|
512
|
+
LockInMode: int = struct_field("b")
|
|
513
|
+
CalMode: int = struct_field("b")
|
|
514
|
+
Spares: str = struct_field("28s", None)
|
|
515
|
+
size_check = 96
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
@dataclass(init=False, repr=False)
|
|
519
|
+
class StimSegmentRecord(TreeNode):
|
|
520
|
+
Mark: int = struct_field("i")
|
|
521
|
+
Class: int = struct_field("b", getSegmentClass)
|
|
522
|
+
StoreKind: int = struct_field("b", getStoreType)
|
|
523
|
+
VoltageIncMode: int = struct_field("b", getIncrementMode)
|
|
524
|
+
DurationIncMode: int = struct_field("b", getIncrementMode)
|
|
525
|
+
Voltage: float = struct_field("d")
|
|
526
|
+
VoltageSource: int = struct_field("i", getSourceType)
|
|
527
|
+
DeltaVFactor: float = struct_field("d")
|
|
528
|
+
DeltaVIncrement: float = struct_field("d")
|
|
529
|
+
Duration: float = struct_field("d")
|
|
530
|
+
DurationSource: int = struct_field("i", getSourceType)
|
|
531
|
+
DeltaTFactor: float = struct_field("d")
|
|
532
|
+
DeltaTIncrement: float = struct_field("d")
|
|
533
|
+
Filler1: int = struct_field("i", None)
|
|
534
|
+
CRC: int = struct_field("I")
|
|
535
|
+
ScanRate: float = struct_field("d")
|
|
536
|
+
size_check = 80
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
@dataclass(init=False, repr=False)
|
|
540
|
+
class ChannelRecord(TreeNode):
|
|
541
|
+
Mark: int = struct_field("i")
|
|
542
|
+
LinkedChannel: int = struct_field("i")
|
|
543
|
+
CompressionFactor: int = struct_field("i")
|
|
544
|
+
YUnit: str = struct_field("8s", cstr)
|
|
545
|
+
AdcChannel: int = struct_field("h")
|
|
546
|
+
AdcMode: int = struct_field("b", getADCMode)
|
|
547
|
+
DoWrite: bool = struct_field("?")
|
|
548
|
+
LeakStore: int = struct_field("b", getLeakStoreType)
|
|
549
|
+
AmplMode: int = struct_field("b", getAmplMode)
|
|
550
|
+
OwnSegTime: bool = struct_field("?")
|
|
551
|
+
SetLastSegVmemb: bool = struct_field("?")
|
|
552
|
+
DacChannel: int = struct_field("h")
|
|
553
|
+
DacMode: int = struct_field("b")
|
|
554
|
+
HasLockInSquare: int = struct_field("b")
|
|
555
|
+
RelevantXSegment: int = struct_field("i")
|
|
556
|
+
RelevantYSegment: int = struct_field("i")
|
|
557
|
+
DacUnit: str = struct_field("8s", cstr)
|
|
558
|
+
Holding: float = struct_field("d")
|
|
559
|
+
LeakHolding: float = struct_field("d")
|
|
560
|
+
LeakSize: float = struct_field("d")
|
|
561
|
+
LeakHoldMode: int = struct_field("b", getLeakHoldMode)
|
|
562
|
+
LeakAlternate: bool = struct_field("?")
|
|
563
|
+
AltLeakAveraging: bool = struct_field("?")
|
|
564
|
+
LeakPulseOn: bool = struct_field("?")
|
|
565
|
+
StimToDacID: int = struct_field("h", convertStimToDacID)
|
|
566
|
+
CompressionMode: int = struct_field("h")
|
|
567
|
+
CompressionSkip: int = struct_field("i")
|
|
568
|
+
DacBit: int = struct_field("h")
|
|
569
|
+
HasLockInSine: bool = struct_field("?")
|
|
570
|
+
BreakMode: int = struct_field("b")
|
|
571
|
+
ZeroSeg: int = struct_field("i")
|
|
572
|
+
StimSweep: int = struct_field("i")
|
|
573
|
+
Sine_Cycle: float = struct_field("d")
|
|
574
|
+
Sine_Amplitude: float = struct_field("d")
|
|
575
|
+
LockIn_VReversal: float = struct_field("d")
|
|
576
|
+
Chirp_StartFreq: float = struct_field("d")
|
|
577
|
+
Chirp_EndFreq: float = struct_field("d")
|
|
578
|
+
Chirp_MinPoints: float = struct_field("d")
|
|
579
|
+
Square_NegAmpl: float = struct_field("d")
|
|
580
|
+
Square_DurFactor: float = struct_field("d")
|
|
581
|
+
LockIn_Skip: int = struct_field("i")
|
|
582
|
+
Photo_MaxCycles: int = struct_field("i")
|
|
583
|
+
Photo_SegmentNo: int = struct_field("i")
|
|
584
|
+
LockIn_AvgCycles: int = struct_field("i")
|
|
585
|
+
Imaging_RoiNo: int = struct_field("i")
|
|
586
|
+
Chirp_Skip: int = struct_field("i")
|
|
587
|
+
Chirp_Amplitude: float = struct_field("d")
|
|
588
|
+
Photo_Adapt: int = struct_field("b")
|
|
589
|
+
Sine_Kind: int = struct_field("b")
|
|
590
|
+
Chirp_PreChirp: int = struct_field("b")
|
|
591
|
+
Sine_Source: int = struct_field("b")
|
|
592
|
+
Square_NegSource: int = struct_field("b")
|
|
593
|
+
Square_PosSource: int = struct_field("b")
|
|
594
|
+
Chirp_Kind: int = struct_field("b", getChirpKind)
|
|
595
|
+
Chirp_Source: int = struct_field("b")
|
|
596
|
+
DacOffset: float = struct_field("d")
|
|
597
|
+
AdcOffset: float = struct_field("d")
|
|
598
|
+
TraceMathFormat: int = struct_field("b")
|
|
599
|
+
HasChirp: bool = struct_field("?")
|
|
600
|
+
Square_Kind: int = struct_field("b", getSquareKind)
|
|
601
|
+
Filler1: bytes = struct_field("5c", None)
|
|
602
|
+
Square_BaseIncr: float = struct_field("d")
|
|
603
|
+
Square_Cycle: float = struct_field("d")
|
|
604
|
+
Square_PosAmpl: float = struct_field("d")
|
|
605
|
+
CompressionOffset: int = struct_field("i")
|
|
606
|
+
PhotoMode: int = struct_field("i")
|
|
607
|
+
BreakLevel: float = struct_field("d")
|
|
608
|
+
TraceMath: str = struct_field("128s", cstr)
|
|
609
|
+
Filler2: int = struct_field("i", None)
|
|
610
|
+
CRC: int = struct_field("I")
|
|
611
|
+
size_check = 400
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
@dataclass(init=False, repr=False)
|
|
615
|
+
class AmpSeriesRecord(TreeNode):
|
|
616
|
+
Mark: int = struct_field("i")
|
|
617
|
+
StateCount: int = struct_field("i")
|
|
618
|
+
Filler1: int = struct_field("i", None)
|
|
619
|
+
CRC: int = struct_field("I")
|
|
620
|
+
size_check = 16
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
@dataclass(init=False, repr=False)
|
|
624
|
+
class Pulsed(RootNode):
|
|
625
|
+
Version: int = struct_field("i")
|
|
626
|
+
Mark: int = struct_field("i")
|
|
627
|
+
VersionName: str = struct_field("32s", cstr)
|
|
628
|
+
AuxFileName: str = struct_field("80s", cstr)
|
|
629
|
+
RootText: str = struct_field("400s", cstr)
|
|
630
|
+
StartTime: float = struct_field("d", heka_time_to_datetime)
|
|
631
|
+
MaxSamples: int = struct_field("i")
|
|
632
|
+
CRC: int = struct_field("I")
|
|
633
|
+
Features: int = struct_field("h")
|
|
634
|
+
Filler1: int = struct_field("h", None)
|
|
635
|
+
Filler2: int = struct_field("i", None)
|
|
636
|
+
TcEnumerator: int = struct_field("32h")
|
|
637
|
+
TcKind: int = struct_field("32b")
|
|
638
|
+
size_check = 640
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
@dataclass(init=False, repr=False)
|
|
642
|
+
class Amplifier(RootNode):
|
|
643
|
+
Version: int = struct_field("i")
|
|
644
|
+
Mark: int = struct_field("i")
|
|
645
|
+
VersionName: str = struct_field("32s", cstr)
|
|
646
|
+
AmplifierName: str = struct_field("32s", cstr)
|
|
647
|
+
Amplifier: int = struct_field("b")
|
|
648
|
+
ADBoard: int = struct_field("b")
|
|
649
|
+
Creator: int = struct_field("b")
|
|
650
|
+
Filler1: bytes = struct_field("c", None)
|
|
651
|
+
CRC: int = struct_field("I")
|
|
652
|
+
size_check = 80
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
@dataclass(init=False, repr=False)
|
|
656
|
+
class Stimulus(RootNode):
|
|
657
|
+
Version: int = struct_field("i")
|
|
658
|
+
Mark: int = struct_field("i")
|
|
659
|
+
VersionName: str = struct_field("32s", cstr)
|
|
660
|
+
MaxSamples: int = struct_field("i")
|
|
661
|
+
Filler1: int = struct_field("i", None)
|
|
662
|
+
Params: float = struct_field("10d")
|
|
663
|
+
ParamText: bytes = struct_field("320c", None)
|
|
664
|
+
Reserved: str = struct_field("128s", cstr)
|
|
665
|
+
Filler2: int = struct_field("i", None)
|
|
666
|
+
Reserved2: str = struct_field("560s", None)
|
|
667
|
+
CRC: int = struct_field("I")
|
|
668
|
+
size_check = 1144
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
class Data:
|
|
672
|
+
def __init__(self, bundle, offset=0, size=None):
|
|
673
|
+
self.bundle = bundle
|
|
674
|
+
self.offset = offset
|
|
675
|
+
|
|
676
|
+
def __getitem__(self, *args):
|
|
677
|
+
index = args[0]
|
|
678
|
+
if not isinstance(index, tuple) or len(index) != 4:
|
|
679
|
+
raise IndexError(
|
|
680
|
+
"Index must be a tuple of length 4 (group, series, sweep, trace)"
|
|
681
|
+
)
|
|
682
|
+
pul = self.bundle.pul
|
|
683
|
+
trace = pul[index[0]][index[1]][index[2]][index[3]]
|
|
684
|
+
fh = self.bundle.fh
|
|
685
|
+
fh.seek(trace.Data)
|
|
686
|
+
dtype = np.dtype(convertDataFormatToNP(trace.DataFormat))
|
|
687
|
+
if not trace.DataKind["IsLittleEndian"]:
|
|
688
|
+
dtype = dtype.newbyteorder(">")
|
|
689
|
+
data = np.fromfile(fh, dtype=dtype, count=trace.DataPoints)
|
|
690
|
+
return data * trace.DataScaler
|