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.
@@ -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