ephys-link 1.3.3__py3-none-any.whl → 2.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.
- ephys_link/__about__.py +1 -1
- ephys_link/__main__.py +51 -105
- ephys_link/back_end/__init__.py +0 -0
- ephys_link/back_end/platform_handler.py +315 -0
- ephys_link/back_end/server.py +274 -0
- ephys_link/bindings/__init__.py +0 -0
- ephys_link/bindings/fake_binding.py +84 -0
- ephys_link/bindings/mpm_binding.py +315 -0
- ephys_link/bindings/ump_4_binding.py +157 -0
- ephys_link/front_end/__init__.py +0 -0
- ephys_link/front_end/cli.py +104 -0
- ephys_link/front_end/gui.py +204 -0
- ephys_link/utils/__init__.py +0 -0
- ephys_link/utils/base_binding.py +176 -0
- ephys_link/utils/console.py +127 -0
- ephys_link/utils/constants.py +23 -0
- ephys_link/utils/converters.py +86 -0
- ephys_link/utils/startup.py +65 -0
- ephys_link-2.0.0.dist-info/METADATA +91 -0
- ephys_link-2.0.0.dist-info/RECORD +25 -0
- {ephys_link-1.3.3.dist-info → ephys_link-2.0.0.dist-info}/WHEEL +1 -1
- {ephys_link-1.3.3.dist-info → ephys_link-2.0.0.dist-info}/licenses/LICENSE +674 -674
- ephys_link/common.py +0 -49
- ephys_link/emergency_stop.py +0 -67
- ephys_link/gui.py +0 -217
- ephys_link/platform_handler.py +0 -465
- ephys_link/platform_manipulator.py +0 -35
- ephys_link/platforms/__init__.py +0 -5
- ephys_link/platforms/new_scale_handler.py +0 -141
- ephys_link/platforms/new_scale_manipulator.py +0 -312
- ephys_link/platforms/new_scale_pathfinder_handler.py +0 -235
- ephys_link/platforms/sensapex_handler.py +0 -151
- ephys_link/platforms/sensapex_manipulator.py +0 -227
- ephys_link/platforms/ump3_handler.py +0 -57
- ephys_link/platforms/ump3_manipulator.py +0 -147
- ephys_link/resources/CP210xManufacturing.dll +0 -0
- ephys_link/resources/NstMotorCtrl.dll +0 -0
- ephys_link/resources/SiUSBXp.dll +0 -0
- ephys_link/server.py +0 -508
- ephys_link-1.3.3.dist-info/METADATA +0 -164
- ephys_link-1.3.3.dist-info/RECORD +0 -26
- {ephys_link-1.3.3.dist-info → ephys_link-2.0.0.dist-info}/entry_points.txt +0 -0
ephys_link/platform_handler.py
DELETED
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
"""Handle communications with platform specific API
|
|
2
|
-
|
|
3
|
-
Handles relaying WebSocket messages to the appropriate platform API functions and
|
|
4
|
-
conducting error checks on the input and output values
|
|
5
|
-
|
|
6
|
-
Function names here are the same as the WebSocket events. They are called when the
|
|
7
|
-
server receives an event from a client. In general, each function does the following:
|
|
8
|
-
|
|
9
|
-
1. Receive extracted arguments from :mod:`ephys_link.server`
|
|
10
|
-
2. Call and check the appropriate platform API function (overloaded by each platform)
|
|
11
|
-
3. Log/handle successes and failures
|
|
12
|
-
4. Return the callback parameters to :mod:`ephys_link.server`
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
from abc import ABC, abstractmethod
|
|
18
|
-
from typing import TYPE_CHECKING
|
|
19
|
-
|
|
20
|
-
from vbl_aquarium.models.ephys_link import (
|
|
21
|
-
AngularResponse,
|
|
22
|
-
BooleanStateResponse,
|
|
23
|
-
CanWriteRequest,
|
|
24
|
-
DriveToDepthRequest,
|
|
25
|
-
DriveToDepthResponse,
|
|
26
|
-
GetManipulatorsResponse,
|
|
27
|
-
GotoPositionRequest,
|
|
28
|
-
InsideBrainRequest,
|
|
29
|
-
PositionalResponse,
|
|
30
|
-
ShankCountResponse,
|
|
31
|
-
)
|
|
32
|
-
from vbl_aquarium.models.unity import Vector4
|
|
33
|
-
|
|
34
|
-
from ephys_link import common as com
|
|
35
|
-
|
|
36
|
-
if TYPE_CHECKING:
|
|
37
|
-
from socketio import AsyncClient, AsyncServer
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class PlatformHandler(ABC):
|
|
41
|
-
"""An abstract class that defines the interface for a manipulator handler."""
|
|
42
|
-
|
|
43
|
-
def __init__(self):
|
|
44
|
-
"""Initialize the manipulator handler with a dictionary of manipulators."""
|
|
45
|
-
|
|
46
|
-
# Registered manipulators are stored as a dictionary of IDs (string) to
|
|
47
|
-
# manipulator objects.
|
|
48
|
-
self.manipulators = {}
|
|
49
|
-
self.num_axes = 4
|
|
50
|
-
|
|
51
|
-
# Platform axes dimensions in mm
|
|
52
|
-
self.dimensions = Vector4(x=20, y=20, z=20, w=20)
|
|
53
|
-
|
|
54
|
-
# Platform Handler Methods.
|
|
55
|
-
|
|
56
|
-
def reset(self) -> bool:
|
|
57
|
-
"""Reset handler.
|
|
58
|
-
|
|
59
|
-
:return: True if successful, False otherwise.
|
|
60
|
-
:rtype: bool
|
|
61
|
-
"""
|
|
62
|
-
stop_result = self.stop()
|
|
63
|
-
self.manipulators.clear()
|
|
64
|
-
return stop_result
|
|
65
|
-
|
|
66
|
-
def stop(self) -> bool:
|
|
67
|
-
"""Stop handler.
|
|
68
|
-
|
|
69
|
-
:return: True if successful, False otherwise.
|
|
70
|
-
:rtype: bool
|
|
71
|
-
"""
|
|
72
|
-
try:
|
|
73
|
-
for manipulator in self.manipulators.values():
|
|
74
|
-
if hasattr(manipulator, "stop"):
|
|
75
|
-
manipulator.stop()
|
|
76
|
-
except Exception as e:
|
|
77
|
-
print(f"[ERROR]\t\t Could not stop manipulators: {e}\n")
|
|
78
|
-
return False
|
|
79
|
-
else:
|
|
80
|
-
return True
|
|
81
|
-
|
|
82
|
-
def get_manipulators(self) -> GetManipulatorsResponse:
|
|
83
|
-
"""Get all registered manipulators.
|
|
84
|
-
|
|
85
|
-
:return: Result of connected manipulators, platform information, and error message (if any).
|
|
86
|
-
:rtype: :class:`vbl_aquarium.models.ephys_link.GetManipulatorsResponse`
|
|
87
|
-
"""
|
|
88
|
-
try:
|
|
89
|
-
manipulators = self._get_manipulators()
|
|
90
|
-
except Exception as e:
|
|
91
|
-
print(f"[ERROR]\t\t Getting manipulators: {type(e)}: {e}\n")
|
|
92
|
-
return GetManipulatorsResponse(error="Error getting manipulators")
|
|
93
|
-
else:
|
|
94
|
-
return GetManipulatorsResponse(
|
|
95
|
-
manipulators=manipulators, num_axes=self.num_axes, dimensions=self.dimensions
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
def register_manipulator(self, manipulator_id: str) -> str:
|
|
99
|
-
"""Register a manipulator.
|
|
100
|
-
|
|
101
|
-
:param manipulator_id: The ID of the manipulator to register.
|
|
102
|
-
:type manipulator_id: str
|
|
103
|
-
:return: Error message on error, empty string otherwise.
|
|
104
|
-
:rtype: str
|
|
105
|
-
"""
|
|
106
|
-
# Check if manipulator is already registered
|
|
107
|
-
if manipulator_id in self.manipulators:
|
|
108
|
-
print(f"[ERROR]\t\t Manipulator already registered:" f" {manipulator_id}\n")
|
|
109
|
-
return "Manipulator already registered"
|
|
110
|
-
|
|
111
|
-
try:
|
|
112
|
-
# Register manipulator
|
|
113
|
-
self._register_manipulator(manipulator_id)
|
|
114
|
-
except ValueError as ve:
|
|
115
|
-
# Manipulator not found in UMP
|
|
116
|
-
print(f"[ERROR]\t\t Manipulator not found: {manipulator_id}: {ve}\n")
|
|
117
|
-
return "Manipulator not found"
|
|
118
|
-
except Exception as e:
|
|
119
|
-
# Other error
|
|
120
|
-
print(f"[ERROR]\t\t Registering manipulator: {manipulator_id}")
|
|
121
|
-
print(f"{type(e)}: {e}\n")
|
|
122
|
-
return "Error registering manipulator"
|
|
123
|
-
else:
|
|
124
|
-
com.dprint(f"[SUCCESS]\t Registered manipulator: {manipulator_id}\n")
|
|
125
|
-
return ""
|
|
126
|
-
|
|
127
|
-
def unregister_manipulator(self, manipulator_id: str) -> str:
|
|
128
|
-
"""Unregister a manipulator.
|
|
129
|
-
|
|
130
|
-
:param manipulator_id: The ID of the manipulator to unregister.
|
|
131
|
-
:type manipulator_id: str
|
|
132
|
-
:return: Error message on error, empty string otherwise.
|
|
133
|
-
:rtype: str
|
|
134
|
-
"""
|
|
135
|
-
# Check if manipulator is not registered
|
|
136
|
-
if manipulator_id not in self.manipulators:
|
|
137
|
-
print(f"[ERROR]\t\t Manipulator not registered: {manipulator_id}\n")
|
|
138
|
-
return "Manipulator not registered"
|
|
139
|
-
|
|
140
|
-
try:
|
|
141
|
-
# Unregister manipulator
|
|
142
|
-
self._unregister_manipulator(manipulator_id)
|
|
143
|
-
except Exception as e:
|
|
144
|
-
# Other error
|
|
145
|
-
print(f"[ERROR]\t\t Unregistering manipulator: {manipulator_id}")
|
|
146
|
-
print(f"{e}\n")
|
|
147
|
-
return "Error unregistering manipulator"
|
|
148
|
-
else:
|
|
149
|
-
com.dprint(f"[SUCCESS]\t Unregistered manipulator: {manipulator_id}\n")
|
|
150
|
-
return ""
|
|
151
|
-
|
|
152
|
-
def get_pos(self, manipulator_id: str) -> PositionalResponse:
|
|
153
|
-
"""Get the current position of a manipulator.
|
|
154
|
-
|
|
155
|
-
:param manipulator_id: The ID of the manipulator to get the position of.
|
|
156
|
-
:type manipulator_id: str
|
|
157
|
-
:return: Positional information for the manipulator and error message (if any).
|
|
158
|
-
:rtype: :class:`vbl_aquarium.models.ephys_link.PositionalResponse`
|
|
159
|
-
"""
|
|
160
|
-
try:
|
|
161
|
-
# Check calibration status.
|
|
162
|
-
if (
|
|
163
|
-
hasattr(self.manipulators[manipulator_id], "get_calibrated")
|
|
164
|
-
and not self.manipulators[manipulator_id].get_calibrated()
|
|
165
|
-
):
|
|
166
|
-
print(f"[ERROR]\t\t Calibration not complete: {manipulator_id}\n")
|
|
167
|
-
return PositionalResponse(error="Manipulator not calibrated")
|
|
168
|
-
|
|
169
|
-
# Get position and convert to unified space.
|
|
170
|
-
manipulator_pos = self._get_pos(manipulator_id)
|
|
171
|
-
|
|
172
|
-
# Shortcut return for Pathfinder.
|
|
173
|
-
if self.num_axes == -1:
|
|
174
|
-
return manipulator_pos
|
|
175
|
-
|
|
176
|
-
# Convert position to unified space.
|
|
177
|
-
return manipulator_pos.model_copy(
|
|
178
|
-
update={"position": self._platform_space_to_unified_space(manipulator_pos.position)}
|
|
179
|
-
)
|
|
180
|
-
except KeyError:
|
|
181
|
-
# Manipulator not found in registered manipulators.
|
|
182
|
-
print(f"[ERROR]\t\t Manipulator not registered: {manipulator_id}")
|
|
183
|
-
return PositionalResponse(error="Manipulator not registered")
|
|
184
|
-
|
|
185
|
-
def get_angles(self, manipulator_id: str) -> AngularResponse:
|
|
186
|
-
"""Get the current position of a manipulator.
|
|
187
|
-
|
|
188
|
-
:param manipulator_id: The ID of the manipulator to get the angles of.
|
|
189
|
-
:type manipulator_id: str
|
|
190
|
-
:return: Angular information for the manipulator and error message (if any).
|
|
191
|
-
:rtype: :class:`vbl_aquarium.models.ephys_link.AngularResponse`
|
|
192
|
-
"""
|
|
193
|
-
try:
|
|
194
|
-
# Check calibration status
|
|
195
|
-
if (
|
|
196
|
-
hasattr(self.manipulators[manipulator_id], "get_calibrated")
|
|
197
|
-
and not self.manipulators[manipulator_id].get_calibrated()
|
|
198
|
-
):
|
|
199
|
-
print(f"[ERROR]\t\t Calibration not complete: {manipulator_id}\n")
|
|
200
|
-
return AngularResponse(error="Manipulator not calibrated")
|
|
201
|
-
|
|
202
|
-
# Get position
|
|
203
|
-
return self._get_angles(manipulator_id)
|
|
204
|
-
|
|
205
|
-
except KeyError:
|
|
206
|
-
# Manipulator not found in registered manipulators
|
|
207
|
-
print(f"[ERROR]\t\t Manipulator not registered: {manipulator_id}")
|
|
208
|
-
return AngularResponse(error="Manipulator not registered")
|
|
209
|
-
|
|
210
|
-
def get_shank_count(self, manipulator_id: str) -> ShankCountResponse:
|
|
211
|
-
"""Get the number of shanks on the probe
|
|
212
|
-
|
|
213
|
-
:param manipulator_id: The ID of the manipulator to get the number of shanks of.
|
|
214
|
-
:type manipulator_id: str
|
|
215
|
-
:return: Number of shanks on the probe.
|
|
216
|
-
:rtype: :class:`vbl_aquarium.models.ephys_link.ShankCountResponse`
|
|
217
|
-
"""
|
|
218
|
-
return self._get_shank_count(manipulator_id)
|
|
219
|
-
|
|
220
|
-
async def goto_pos(self, request: GotoPositionRequest) -> PositionalResponse:
|
|
221
|
-
"""Move manipulator to position
|
|
222
|
-
|
|
223
|
-
:param request: The goto request parsed from the server.
|
|
224
|
-
:type request: :class:`vbl_aquarium.models.ephys_link.GotoPositionRequest`
|
|
225
|
-
:return: Resulting position of the manipulator and error message (if any).
|
|
226
|
-
:rtype: :class:`vbl_aquarium.models.ephys_link.PositionalResponse`
|
|
227
|
-
"""
|
|
228
|
-
try:
|
|
229
|
-
# Check calibration status.
|
|
230
|
-
if not self.manipulators[request.manipulator_id].get_calibrated():
|
|
231
|
-
print(f"[ERROR]\t\t Calibration not complete: {request.manipulator_id}\n")
|
|
232
|
-
return PositionalResponse(error="Manipulator not calibrated")
|
|
233
|
-
|
|
234
|
-
# Check write state.
|
|
235
|
-
if not self.manipulators[request.manipulator_id].get_can_write():
|
|
236
|
-
print(f"[ERROR]\t\t Cannot write to manipulator: {request.manipulator_id}")
|
|
237
|
-
return PositionalResponse(error="Cannot write to manipulator")
|
|
238
|
-
|
|
239
|
-
# Convert position to platform space, move, and convert final position back to
|
|
240
|
-
# unified space.
|
|
241
|
-
end_position = await self._goto_pos(
|
|
242
|
-
request.model_copy(update={"position": self._unified_space_to_platform_space(request.position)})
|
|
243
|
-
)
|
|
244
|
-
return end_position.model_copy(
|
|
245
|
-
update={"position": self._platform_space_to_unified_space(end_position.position)}
|
|
246
|
-
)
|
|
247
|
-
except KeyError:
|
|
248
|
-
# Manipulator not found in registered manipulators.
|
|
249
|
-
print(f"[ERROR]\t\t Manipulator not registered: {request.manipulator_id}\n")
|
|
250
|
-
return PositionalResponse(error="Manipulator not registered")
|
|
251
|
-
|
|
252
|
-
async def drive_to_depth(self, request: DriveToDepthRequest) -> DriveToDepthResponse:
|
|
253
|
-
"""Drive manipulator to depth
|
|
254
|
-
|
|
255
|
-
:param request: The drive to depth request parsed from the server.
|
|
256
|
-
:type request: :class:`vbl_aquarium.models.ephys_link.DriveToDepthRequest`
|
|
257
|
-
:return: Resulting depth of the manipulator and error message (if any).
|
|
258
|
-
:rtype: :class:`ephys_link.common.DriveToDepthOutputData`
|
|
259
|
-
"""
|
|
260
|
-
try:
|
|
261
|
-
# Check calibration status
|
|
262
|
-
if not self.manipulators[request.manipulator_id].get_calibrated():
|
|
263
|
-
print(f"[ERROR]\t\t Calibration not complete: {request.manipulator_id}\n")
|
|
264
|
-
return DriveToDepthResponse(error="Manipulator not calibrated")
|
|
265
|
-
|
|
266
|
-
# Check write state
|
|
267
|
-
if not self.manipulators[request.manipulator_id].get_can_write():
|
|
268
|
-
print(f"[ERROR]\t\t Cannot write to manipulator: {request.manipulator_id}")
|
|
269
|
-
return DriveToDepthResponse(error="Cannot write to manipulator")
|
|
270
|
-
|
|
271
|
-
end_depth = await self._drive_to_depth(
|
|
272
|
-
request.model_copy(update={"depth": self._unified_space_to_platform_space(Vector4(w=request.depth)).w})
|
|
273
|
-
)
|
|
274
|
-
return end_depth.model_copy(
|
|
275
|
-
update={"depth": self._platform_space_to_unified_space(Vector4(w=end_depth.depth)).w}
|
|
276
|
-
)
|
|
277
|
-
except KeyError:
|
|
278
|
-
# Manipulator not found in registered manipulators
|
|
279
|
-
print(f"[ERROR]\t\t Manipulator not registered: {request.manipulator_id}\n")
|
|
280
|
-
return DriveToDepthResponse(error="Manipulator not registered")
|
|
281
|
-
|
|
282
|
-
def set_inside_brain(self, request: InsideBrainRequest) -> BooleanStateResponse:
|
|
283
|
-
"""Set manipulator inside brain state (restricts motion)
|
|
284
|
-
|
|
285
|
-
:param request: The inside brain request parsed from the server.
|
|
286
|
-
:type request: :class:`vbl_aquarium.models.ephys_link.InsideBrainRequest`
|
|
287
|
-
:return: New inside brain state of the manipulator and error message (if any).
|
|
288
|
-
:rtype: :class:`vbl_aquarium.models.ephys_link.BooleanStateResponse`
|
|
289
|
-
"""
|
|
290
|
-
try:
|
|
291
|
-
# Check calibration status
|
|
292
|
-
if (
|
|
293
|
-
hasattr(self.manipulators[request.manipulator_id], "get_calibrated")
|
|
294
|
-
and not self.manipulators[request.manipulator_id].get_calibrated()
|
|
295
|
-
):
|
|
296
|
-
print("[ERROR]\t\t Calibration not complete\n")
|
|
297
|
-
return BooleanStateResponse(error="Manipulator not calibrated")
|
|
298
|
-
|
|
299
|
-
return self._set_inside_brain(request)
|
|
300
|
-
|
|
301
|
-
except KeyError:
|
|
302
|
-
# Manipulator not found in registered manipulators
|
|
303
|
-
print(f"[ERROR]\t\t Manipulator {request.manipulator_id} not registered\n")
|
|
304
|
-
return BooleanStateResponse(error="Manipulator not " "registered")
|
|
305
|
-
|
|
306
|
-
except Exception as e:
|
|
307
|
-
# Other error
|
|
308
|
-
print(f"[ERROR]\t\t Set manipulator {request.manipulator_id} inside brain " f"state")
|
|
309
|
-
print(f"{e}\n")
|
|
310
|
-
return BooleanStateResponse(error="Error setting inside brain")
|
|
311
|
-
|
|
312
|
-
async def calibrate(self, manipulator_id: str, sio: AsyncClient | AsyncServer) -> str:
|
|
313
|
-
"""Calibrate manipulator
|
|
314
|
-
|
|
315
|
-
:param manipulator_id: ID of manipulator to calibrate
|
|
316
|
-
:type manipulator_id: str
|
|
317
|
-
:param sio: SocketIO object (to call sleep)
|
|
318
|
-
:type sio: :class:`socketio.AsyncServer`
|
|
319
|
-
:return: Error message on error, empty string otherwise.
|
|
320
|
-
:rtype: str
|
|
321
|
-
"""
|
|
322
|
-
try:
|
|
323
|
-
# Check write state
|
|
324
|
-
if not self.manipulators[manipulator_id].get_can_write():
|
|
325
|
-
print(f"[ERROR]\t\t Cannot write to manipulator: {manipulator_id}")
|
|
326
|
-
return "Cannot write to manipulator"
|
|
327
|
-
|
|
328
|
-
return await self._calibrate(manipulator_id, sio)
|
|
329
|
-
|
|
330
|
-
except KeyError:
|
|
331
|
-
# Manipulator not found in registered manipulators
|
|
332
|
-
print(f"[ERROR]\t\t Manipulator {manipulator_id} not registered\n")
|
|
333
|
-
return "Manipulator not registered"
|
|
334
|
-
|
|
335
|
-
except Exception as e:
|
|
336
|
-
# Other error
|
|
337
|
-
print(f"[ERROR]\t\t Calibrate manipulator {manipulator_id}")
|
|
338
|
-
print(f"{e}\n")
|
|
339
|
-
return "Error calibrating manipulator"
|
|
340
|
-
|
|
341
|
-
def bypass_calibration(self, manipulator_id: str) -> str:
|
|
342
|
-
"""Bypass calibration of manipulator
|
|
343
|
-
|
|
344
|
-
:param manipulator_id: ID of manipulator to bypass calibration
|
|
345
|
-
:type manipulator_id: str
|
|
346
|
-
:return: Error message on error, empty string otherwise.
|
|
347
|
-
:rtype: str
|
|
348
|
-
"""
|
|
349
|
-
try:
|
|
350
|
-
# Bypass calibration
|
|
351
|
-
return self._bypass_calibration(manipulator_id)
|
|
352
|
-
|
|
353
|
-
except KeyError:
|
|
354
|
-
# Manipulator not found in registered manipulators
|
|
355
|
-
print(f"[ERROR]\t\t Manipulator {manipulator_id} not registered\n")
|
|
356
|
-
return "Manipulator not registered"
|
|
357
|
-
|
|
358
|
-
except Exception as e:
|
|
359
|
-
# Other error
|
|
360
|
-
print(f"[ERROR]\t\t Bypass calibration of manipulator {manipulator_id}")
|
|
361
|
-
print(f"{e}\n")
|
|
362
|
-
return "Error bypassing calibration"
|
|
363
|
-
|
|
364
|
-
def set_can_write(
|
|
365
|
-
self,
|
|
366
|
-
request: CanWriteRequest,
|
|
367
|
-
) -> BooleanStateResponse:
|
|
368
|
-
"""Set manipulator can_write state (enables/disabled moving manipulator)
|
|
369
|
-
|
|
370
|
-
:param request: The can write request parsed from the server.
|
|
371
|
-
:type request: :class:`vbl_aquarium.models.ephys_link.CanWriteRequest`
|
|
372
|
-
:return: New can_write state of the manipulator and error message (if any).
|
|
373
|
-
:rtype: :class:`ephys_link.common.StateOutputData`
|
|
374
|
-
"""
|
|
375
|
-
try:
|
|
376
|
-
return self._set_can_write(request)
|
|
377
|
-
except KeyError:
|
|
378
|
-
# Manipulator not found in registered manipulators
|
|
379
|
-
print(f"[ERROR]\t\t Manipulator not registered: {request.manipulator_id}\n")
|
|
380
|
-
return BooleanStateResponse(error="Manipulator not registered")
|
|
381
|
-
except Exception as e:
|
|
382
|
-
# Other error
|
|
383
|
-
print(f"[ERROR]\t\t Set manipulator {request.manipulator_id} can_write state")
|
|
384
|
-
print(f"{e}\n")
|
|
385
|
-
return BooleanStateResponse(error="Error setting can_write")
|
|
386
|
-
|
|
387
|
-
# Platform specific methods to override
|
|
388
|
-
|
|
389
|
-
@abstractmethod
|
|
390
|
-
def _get_manipulators(self) -> list:
|
|
391
|
-
raise NotImplementedError
|
|
392
|
-
|
|
393
|
-
@abstractmethod
|
|
394
|
-
def _register_manipulator(self, manipulator_id: str) -> None:
|
|
395
|
-
raise NotImplementedError
|
|
396
|
-
|
|
397
|
-
@abstractmethod
|
|
398
|
-
def _unregister_manipulator(self, manipulator_id: str) -> None:
|
|
399
|
-
raise NotImplementedError
|
|
400
|
-
|
|
401
|
-
@abstractmethod
|
|
402
|
-
def _get_pos(self, manipulator_id: str) -> PositionalResponse:
|
|
403
|
-
raise NotImplementedError
|
|
404
|
-
|
|
405
|
-
@abstractmethod
|
|
406
|
-
def _get_angles(self, manipulator_id: str) -> AngularResponse:
|
|
407
|
-
raise NotImplementedError
|
|
408
|
-
|
|
409
|
-
@abstractmethod
|
|
410
|
-
def _get_shank_count(self, manipulator_id: str) -> ShankCountResponse:
|
|
411
|
-
raise NotImplementedError
|
|
412
|
-
|
|
413
|
-
@abstractmethod
|
|
414
|
-
async def _goto_pos(self, request: GotoPositionRequest) -> PositionalResponse:
|
|
415
|
-
raise NotImplementedError
|
|
416
|
-
|
|
417
|
-
@abstractmethod
|
|
418
|
-
async def _drive_to_depth(self, request: DriveToDepthRequest) -> DriveToDepthResponse:
|
|
419
|
-
raise NotImplementedError
|
|
420
|
-
|
|
421
|
-
@abstractmethod
|
|
422
|
-
def _set_inside_brain(self, request: InsideBrainRequest) -> BooleanStateResponse:
|
|
423
|
-
raise NotImplementedError
|
|
424
|
-
|
|
425
|
-
@abstractmethod
|
|
426
|
-
async def _calibrate(self, manipulator_id: str, sio: AsyncClient | AsyncServer) -> str:
|
|
427
|
-
"""Calibrate manipulator
|
|
428
|
-
|
|
429
|
-
:param manipulator_id: ID of manipulator to calibrate
|
|
430
|
-
:type manipulator_id: str
|
|
431
|
-
:param sio: SocketIO object (to call sleep)
|
|
432
|
-
:type sio: :class:`socketio.AsyncServer`
|
|
433
|
-
:return: Callback parameters (manipulator ID, error message)
|
|
434
|
-
:rtype: str
|
|
435
|
-
"""
|
|
436
|
-
|
|
437
|
-
@abstractmethod
|
|
438
|
-
def _bypass_calibration(self, manipulator_id: str) -> str:
|
|
439
|
-
raise NotImplementedError
|
|
440
|
-
|
|
441
|
-
@abstractmethod
|
|
442
|
-
def _set_can_write(self, request: CanWriteRequest) -> BooleanStateResponse:
|
|
443
|
-
raise NotImplementedError
|
|
444
|
-
|
|
445
|
-
@abstractmethod
|
|
446
|
-
def _platform_space_to_unified_space(self, platform_position: Vector4) -> Vector4:
|
|
447
|
-
"""Convert position in platform space to position in unified manipulator space
|
|
448
|
-
|
|
449
|
-
:param platform_position: Position in platform space (x, y, z, w) in mm
|
|
450
|
-
:type platform_position: Vector4
|
|
451
|
-
:return: Position in unified manipulator space (x, y, z, w) in mm
|
|
452
|
-
:rtype: Vector4
|
|
453
|
-
"""
|
|
454
|
-
raise NotImplementedError
|
|
455
|
-
|
|
456
|
-
@abstractmethod
|
|
457
|
-
def _unified_space_to_platform_space(self, unified_position: Vector4) -> Vector4:
|
|
458
|
-
"""Convert position in unified manipulator space to position in platform space
|
|
459
|
-
|
|
460
|
-
:param unified_position: Position in unified manipulator space (x, y, z, w) in mm
|
|
461
|
-
:type unified_position: Vector4
|
|
462
|
-
:return: Position in platform space (x, y, z, w) in mm
|
|
463
|
-
:rtype: Vector4
|
|
464
|
-
"""
|
|
465
|
-
raise NotImplementedError
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"""Defines what the properties and required functionality of a manipulator are.
|
|
2
|
-
|
|
3
|
-
Most functionality will be implemented on the platform handler side. This is mostly
|
|
4
|
-
for enforcing implementation of the stop method and hold common properties.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from abc import ABC, abstractmethod
|
|
8
|
-
|
|
9
|
-
# Constants
|
|
10
|
-
MM_TO_UM = 1000
|
|
11
|
-
HOURS_TO_SECONDS = 3600
|
|
12
|
-
POSITION_POLL_DELAY = 0.1
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class PlatformManipulator(ABC):
|
|
16
|
-
"""An abstract class that defines the interface for a manipulator."""
|
|
17
|
-
|
|
18
|
-
def __init__(self):
|
|
19
|
-
"""Initialize manipulator."""
|
|
20
|
-
|
|
21
|
-
self._id = None
|
|
22
|
-
self._movement_tolerance = 0.001
|
|
23
|
-
self._calibrated = False
|
|
24
|
-
self._inside_brain = False
|
|
25
|
-
self._can_write = False
|
|
26
|
-
self._reset_timer = None
|
|
27
|
-
self._is_moving = False
|
|
28
|
-
|
|
29
|
-
@abstractmethod
|
|
30
|
-
def stop(self) -> None:
|
|
31
|
-
"""Stop all axes on manipulator
|
|
32
|
-
|
|
33
|
-
:returns None
|
|
34
|
-
"""
|
|
35
|
-
raise NotImplementedError
|
ephys_link/platforms/__init__.py
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
"""Handle communications with New Scale API
|
|
2
|
-
|
|
3
|
-
Implements New Scale specific API calls.
|
|
4
|
-
|
|
5
|
-
This is a subclass of :class:`ephys_link.platform_handler.PlatformHandler`.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
from typing import TYPE_CHECKING
|
|
11
|
-
|
|
12
|
-
# noinspection PyUnresolvedReferences
|
|
13
|
-
from NstMotorCtrl import NstCtrlHostIntf
|
|
14
|
-
from vbl_aquarium.models.ephys_link import (
|
|
15
|
-
AngularResponse,
|
|
16
|
-
BooleanStateResponse,
|
|
17
|
-
CanWriteRequest,
|
|
18
|
-
DriveToDepthRequest,
|
|
19
|
-
DriveToDepthResponse,
|
|
20
|
-
GotoPositionRequest,
|
|
21
|
-
InsideBrainRequest,
|
|
22
|
-
PositionalResponse,
|
|
23
|
-
ShankCountResponse,
|
|
24
|
-
)
|
|
25
|
-
from vbl_aquarium.models.unity import Vector4
|
|
26
|
-
|
|
27
|
-
from ephys_link import common as com
|
|
28
|
-
from ephys_link.platform_handler import PlatformHandler
|
|
29
|
-
from ephys_link.platforms.new_scale_manipulator import NewScaleManipulator
|
|
30
|
-
|
|
31
|
-
if TYPE_CHECKING:
|
|
32
|
-
import socketio
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class NewScaleHandler(PlatformHandler):
|
|
36
|
-
"""Handler for New Scale platform"""
|
|
37
|
-
|
|
38
|
-
def __init__(self) -> None:
|
|
39
|
-
"""Initialize New Scale handler"""
|
|
40
|
-
super().__init__()
|
|
41
|
-
|
|
42
|
-
self.num_axes = 3
|
|
43
|
-
self.dimensions = Vector4(x=15, y=15, z=15, w=0)
|
|
44
|
-
|
|
45
|
-
self.ctrl = NstCtrlHostIntf()
|
|
46
|
-
|
|
47
|
-
# Connect manipulators and initialize
|
|
48
|
-
self.ctrl.ShowProperties()
|
|
49
|
-
self.ctrl.Initialize()
|
|
50
|
-
|
|
51
|
-
def _get_manipulators(self) -> list:
|
|
52
|
-
return list(map(str, range(self.ctrl.PortCount)))
|
|
53
|
-
|
|
54
|
-
def _register_manipulator(self, manipulator_id: str) -> None:
|
|
55
|
-
# Check if ID is numeric
|
|
56
|
-
if not manipulator_id.isnumeric():
|
|
57
|
-
msg = "Manipulator ID must be numeric"
|
|
58
|
-
raise ValueError(msg)
|
|
59
|
-
|
|
60
|
-
# Check if ID is connected
|
|
61
|
-
if manipulator_id not in self._get_manipulators():
|
|
62
|
-
msg = f"Manipulator {manipulator_id} not connected"
|
|
63
|
-
raise ValueError(msg)
|
|
64
|
-
|
|
65
|
-
# Check if there are enough axes
|
|
66
|
-
if int(manipulator_id) * 3 + 2 >= self.ctrl.AxisCount:
|
|
67
|
-
msg = f"Manipulator {manipulator_id} has no axes"
|
|
68
|
-
raise ValueError(msg)
|
|
69
|
-
|
|
70
|
-
# Register manipulator
|
|
71
|
-
first_axis_index = int(manipulator_id) * 3
|
|
72
|
-
self.manipulators[manipulator_id] = NewScaleManipulator(
|
|
73
|
-
manipulator_id,
|
|
74
|
-
self.ctrl.GetAxis(first_axis_index),
|
|
75
|
-
self.ctrl.GetAxis(first_axis_index + 1),
|
|
76
|
-
self.ctrl.GetAxis(first_axis_index + 2),
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
def _unregister_manipulator(self, manipulator_id: str) -> None:
|
|
80
|
-
del self.manipulators[manipulator_id]
|
|
81
|
-
|
|
82
|
-
def _get_pos(self, manipulator_id: str) -> PositionalResponse:
|
|
83
|
-
return self.manipulators[manipulator_id].get_pos()
|
|
84
|
-
|
|
85
|
-
def _get_angles(self, manipulator_id: str) -> AngularResponse:
|
|
86
|
-
raise NotImplementedError
|
|
87
|
-
|
|
88
|
-
def _get_shank_count(self, manipulator_id: str) -> ShankCountResponse:
|
|
89
|
-
raise NotImplementedError
|
|
90
|
-
|
|
91
|
-
async def _goto_pos(self, request: GotoPositionRequest) -> PositionalResponse:
|
|
92
|
-
return await self.manipulators[request.manipulator_id].goto_pos(request)
|
|
93
|
-
|
|
94
|
-
async def _drive_to_depth(self, request: DriveToDepthRequest) -> DriveToDepthResponse:
|
|
95
|
-
return await self.manipulators[request.manipulator_id].drive_to_depth(request)
|
|
96
|
-
|
|
97
|
-
def _set_inside_brain(self, request: InsideBrainRequest) -> BooleanStateResponse:
|
|
98
|
-
self.manipulators[request.manipulator_id].set_inside_brain(request.inside)
|
|
99
|
-
com.dprint(f"[SUCCESS]\t Set inside brain state for manipulator: {request.manipulator_id}\n")
|
|
100
|
-
return BooleanStateResponse(state=request.inside)
|
|
101
|
-
|
|
102
|
-
async def _calibrate(self, manipulator_id: str, sio: socketio.AsyncServer) -> str:
|
|
103
|
-
return "" if self.manipulators[manipulator_id].calibrate() else "Error calling calibrate"
|
|
104
|
-
|
|
105
|
-
def _bypass_calibration(self, manipulator_id: str) -> str:
|
|
106
|
-
self.manipulators[manipulator_id].set_calibrated()
|
|
107
|
-
com.dprint(f"[SUCCESS]\t Bypassed calibration for manipulator" f" {manipulator_id}\n")
|
|
108
|
-
return ""
|
|
109
|
-
|
|
110
|
-
def _set_can_write(self, request: CanWriteRequest) -> BooleanStateResponse:
|
|
111
|
-
self.manipulators[request.manipulator_id].set_can_write(request)
|
|
112
|
-
com.dprint(f"[SUCCESS]\t Set can_write state for manipulator {request.manipulator_id}\n")
|
|
113
|
-
return BooleanStateResponse(state=request.can_write)
|
|
114
|
-
|
|
115
|
-
def _platform_space_to_unified_space(self, platform_position: Vector4) -> Vector4:
|
|
116
|
-
# unified <- platform
|
|
117
|
-
# +x <- -x
|
|
118
|
-
# +y <- +z
|
|
119
|
-
# +z <- +y
|
|
120
|
-
# +d <- -d
|
|
121
|
-
|
|
122
|
-
return Vector4(
|
|
123
|
-
x=self.dimensions.x - platform_position.x,
|
|
124
|
-
y=platform_position.z,
|
|
125
|
-
z=platform_position.y,
|
|
126
|
-
w=self.dimensions.z - platform_position.w,
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
def _unified_space_to_platform_space(self, unified_position: Vector4) -> Vector4:
|
|
130
|
-
# platform <- unified
|
|
131
|
-
# +x <- -x
|
|
132
|
-
# +y <- +z
|
|
133
|
-
# +z <- +y
|
|
134
|
-
# +d <- -d
|
|
135
|
-
|
|
136
|
-
return Vector4(
|
|
137
|
-
x=self.dimensions.x - unified_position.x,
|
|
138
|
-
y=unified_position.z,
|
|
139
|
-
z=unified_position.y,
|
|
140
|
-
w=self.dimensions.z - unified_position.w,
|
|
141
|
-
)
|