pyxdaq 0.1.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.
- pyxdaq/__init__.py +9 -0
- pyxdaq/board.py +98 -0
- pyxdaq/constants.py +309 -0
- pyxdaq/datablock.py +155 -0
- pyxdaq/gen2.py +61 -0
- pyxdaq/impedance.py +206 -0
- pyxdaq/intan_headstage.py +223 -0
- pyxdaq/register.py +191 -0
- pyxdaq/resources/__init__.py +12 -0
- pyxdaq/resources/config/isa_rhd.json +46 -0
- pyxdaq/resources/config/isa_rhs.json +84 -0
- pyxdaq/resources/config/reg_rhd.json +141 -0
- pyxdaq/resources/config/reg_rhs.json +170 -0
- pyxdaq/rhd_driver.py +127 -0
- pyxdaq/rhs_driver.py +155 -0
- pyxdaq/stim.py +100 -0
- pyxdaq/tools/self_diagnosis.py +201 -0
- pyxdaq/utils.py +17 -0
- pyxdaq/xdaq.py +1246 -0
- pyxdaq-0.1.1.dist-info/METADATA +15 -0
- pyxdaq-0.1.1.dist-info/RECORD +25 -0
- pyxdaq-0.1.1.dist-info/WHEEL +5 -0
- pyxdaq-0.1.1.dist-info/entry_points.txt +2 -0
- pyxdaq-0.1.1.dist-info/licenses/LICENSE +9 -0
- pyxdaq-0.1.1.dist-info/top_level.txt +1 -0
pyxdaq/__init__.py
ADDED
pyxdaq/board.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Callable, List
|
|
7
|
+
|
|
8
|
+
from pylibxdaq import pyxdaq_device
|
|
9
|
+
from pylibxdaq.managers import manager_paths
|
|
10
|
+
|
|
11
|
+
from .constants import EndPoints
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class DeviceSetup:
|
|
18
|
+
manager_path: Path
|
|
19
|
+
manager_info: dict
|
|
20
|
+
options: dict
|
|
21
|
+
|
|
22
|
+
def with_mode(self, mode: str):
|
|
23
|
+
info = deepcopy(self)
|
|
24
|
+
info.options['mode'] = mode
|
|
25
|
+
return info
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Board:
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def list_devices(cls) -> List[DeviceSetup]:
|
|
32
|
+
devices = []
|
|
33
|
+
for manager_path in manager_paths:
|
|
34
|
+
manager = pyxdaq_device.get_device_manager(str(manager_path))
|
|
35
|
+
info = manager.info()
|
|
36
|
+
for device_options in json.loads(manager.list_devices()):
|
|
37
|
+
devices.append(DeviceSetup(manager_path, info, device_options))
|
|
38
|
+
return sorted(devices, key=lambda x: str(x.options))
|
|
39
|
+
|
|
40
|
+
def __init__(self, device_info: DeviceSetup):
|
|
41
|
+
manager: pyxdaq_device.DeviceManager = pyxdaq_device.get_device_manager(
|
|
42
|
+
str(device_info.manager_path)
|
|
43
|
+
)
|
|
44
|
+
self.dev = manager.create_device(json.dumps(device_info.options))
|
|
45
|
+
status = json.loads(self.dev.get_status())
|
|
46
|
+
if status['Mode'] == 'rhd':
|
|
47
|
+
self.rhs = False
|
|
48
|
+
elif status['Mode'] == 'rhs':
|
|
49
|
+
self.rhs = True
|
|
50
|
+
|
|
51
|
+
def __enter__(self):
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
55
|
+
del self.dev
|
|
56
|
+
|
|
57
|
+
def __getattr__(self, name: str):
|
|
58
|
+
return getattr(self.dev, name)
|
|
59
|
+
|
|
60
|
+
def GetWireOutValue(self, addr: EndPoints, update: bool = True) -> int:
|
|
61
|
+
if update:
|
|
62
|
+
return self.dev.get_register_sync(addr.value)
|
|
63
|
+
else:
|
|
64
|
+
return self.dev.get_register(addr.value)
|
|
65
|
+
|
|
66
|
+
def SetWireInValue(
|
|
67
|
+
self, addr: EndPoints, value: int, mask: int = 0xFFFFFFFF, update: bool = True
|
|
68
|
+
):
|
|
69
|
+
if update:
|
|
70
|
+
self.dev.set_register_sync(addr.value, value, mask)
|
|
71
|
+
else:
|
|
72
|
+
self.dev.set_register(addr.value, value, mask)
|
|
73
|
+
|
|
74
|
+
def ActivateTriggerIn(self, addr: EndPoints, value: int):
|
|
75
|
+
self.dev.trigger(addr.value, value)
|
|
76
|
+
|
|
77
|
+
def WriteToBlockPipeIn(self, epAddr: EndPoints, data: bytearray):
|
|
78
|
+
return self.dev.write(epAddr.value, data)
|
|
79
|
+
|
|
80
|
+
def ReadFromBlockPipeOut(self, epAddr: EndPoints, data: bytearray):
|
|
81
|
+
return self.dev.read(epAddr.value, data)
|
|
82
|
+
|
|
83
|
+
def start_receiving_aligned_buffer(
|
|
84
|
+
self,
|
|
85
|
+
epAddr: EndPoints,
|
|
86
|
+
alignment: int,
|
|
87
|
+
callback: Callable[[pyxdaq_device.ManagedBuffer], None],
|
|
88
|
+
chunk_size: int = 0
|
|
89
|
+
):
|
|
90
|
+
return self.dev.start_aligned_read_stream(
|
|
91
|
+
epAddr.value, alignment, callback, chunk_size=chunk_size
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def SendTrig(
|
|
95
|
+
self, trig: EndPoints, bit: int, epAddr: EndPoints, value: int, mask: int = 0xFFFFFFFF
|
|
96
|
+
):
|
|
97
|
+
self.dev.set_register_sync(epAddr.value, value, mask)
|
|
98
|
+
self.dev.trigger(trig.value, bit)
|
pyxdaq/constants.py
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EndPoints(Enum):
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class XDAQWireOut(EndPoints):
|
|
9
|
+
Serial = 0x32
|
|
10
|
+
Hdmi = 0x31
|
|
11
|
+
Fpga = 0x3f
|
|
12
|
+
Daio = 0x30
|
|
13
|
+
Oled = 0x33
|
|
14
|
+
Vido = 0x34
|
|
15
|
+
Expr = 0x35
|
|
16
|
+
Xprt = 0x36
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RHD(EndPoints):
|
|
20
|
+
TTL_override = 0x1400
|
|
21
|
+
WireInResetRun = 0x00
|
|
22
|
+
WireInMaxTimeStep = 0x01
|
|
23
|
+
WireInSerialDigitalInCntl = 0x02
|
|
24
|
+
WireInDataFreqPll = 0x03
|
|
25
|
+
WireInMisoDelay = 0x04
|
|
26
|
+
WireInCmdRamAddr = 0x05
|
|
27
|
+
WireInCmdRamBank = 0x06
|
|
28
|
+
WireInCmdRamData = 0x07
|
|
29
|
+
WireInAuxCmdBank1 = 0x08
|
|
30
|
+
WireInAuxCmdBank2 = 0x09
|
|
31
|
+
WireInAuxCmdBank3 = 0x0a
|
|
32
|
+
WireInAuxCmdLength = 0x0b
|
|
33
|
+
WireInAuxCmdLoop = 0x0c
|
|
34
|
+
WireInLedDisplay = 0x0d
|
|
35
|
+
WireInDacReref = 0x0e
|
|
36
|
+
WireInDataStreamEn = 0x14
|
|
37
|
+
WireInTtlOut = 0x15
|
|
38
|
+
WireInTtlOut32 = 0x10
|
|
39
|
+
WireInDacSource1 = 0x16
|
|
40
|
+
WireInDacSource2 = 0x17
|
|
41
|
+
WireInDacSource3 = 0x18
|
|
42
|
+
WireInDacSource4 = 0x19
|
|
43
|
+
WireInDacSource5 = 0x1a
|
|
44
|
+
WireInDacSource6 = 0x1b
|
|
45
|
+
WireInDacSource7 = 0x1c
|
|
46
|
+
WireInDacSource8 = 0x1d
|
|
47
|
+
WireInDacSource9 = 0x48
|
|
48
|
+
WireInDacSource10 = 0x49
|
|
49
|
+
WireInDacSource11 = 0x4A
|
|
50
|
+
WireInDacSource12 = 0x4B
|
|
51
|
+
WireInDacManual = 0x1e
|
|
52
|
+
WireInMultiUse = 0x1f
|
|
53
|
+
TrigInConfig = 0x40
|
|
54
|
+
TrigInSpiStart = 0x41
|
|
55
|
+
TrigInDacConfig = 0x42
|
|
56
|
+
WireOutNumWords = 0x20
|
|
57
|
+
WireOutSerialDigitalIn = 0x21
|
|
58
|
+
WireOutSpiRunning = 0x22
|
|
59
|
+
WireOutTtlIn = 0x23
|
|
60
|
+
WireOutDataClkLocked = 0x24
|
|
61
|
+
WireOutBoardMode = 0x25
|
|
62
|
+
WireOutBoardId = 0x3e
|
|
63
|
+
WireOutBoardVersion = 0x3f
|
|
64
|
+
PipeOutData = 0xa0
|
|
65
|
+
TrigVStim = 0x40
|
|
66
|
+
TrigMCU = 0x48
|
|
67
|
+
WireOutXDAQStatus = 0x22
|
|
68
|
+
WireInMCUControl = 0x02
|
|
69
|
+
PipeInFirmware = 0x88
|
|
70
|
+
PipeOutFirmware = 0xb0
|
|
71
|
+
WireInSetMode = 0x00
|
|
72
|
+
Enable32bitDIO = 0x12
|
|
73
|
+
PipeInDAC1 = 0x90
|
|
74
|
+
PipeInDAC2 = 0x91
|
|
75
|
+
PipeInDAC3 = 0x92
|
|
76
|
+
PipeInDAC4 = 0x93
|
|
77
|
+
PipeInDAC5 = 0x94
|
|
78
|
+
PipeInDAC6 = 0x95
|
|
79
|
+
PipeInDAC7 = 0x96
|
|
80
|
+
PipeInDAC8 = 0x97
|
|
81
|
+
PipeInDAC9 = 0x98
|
|
82
|
+
PipeInDAC10 = 0x99
|
|
83
|
+
PipeInDAC11 = 0x9A
|
|
84
|
+
PipeInDAC12 = 0x9B
|
|
85
|
+
ExpanderInfo = 0x35
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class RHS(EndPoints):
|
|
89
|
+
TTL_override = 0x1400
|
|
90
|
+
WireInResetRun = 0x00
|
|
91
|
+
WireInMaxTimeStep = 0x01
|
|
92
|
+
WireInMaxTimeStepLsb = 0x01
|
|
93
|
+
WireInMaxTimeStepMsb = 0x02
|
|
94
|
+
WireInDataFreqPll = 0x03
|
|
95
|
+
WireInMisoDelay = 0x04
|
|
96
|
+
WireInStimCmdMode = 0x05
|
|
97
|
+
WireInStimRegAddr = 0x06
|
|
98
|
+
WireInStimRegWord = 0x07
|
|
99
|
+
WireInDcAmpConvert = 0x08
|
|
100
|
+
WireInExtraStates = 0x09
|
|
101
|
+
WireInDacReref = 0x0a
|
|
102
|
+
WireInAuxEnable = 0x0c
|
|
103
|
+
WireInGlobalSettleSelect = 0x0d
|
|
104
|
+
WireInAdcThreshold = 0x0f
|
|
105
|
+
WireInSerialDigitalInCntl = 0x10
|
|
106
|
+
WireInTtlOut32 = 0x10
|
|
107
|
+
WireInLedDisplay = 0x11
|
|
108
|
+
WireInManualTriggers = 0x12
|
|
109
|
+
WireInTtlOutMode = 0x13
|
|
110
|
+
WireInDataStreamEn = 0x14
|
|
111
|
+
WireInTtlOut = 0x15
|
|
112
|
+
WireInDacSource1 = 0x16
|
|
113
|
+
WireInDacSource2 = 0x17
|
|
114
|
+
WireInDacSource3 = 0x18
|
|
115
|
+
WireInDacSource4 = 0x19
|
|
116
|
+
WireInDacSource5 = 0x1a
|
|
117
|
+
WireInDacSource6 = 0x1b
|
|
118
|
+
WireInDacSource7 = 0x1c
|
|
119
|
+
WireInDacSource8 = 0x1d
|
|
120
|
+
WireInDacSource9 = 0x48
|
|
121
|
+
WireInDacSource10 = 0x49
|
|
122
|
+
WireInDacSource11 = 0x4A
|
|
123
|
+
WireInDacSource12 = 0x4B
|
|
124
|
+
WireInDacManual = 0x1e
|
|
125
|
+
WireInMultiUse = 0x1f
|
|
126
|
+
TrigInConfig = 0x40
|
|
127
|
+
TrigInDcmProg = 0x40
|
|
128
|
+
TrigInSpiStart = 0x41
|
|
129
|
+
TrigInRamAddrReset = 0x42
|
|
130
|
+
TrigInDacThresh = 0x43
|
|
131
|
+
TrigInDacHpf = 0x44
|
|
132
|
+
TrigInAuxCmdLength = 0x45
|
|
133
|
+
WireOutNumWords = 0x20
|
|
134
|
+
WireOutNumWordsLsb = 0x20
|
|
135
|
+
WireOutNumWordsMsb = 0x21
|
|
136
|
+
WireOutSpiRunning = 0x22
|
|
137
|
+
WireOutTtlIn = 0x23
|
|
138
|
+
WireOutDataClkLocked = 0x24
|
|
139
|
+
WireOutBoardMode = 0x25
|
|
140
|
+
WireOutSerialDigitalIn = 0x26
|
|
141
|
+
WireOutBoardId = 0x3e
|
|
142
|
+
WireOutBoardVersion = 0x3f
|
|
143
|
+
PipeInAuxCmd1Msw = 0x80
|
|
144
|
+
PipeInAuxCmd1 = 0x81
|
|
145
|
+
PipeInAuxCmd1Lsw = 0x81
|
|
146
|
+
PipeInAuxCmd2Msw = 0x82
|
|
147
|
+
PipeInAuxCmd2 = 0x83
|
|
148
|
+
PipeInAuxCmd2Lsw = 0x83
|
|
149
|
+
PipeInAuxCmd3Msw = 0x84
|
|
150
|
+
PipeInAuxCmd3 = 0x85
|
|
151
|
+
PipeInAuxCmd3Lsw = 0x85
|
|
152
|
+
PipeInAuxCmd4Msw = 0x86
|
|
153
|
+
PipeInAuxCmd4 = 0x87
|
|
154
|
+
PipeInAuxCmd4Lsw = 0x87
|
|
155
|
+
PipeOutData = 0xa0
|
|
156
|
+
TrigVStim = 0x40
|
|
157
|
+
TrigMCU = 0x48
|
|
158
|
+
WireOutXDAQStatus = 0x22
|
|
159
|
+
WireInMCUControl = 0x02
|
|
160
|
+
PipeInFirmware = 0x88
|
|
161
|
+
PipeOutFirmware = 0xb0
|
|
162
|
+
WireInSetMode = 0x00
|
|
163
|
+
Enable32bitDIO = 0x0B
|
|
164
|
+
PipeInDAC1 = 0x90
|
|
165
|
+
PipeInDAC2 = 0x91
|
|
166
|
+
PipeInDAC3 = 0x92
|
|
167
|
+
PipeInDAC4 = 0x93
|
|
168
|
+
PipeInDAC5 = 0x94
|
|
169
|
+
PipeInDAC6 = 0x95
|
|
170
|
+
PipeInDAC7 = 0x96
|
|
171
|
+
PipeInDAC8 = 0x97
|
|
172
|
+
PipeInDAC9 = 0x98
|
|
173
|
+
PipeInDAC10 = 0x99
|
|
174
|
+
PipeInDAC11 = 0x9A
|
|
175
|
+
PipeInDAC12 = 0x9B
|
|
176
|
+
ExpanderInfo = 0x35
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class SampleRate(Enum):
|
|
180
|
+
SampleRate1000Hz = (7, 125, 1000)
|
|
181
|
+
SampleRate1250Hz = (7, 100, 1250)
|
|
182
|
+
SampleRate1500Hz = (21, 250, 1500)
|
|
183
|
+
SampleRate2000Hz = (14, 125, 2000)
|
|
184
|
+
SampleRate2500Hz = (35, 250, 2500)
|
|
185
|
+
SampleRate3000Hz = (21, 125, 3000)
|
|
186
|
+
SampleRate3333Hz = (14, 75, 3333)
|
|
187
|
+
SampleRate4000Hz = (28, 125, 4000)
|
|
188
|
+
SampleRate5000Hz = (7, 25, 5000)
|
|
189
|
+
SampleRate6250Hz = (7, 20, 6250)
|
|
190
|
+
SampleRate8000Hz = (112, 250, 8000)
|
|
191
|
+
SampleRate10000Hz = (14, 25, 10000)
|
|
192
|
+
SampleRate12500Hz = (7, 10, 12500)
|
|
193
|
+
SampleRate15000Hz = (21, 25, 15000)
|
|
194
|
+
SampleRate20000Hz = (28, 25, 20000)
|
|
195
|
+
SampleRate25000Hz = (35, 25, 25000)
|
|
196
|
+
SampleRate30000Hz = (42, 25, 30000)
|
|
197
|
+
|
|
198
|
+
@classmethod
|
|
199
|
+
def fromRate(cls, rate: int):
|
|
200
|
+
for sr in SampleRate:
|
|
201
|
+
if sr.value[2] == rate:
|
|
202
|
+
return sr
|
|
203
|
+
raise ValueError('Invalid sample rate')
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def rate(self):
|
|
207
|
+
return self.value[2]
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class HeadstageChipID(Enum):
|
|
211
|
+
NA = 0
|
|
212
|
+
RHD2132 = 1
|
|
213
|
+
RHD2216 = 2
|
|
214
|
+
RHD2164 = 4
|
|
215
|
+
RHS2116 = 32
|
|
216
|
+
|
|
217
|
+
def num_channels(self):
|
|
218
|
+
if self == HeadstageChipID.RHD2164:
|
|
219
|
+
return 64
|
|
220
|
+
elif self == HeadstageChipID.RHD2132:
|
|
221
|
+
return 32
|
|
222
|
+
elif self == HeadstageChipID.RHD2216:
|
|
223
|
+
return 16
|
|
224
|
+
elif self == HeadstageChipID.RHS2116:
|
|
225
|
+
return 16
|
|
226
|
+
else:
|
|
227
|
+
return 0
|
|
228
|
+
|
|
229
|
+
def num_channels_per_stream(self):
|
|
230
|
+
if self == HeadstageChipID.RHD2164:
|
|
231
|
+
return self.num_channels() // 2
|
|
232
|
+
return self.num_channels()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class HeadstageChipMISOID(Enum):
|
|
236
|
+
NA = 0
|
|
237
|
+
MISO_A = 53
|
|
238
|
+
MISO_B = 58
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class TriggerEvent(Enum):
|
|
242
|
+
Level = 0
|
|
243
|
+
Edge = 1
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class TriggerPolarity(Enum):
|
|
247
|
+
Low = 0
|
|
248
|
+
High = 1
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class StimShape(Enum):
|
|
252
|
+
Biphasic = 0
|
|
253
|
+
BiphasicWithInterphaseDelay = 1
|
|
254
|
+
Triphasic = 2
|
|
255
|
+
Monophasic = 3
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class StartPolarity(Enum):
|
|
259
|
+
anodic = 0
|
|
260
|
+
cathodic = 1
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class StimRegister(Enum):
|
|
264
|
+
Trigger = 0
|
|
265
|
+
Param = 1
|
|
266
|
+
EventAmpSettleOn = 2
|
|
267
|
+
EventAmpSettleOff = 3
|
|
268
|
+
EventStartStim = 4
|
|
269
|
+
EventStimPhase2 = 5
|
|
270
|
+
EventStimPhase3 = 6
|
|
271
|
+
EventEndStim = 7
|
|
272
|
+
EventRepeatStim = 8
|
|
273
|
+
EventChargeRecovOn = 9
|
|
274
|
+
EventChargeRecovOff = 10
|
|
275
|
+
EventAmpSettleOnRepeat = 11
|
|
276
|
+
EventAmpSettleOffRepeat = 12
|
|
277
|
+
EventEnd = 13
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class StimStepSize(Enum):
|
|
281
|
+
StimStepSizeMin = 0
|
|
282
|
+
StimStepSize10nA = 1
|
|
283
|
+
StimStepSize20nA = 2
|
|
284
|
+
StimStepSize50nA = 3
|
|
285
|
+
StimStepSize100nA = 4
|
|
286
|
+
StimStepSize200nA = 5
|
|
287
|
+
StimStepSize500nA = 6
|
|
288
|
+
StimStepSize1uA = 7
|
|
289
|
+
StimStepSize2uA = 8
|
|
290
|
+
StimStepSize5uA = 9
|
|
291
|
+
StimStepSize10uA = 10
|
|
292
|
+
StimStepSizeMax = 11
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def nA(self):
|
|
296
|
+
return {
|
|
297
|
+
StimStepSize.StimStepSizeMin: float('nan'),
|
|
298
|
+
StimStepSize.StimStepSize10nA: 10,
|
|
299
|
+
StimStepSize.StimStepSize20nA: 20,
|
|
300
|
+
StimStepSize.StimStepSize50nA: 50,
|
|
301
|
+
StimStepSize.StimStepSize100nA: 100,
|
|
302
|
+
StimStepSize.StimStepSize200nA: 200,
|
|
303
|
+
StimStepSize.StimStepSize500nA: 500,
|
|
304
|
+
StimStepSize.StimStepSize1uA: 1000,
|
|
305
|
+
StimStepSize.StimStepSize2uA: 2000,
|
|
306
|
+
StimStepSize.StimStepSize5uA: 5000,
|
|
307
|
+
StimStepSize.StimStepSize10uA: 10000,
|
|
308
|
+
StimStepSize.StimStepSizeMax: float('nan')
|
|
309
|
+
}[self]
|
pyxdaq/datablock.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import List, Union
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
_uint16le = np.dtype('u2').newbyteorder('<')
|
|
8
|
+
_uint32le = np.dtype('u4').newbyteorder('<')
|
|
9
|
+
_RHD_HEADER_MAGIC = 0xD7A22AAA38132A53
|
|
10
|
+
_RHS_HEADER_MAGIC = 0x8D542C8A49712F0B
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Sample:
|
|
15
|
+
"""
|
|
16
|
+
Represents a single sample at time `ts` from the XDAQ data stream.
|
|
17
|
+
"""
|
|
18
|
+
ts: int
|
|
19
|
+
aux: np.ndarray
|
|
20
|
+
amp: np.ndarray
|
|
21
|
+
adc: np.ndarray
|
|
22
|
+
ttlin: np.ndarray
|
|
23
|
+
ttlout: np.ndarray
|
|
24
|
+
dac: Union[None, np.ndarray]
|
|
25
|
+
stim: Union[None, np.ndarray]
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_buffer(
|
|
29
|
+
cls, rhs: bool, buffer: Union[bytearray, memoryview], datastreams: int
|
|
30
|
+
) -> 'Sample':
|
|
31
|
+
"""
|
|
32
|
+
Deserialize a single sample from a buffer.
|
|
33
|
+
Keeps the same memory layout as the original data for further optimization.
|
|
34
|
+
"""
|
|
35
|
+
idx = 0
|
|
36
|
+
magic, ts = struct.unpack("<QI", buffer[idx:12])
|
|
37
|
+
if magic != (_RHS_HEADER_MAGIC if rhs else _RHD_HEADER_MAGIC):
|
|
38
|
+
raise ValueError(f"Invalid magic: {magic:016X}")
|
|
39
|
+
idx += 12
|
|
40
|
+
|
|
41
|
+
aux = np.frombuffer(
|
|
42
|
+
buffer[idx:], dtype=_uint16le, count=3 * datastreams * (2 if rhs else 1)
|
|
43
|
+
).reshape([3, datastreams] + ([2] if rhs else []))
|
|
44
|
+
idx += 3 * datastreams * 2 * (2 if rhs else 1)
|
|
45
|
+
|
|
46
|
+
amp = np.frombuffer(
|
|
47
|
+
buffer[idx:],
|
|
48
|
+
dtype=_uint16le,
|
|
49
|
+
count=(16 if rhs else 32) * datastreams * (2 if rhs else 1)
|
|
50
|
+
).reshape([16 if rhs else 32, datastreams] + ([2] if rhs else []))
|
|
51
|
+
idx += (16 if rhs else 32) * datastreams * 2 * (2 if rhs else 1)
|
|
52
|
+
|
|
53
|
+
if rhs:
|
|
54
|
+
aux0 = np.frombuffer(
|
|
55
|
+
buffer[idx:], dtype=_uint16le, count=1 * datastreams * 2
|
|
56
|
+
).reshape((1, datastreams, 2))
|
|
57
|
+
aux = np.concatenate((aux0, aux), axis=0)
|
|
58
|
+
idx += 1 * datastreams * 2 * 2
|
|
59
|
+
|
|
60
|
+
stim = np.frombuffer(
|
|
61
|
+
buffer[idx:], dtype=_uint16le, count=4 * datastreams
|
|
62
|
+
).reshape(4, datastreams)
|
|
63
|
+
idx += 4 * datastreams * 2
|
|
64
|
+
idx += 4
|
|
65
|
+
|
|
66
|
+
dac = np.frombuffer(buffer[idx:], dtype=_uint16le, count=8)
|
|
67
|
+
idx += 16
|
|
68
|
+
else:
|
|
69
|
+
stim = None
|
|
70
|
+
dac = None
|
|
71
|
+
idx += 2 * ((datastreams + 2) % 4) # padding
|
|
72
|
+
|
|
73
|
+
adc = np.frombuffer(buffer[idx:], dtype=_uint16le, count=8)
|
|
74
|
+
idx += 16
|
|
75
|
+
|
|
76
|
+
ttlin = np.frombuffer(buffer[idx:], dtype=_uint32le, count=1)
|
|
77
|
+
idx += 4
|
|
78
|
+
|
|
79
|
+
ttlout = np.frombuffer(buffer[idx:], dtype=_uint32le, count=1)
|
|
80
|
+
idx += 4
|
|
81
|
+
return cls(ts, aux, amp, adc, ttlin, ttlout, dac, stim)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class Samples(Sample):
|
|
86
|
+
"""
|
|
87
|
+
A collection of samples, the first dimension represents the sample index.
|
|
88
|
+
"""
|
|
89
|
+
n: int
|
|
90
|
+
|
|
91
|
+
def device_name(self):
|
|
92
|
+
if self.n != 128:
|
|
93
|
+
raise ValueError("Unable to determine device name for non-128 sample data block")
|
|
94
|
+
if self.stim is None:
|
|
95
|
+
return self.aux[[32, 33, 34, 35, 36, 24, 25, 26], 2, :]
|
|
96
|
+
else:
|
|
97
|
+
rom = self.aux[:, 0, :, :][58:61, :, 0]
|
|
98
|
+
aux = np.array(rom).view(np.uint8).reshape(
|
|
99
|
+
(rom.shape[0], rom.shape[1], 2)
|
|
100
|
+
).transpose(1, 0, 2).reshape((rom.shape[1], -1))
|
|
101
|
+
return aux[:, :0:-1].T
|
|
102
|
+
|
|
103
|
+
def device_id(self):
|
|
104
|
+
if self.n != 128:
|
|
105
|
+
raise ValueError("Unable to determine device ID for non-128 sample data block")
|
|
106
|
+
if self.stim is None:
|
|
107
|
+
return self.aux[19, 2, :], self.aux[23, 2, :]
|
|
108
|
+
else:
|
|
109
|
+
rom = self.aux[:, 0, :, :][56:58, :, 0]
|
|
110
|
+
aux = np.array(rom).view(np.uint8).reshape(
|
|
111
|
+
(rom.shape[0], rom.shape[1], 2)
|
|
112
|
+
).transpose(1, 0, 2).reshape((rom.shape[1], -1))
|
|
113
|
+
return aux[:, 0].T, np.zeros_like(aux[:, 0].T)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass
|
|
117
|
+
class DataBlock:
|
|
118
|
+
"""
|
|
119
|
+
Raw data block which keeps the original memory layout.
|
|
120
|
+
"""
|
|
121
|
+
samples: List[Sample]
|
|
122
|
+
# Samples x C x Datastreams : [C x Datastreams] [C x Datastreams] ... [C x Datastreams]
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def from_buffer(
|
|
126
|
+
cls, rhs, sample_size, buffer: Union[bytearray, memoryview], datastreams: int
|
|
127
|
+
) -> 'DataBlock':
|
|
128
|
+
return cls(
|
|
129
|
+
[
|
|
130
|
+
Sample.from_buffer(rhs, buffer[i:i + sample_size], datastreams)
|
|
131
|
+
for i in range(0, len(buffer), sample_size)
|
|
132
|
+
]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def to_samples(self) -> Samples:
|
|
136
|
+
"""
|
|
137
|
+
Concatenate all samples into a single Samples object.
|
|
138
|
+
This method breaks the original memory layout.
|
|
139
|
+
"""
|
|
140
|
+
return Samples(
|
|
141
|
+
np.array([s.ts for s in self.samples]), np.stack([s.aux for s in self.samples]),
|
|
142
|
+
np.stack([s.amp for s in self.samples]), np.stack([s.adc for s in self.samples]),
|
|
143
|
+
np.stack([s.ttlin for s in self.samples]), np.stack([s.ttlout for s in self.samples]),
|
|
144
|
+
None if self.samples[0].dac is None else np.stack([s.dac for s in self.samples]),
|
|
145
|
+
None if self.samples[0].dac is None else np.stack([s.stim for s in self.samples]),
|
|
146
|
+
len(self.samples)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def amplifier2mv(amp: np.array):
|
|
151
|
+
return (amp.astype(np.float32) - 32768) * 0.195
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def adc2v(adc: np.array):
|
|
155
|
+
return (adc.astype(np.float32) - 32768) * 0.0003125
|
pyxdaq/gen2.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from .xdaq import XDAQ
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def upload_waveform(xdaq: XDAQ, waveform: np.ndarray, idx: int, divisor: int):
|
|
10
|
+
if waveform.dtype != np.uint16:
|
|
11
|
+
raise TypeError(f'Only 16 bits resolution for waveform, get {waveform.dtype}')
|
|
12
|
+
for i, val in enumerate(waveform.astype(np.uint32)):
|
|
13
|
+
xdaq.dev.set_register(0x2008 + idx * 8, int(val) | (i << 16) | (1 << 31), 0xFFFFFFFF)
|
|
14
|
+
xdaq.dev.set_register(0x200C + idx * 8, divisor | (len(waveform) - 1) << 16, 0xFFFFFFFF)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def config_waveform(xdaq: XDAQ, enable: int):
|
|
18
|
+
xdaq.dev.set_register(0x2004, enable, 15) # enable
|
|
19
|
+
xdaq.dev.set_register(0x2000, enable, 15) # reset enabled
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def config_channel(xdaq: XDAQ, channel: int, waveform: Union[int, None]):
|
|
23
|
+
if waveform is None:
|
|
24
|
+
xdaq.dev.set_register(0x2028 + 4 * channel, 0 | (0 << 31), 0xFFFFFFFF)
|
|
25
|
+
else:
|
|
26
|
+
xdaq.dev.set_register(0x2028 + 4 * channel, waveform | (1 << 31), 0xFFFFFFFF)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_sine(period, amp=32768):
|
|
30
|
+
return np.clip(
|
|
31
|
+
(32768 + amp * np.sin(np.linspace(0, np.pi * 2, period, endpoint=False))).astype(np.int32),
|
|
32
|
+
0,
|
|
33
|
+
65535,
|
|
34
|
+
).astype(np.uint16)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def approximate_waveform_param(
|
|
38
|
+
target_freq: float,
|
|
39
|
+
tolerance: float = 0,
|
|
40
|
+
clock: int = int(125e6),
|
|
41
|
+
min_div: int = 1024,
|
|
42
|
+
max_div: int = 65535,
|
|
43
|
+
min_period: int = 32,
|
|
44
|
+
max_period: int = 1024,
|
|
45
|
+
):
|
|
46
|
+
d = clock / target_freq
|
|
47
|
+
best = float('inf')
|
|
48
|
+
best_param = None
|
|
49
|
+
for period in range(max_period, min_period, -1):
|
|
50
|
+
a = max(int(math.floor(d / period)), min_div)
|
|
51
|
+
b = min(int(math.ceil(d / period)), max_div)
|
|
52
|
+
for div in range(a, b + 1):
|
|
53
|
+
if d / period == div:
|
|
54
|
+
return (period, div)
|
|
55
|
+
resudial = abs(clock / period / div - target_freq)
|
|
56
|
+
if resudial < tolerance:
|
|
57
|
+
return (period, div)
|
|
58
|
+
if resudial < best:
|
|
59
|
+
best = resudial
|
|
60
|
+
best_param = (period, div)
|
|
61
|
+
return best_param
|