ephys-link 2.0.1__py3-none-any.whl → 2.1.0b0__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/back_end/platform_handler.py +13 -1
- ephys_link/bindings/fake_binding.py +2 -1
- ephys_link/bindings/mpm_binding.py +15 -9
- ephys_link/bindings/ump_binding.py +239 -0
- ephys_link/utils/base_binding.py +2 -1
- {ephys_link-2.0.1.dist-info → ephys_link-2.1.0b0.dist-info}/METADATA +2 -2
- {ephys_link-2.0.1.dist-info → ephys_link-2.1.0b0.dist-info}/RECORD +11 -11
- ephys_link/bindings/ump_4_binding.py +0 -157
- {ephys_link-2.0.1.dist-info → ephys_link-2.1.0b0.dist-info}/WHEEL +0 -0
- {ephys_link-2.0.1.dist-info → ephys_link-2.1.0b0.dist-info}/entry_points.txt +0 -0
- {ephys_link-2.0.1.dist-info → ephys_link-2.1.0b0.dist-info}/licenses/LICENSE +0 -0
ephys_link/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.
|
|
1
|
+
__version__ = "2.1.0b0"
|
|
@@ -70,10 +70,22 @@ class PlatformHandler:
|
|
|
70
70
|
Returns:
|
|
71
71
|
Bindings for the specified platform type.
|
|
72
72
|
"""
|
|
73
|
+
|
|
74
|
+
# What the user supplied.
|
|
75
|
+
selected_type = options.type
|
|
76
|
+
|
|
73
77
|
for binding_type in get_bindings():
|
|
74
78
|
binding_cli_name = binding_type.get_cli_name()
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
# Notify deprecation of "ump-4" and "ump-3" CLI options and fix.
|
|
81
|
+
if selected_type in ("ump-4", "ump-3"):
|
|
82
|
+
self._console.error_print(
|
|
83
|
+
"DEPRECATION",
|
|
84
|
+
f"CLI option '{selected_type}' is deprecated and will be removed in v3.0.0. Use 'ump' instead.",
|
|
85
|
+
)
|
|
86
|
+
selected_type = "ump"
|
|
87
|
+
|
|
88
|
+
if binding_cli_name == selected_type:
|
|
77
89
|
# Pass in HTTP port for Pathfinder MPM.
|
|
78
90
|
if binding_cli_name == "pathfinder-mpm":
|
|
79
91
|
return MPMBinding(options.mpm_port)
|
|
@@ -57,8 +57,9 @@ class FakeBinding(BaseBinding):
|
|
|
57
57
|
async def get_shank_count(self, manipulator_id: str) -> int:
|
|
58
58
|
return 1
|
|
59
59
|
|
|
60
|
+
@staticmethod
|
|
60
61
|
@override
|
|
61
|
-
def get_movement_tolerance(
|
|
62
|
+
def get_movement_tolerance() -> float:
|
|
62
63
|
return 0.001
|
|
63
64
|
|
|
64
65
|
@override
|
|
@@ -62,12 +62,11 @@ class MPMBinding(BaseBinding):
|
|
|
62
62
|
"AN",
|
|
63
63
|
)
|
|
64
64
|
|
|
65
|
-
# Server
|
|
66
|
-
|
|
65
|
+
# Server data update rate (30 FPS).
|
|
66
|
+
SERVER_DATA_UPDATE_RATE = 1 / 30
|
|
67
67
|
|
|
68
68
|
# Movement polling preferences.
|
|
69
69
|
UNCHANGED_COUNTER_LIMIT = 10
|
|
70
|
-
POLL_INTERVAL = 0.1
|
|
71
70
|
|
|
72
71
|
# Speed preferences (mm/s to use coarse mode).
|
|
73
72
|
COARSE_SPEED_THRESHOLD = 0.1
|
|
@@ -113,7 +112,8 @@ class MPMBinding(BaseBinding):
|
|
|
113
112
|
manipulator_data: dict[str, float] = await self._manipulator_data(manipulator_id)
|
|
114
113
|
stage_z: float = manipulator_data["Stage_Z"]
|
|
115
114
|
|
|
116
|
-
|
|
115
|
+
# Wait for the stage to stabilize.
|
|
116
|
+
await sleep(self.SERVER_DATA_UPDATE_RATE)
|
|
117
117
|
|
|
118
118
|
return Vector4(
|
|
119
119
|
x=manipulator_data["Stage_X"],
|
|
@@ -139,8 +139,9 @@ class MPMBinding(BaseBinding):
|
|
|
139
139
|
async def get_shank_count(self, manipulator_id: str) -> int:
|
|
140
140
|
return int((await self._manipulator_data(manipulator_id))["ShankCount"]) # pyright: ignore [reportAny]
|
|
141
141
|
|
|
142
|
+
@staticmethod
|
|
142
143
|
@override
|
|
143
|
-
def get_movement_tolerance(
|
|
144
|
+
def get_movement_tolerance() -> float:
|
|
144
145
|
return 0.01
|
|
145
146
|
|
|
146
147
|
@override
|
|
@@ -180,7 +181,7 @@ class MPMBinding(BaseBinding):
|
|
|
180
181
|
and unchanged_counter < self.UNCHANGED_COUNTER_LIMIT
|
|
181
182
|
):
|
|
182
183
|
# Wait for a short time before checking again.
|
|
183
|
-
await sleep(self.
|
|
184
|
+
await sleep(self.SERVER_DATA_UPDATE_RATE)
|
|
184
185
|
|
|
185
186
|
# Update current position.
|
|
186
187
|
current_position = await self.get_position(manipulator_id)
|
|
@@ -219,9 +220,13 @@ class MPMBinding(BaseBinding):
|
|
|
219
220
|
)
|
|
220
221
|
|
|
221
222
|
# Wait for the manipulator to reach the target depth or be stopped or get stuck.
|
|
222
|
-
while
|
|
223
|
+
while (
|
|
224
|
+
not self._movement_stopped
|
|
225
|
+
and not abs(current_depth - depth) <= self.get_movement_tolerance()
|
|
226
|
+
and unchanged_counter < self.UNCHANGED_COUNTER_LIMIT
|
|
227
|
+
):
|
|
223
228
|
# Wait for a short time before checking again.
|
|
224
|
-
await sleep(self.
|
|
229
|
+
await sleep(self.SERVER_DATA_UPDATE_RATE)
|
|
225
230
|
|
|
226
231
|
# Get the current depth.
|
|
227
232
|
current_depth = (await self.get_position(manipulator_id)).w
|
|
@@ -281,10 +286,11 @@ class MPMBinding(BaseBinding):
|
|
|
281
286
|
)
|
|
282
287
|
|
|
283
288
|
# Helper functions.
|
|
289
|
+
|
|
284
290
|
async def _query_data(self) -> dict[str, Any]: # pyright: ignore [reportExplicitAny]
|
|
285
291
|
try:
|
|
286
292
|
# Update cache if it's expired.
|
|
287
|
-
if get_running_loop().time() - self.cache_time > self.
|
|
293
|
+
if get_running_loop().time() - self.cache_time > self.SERVER_DATA_UPDATE_RATE:
|
|
288
294
|
# noinspection PyTypeChecker
|
|
289
295
|
self.cache = (await get_running_loop().run_in_executor(None, get, self._url)).json()
|
|
290
296
|
self.cache_time = get_running_loop().time()
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Bindings for Sensapex uMp platform.
|
|
2
|
+
|
|
3
|
+
Usage: Instantiate UmpBindings to interact with Sensapex uMp-4 and uMp-3 manipulators.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from asyncio import get_running_loop
|
|
7
|
+
from typing import NoReturn, final, override
|
|
8
|
+
|
|
9
|
+
from sensapex import UMP, SensapexDevice # pyright: ignore [reportMissingTypeStubs]
|
|
10
|
+
from vbl_aquarium.models.unity import Vector4
|
|
11
|
+
|
|
12
|
+
from ephys_link.utils.base_binding import BaseBinding
|
|
13
|
+
from ephys_link.utils.constants import RESOURCES_DIRECTORY
|
|
14
|
+
from ephys_link.utils.converters import (
|
|
15
|
+
list_to_vector4,
|
|
16
|
+
scalar_mm_to_um,
|
|
17
|
+
um_to_mm,
|
|
18
|
+
vector4_to_array,
|
|
19
|
+
vector_mm_to_um,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@final
|
|
24
|
+
class UmpBinding(BaseBinding):
|
|
25
|
+
"""Bindings for uMp platform"""
|
|
26
|
+
|
|
27
|
+
# Number of axes for uMp-3.
|
|
28
|
+
UMP_3_NUM_AXES = 3
|
|
29
|
+
|
|
30
|
+
def __init__(self) -> None:
|
|
31
|
+
"""Initialize uMp bindings."""
|
|
32
|
+
|
|
33
|
+
# Establish connection to Sensapex API (exit if connection fails).
|
|
34
|
+
UMP.set_library_path(RESOURCES_DIRECTORY)
|
|
35
|
+
self._ump: UMP = UMP.get_ump() # pyright: ignore [reportUnknownMemberType]
|
|
36
|
+
|
|
37
|
+
# Exit if no manipulators are connected.
|
|
38
|
+
device_ids: list[str] = list(map(str, self._ump.list_devices()))
|
|
39
|
+
if len(device_ids) == 0:
|
|
40
|
+
msg = "No manipulators connected."
|
|
41
|
+
raise RuntimeError(msg)
|
|
42
|
+
|
|
43
|
+
# Currently only supports using uMp-4 XOR uMp-3. Exit if both are connected.
|
|
44
|
+
|
|
45
|
+
# Use the first device as the reference for the number of axes.
|
|
46
|
+
self.num_axes: int = self._get_device(device_ids[0]).n_axes()
|
|
47
|
+
|
|
48
|
+
if any(self._get_device(device_id).n_axes() != self.num_axes for device_id in device_ids): # pyright: ignore [reportUnknownArgumentType, reportUnknownMemberType]
|
|
49
|
+
msg = "uMp-4 and uMp-3 cannot be used at the same time."
|
|
50
|
+
raise RuntimeError(msg)
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
@override
|
|
54
|
+
def get_display_name() -> str:
|
|
55
|
+
return "Sensapex uMp"
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
@override
|
|
59
|
+
def get_cli_name() -> str:
|
|
60
|
+
return "ump"
|
|
61
|
+
|
|
62
|
+
@override
|
|
63
|
+
async def get_manipulators(self) -> list[str]:
|
|
64
|
+
return list(map(str, self._ump.list_devices()))
|
|
65
|
+
|
|
66
|
+
@override
|
|
67
|
+
async def get_axes_count(self) -> int:
|
|
68
|
+
return self.num_axes
|
|
69
|
+
|
|
70
|
+
@override
|
|
71
|
+
def get_dimensions(self) -> Vector4:
|
|
72
|
+
return Vector4(x=20, y=20, z=20, w=20)
|
|
73
|
+
|
|
74
|
+
@override
|
|
75
|
+
async def get_position(self, manipulator_id: str) -> Vector4:
|
|
76
|
+
# Get the position list from the device.
|
|
77
|
+
position = self._get_device(manipulator_id).get_pos(1) # pyright: ignore [reportUnknownMemberType]
|
|
78
|
+
|
|
79
|
+
# Copy x-coordinate into depth for uMp-3.
|
|
80
|
+
return um_to_mm(list_to_vector4([*position, position[0]] if self._is_ump_3() else position))
|
|
81
|
+
|
|
82
|
+
@override
|
|
83
|
+
async def get_angles(self, manipulator_id: str) -> NoReturn:
|
|
84
|
+
"""uMp does not support getting angles so raise an error.
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
AttributeError: uMp does not support getting angles.
|
|
88
|
+
"""
|
|
89
|
+
error_message = "uMp does not support getting angles"
|
|
90
|
+
raise AttributeError(error_message)
|
|
91
|
+
|
|
92
|
+
@override
|
|
93
|
+
async def get_shank_count(self, manipulator_id: str) -> NoReturn:
|
|
94
|
+
"""uMp does not support getting shank count so raise an error.
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
AttributeError: uMp does not support getting shank count.
|
|
98
|
+
"""
|
|
99
|
+
error_message = "uMp does not support getting shank count"
|
|
100
|
+
raise AttributeError(error_message)
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
@override
|
|
104
|
+
def get_movement_tolerance() -> float:
|
|
105
|
+
return 0.001
|
|
106
|
+
|
|
107
|
+
@override
|
|
108
|
+
async def set_position(self, manipulator_id: str, position: Vector4, speed: float) -> Vector4:
|
|
109
|
+
# Convert position to micrometers array.
|
|
110
|
+
target_position_um = vector4_to_array(vector_mm_to_um(position))
|
|
111
|
+
|
|
112
|
+
# Request movement (clip 4th axis for uMp-3).
|
|
113
|
+
movement = self._get_device(
|
|
114
|
+
manipulator_id
|
|
115
|
+
).goto_pos( # pyright: ignore [reportUnknownMemberType]
|
|
116
|
+
target_position_um[: self.UMP_3_NUM_AXES] if self._is_ump_3() else target_position_um,
|
|
117
|
+
scalar_mm_to_um(speed),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Wait for movement to finish.
|
|
121
|
+
_ = await get_running_loop().run_in_executor(None, movement.finished_event.wait, None)
|
|
122
|
+
|
|
123
|
+
# Handle interrupted movement.
|
|
124
|
+
if movement.interrupted:
|
|
125
|
+
error_message = f"Manipulator {manipulator_id} interrupted: {movement.interrupt_reason}" # pyright: ignore [reportUnknownMemberType]
|
|
126
|
+
raise RuntimeError(error_message)
|
|
127
|
+
|
|
128
|
+
# Handle empty end position.
|
|
129
|
+
if movement.last_pos is None or len(movement.last_pos) == 0: # pyright: ignore [reportUnknownMemberType, reportUnknownArgumentType]
|
|
130
|
+
error_message = f"Manipulator {manipulator_id} did not reach target position"
|
|
131
|
+
raise RuntimeError(error_message)
|
|
132
|
+
|
|
133
|
+
return um_to_mm(
|
|
134
|
+
list_to_vector4([*movement.last_pos, movement.last_pos[0]] if self._is_ump_3() else list(movement.last_pos)) # pyright: ignore [reportUnknownArgumentType, reportUnknownMemberType]
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
@override
|
|
138
|
+
async def set_depth(self, manipulator_id: str, depth: float, speed: float) -> float:
|
|
139
|
+
# Augment current position with depth.
|
|
140
|
+
current_position = await self.get_position(manipulator_id)
|
|
141
|
+
new_platform_position = current_position.model_copy(update={"x" if self._is_ump_3() else "w": depth})
|
|
142
|
+
|
|
143
|
+
# Make the movement.
|
|
144
|
+
final_platform_position = await self.set_position(manipulator_id, new_platform_position, speed)
|
|
145
|
+
|
|
146
|
+
# Return the final depth.
|
|
147
|
+
return float(final_platform_position.w)
|
|
148
|
+
|
|
149
|
+
@override
|
|
150
|
+
async def stop(self, manipulator_id: str) -> None:
|
|
151
|
+
self._get_device(manipulator_id).stop()
|
|
152
|
+
|
|
153
|
+
@override
|
|
154
|
+
def platform_space_to_unified_space(self, platform_space: Vector4) -> Vector4:
|
|
155
|
+
"""
|
|
156
|
+
For uMp-3:
|
|
157
|
+
unified <- platform
|
|
158
|
+
+x <- +y
|
|
159
|
+
+y <- -x
|
|
160
|
+
+z <- -z
|
|
161
|
+
+d <- +d
|
|
162
|
+
|
|
163
|
+
For uMp-4:
|
|
164
|
+
unified <- platform
|
|
165
|
+
+x <- +y
|
|
166
|
+
+y <- -z
|
|
167
|
+
+z <- +x
|
|
168
|
+
+d <- +d
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
Vector4(
|
|
173
|
+
x=platform_space.y,
|
|
174
|
+
y=self.get_dimensions().x - platform_space.x,
|
|
175
|
+
z=self.get_dimensions().z - platform_space.z,
|
|
176
|
+
w=platform_space.w,
|
|
177
|
+
)
|
|
178
|
+
if self._is_ump_3()
|
|
179
|
+
else Vector4(
|
|
180
|
+
x=platform_space.y,
|
|
181
|
+
y=self.get_dimensions().z - platform_space.z,
|
|
182
|
+
z=platform_space.x,
|
|
183
|
+
w=platform_space.w,
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
@override
|
|
188
|
+
def unified_space_to_platform_space(self, unified_space: Vector4) -> Vector4:
|
|
189
|
+
"""
|
|
190
|
+
For uMp-3:
|
|
191
|
+
platform <- unified
|
|
192
|
+
+x <- -y
|
|
193
|
+
+y <- +x
|
|
194
|
+
+z <- -z
|
|
195
|
+
+d <- +d
|
|
196
|
+
|
|
197
|
+
For uMp-4:
|
|
198
|
+
platform <- unified
|
|
199
|
+
+x <- +z
|
|
200
|
+
+y <- +x
|
|
201
|
+
+z <- -y
|
|
202
|
+
+d <- +d
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
Vector4(
|
|
207
|
+
x=self.get_dimensions().y - unified_space.y,
|
|
208
|
+
y=unified_space.x,
|
|
209
|
+
z=self.get_dimensions().z - unified_space.z,
|
|
210
|
+
w=unified_space.w,
|
|
211
|
+
)
|
|
212
|
+
if self._is_ump_3()
|
|
213
|
+
else Vector4(
|
|
214
|
+
x=unified_space.z,
|
|
215
|
+
y=unified_space.x,
|
|
216
|
+
z=self.get_dimensions().y - unified_space.y,
|
|
217
|
+
w=unified_space.w,
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Helper methods.
|
|
222
|
+
def _get_device(self, manipulator_id: str) -> SensapexDevice:
|
|
223
|
+
"""Returns the Sensapex device object for the given manipulator ID.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
manipulator_id: Manipulator ID.
|
|
227
|
+
Returns:
|
|
228
|
+
Sensapex device object.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
return self._ump.get_device(int(manipulator_id)) # pyright: ignore [reportUnknownMemberType]
|
|
232
|
+
|
|
233
|
+
def _is_ump_3(self) -> bool:
|
|
234
|
+
"""Check if the current device is uMp-3.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
True if the device is uMp-3, False otherwise.
|
|
238
|
+
"""
|
|
239
|
+
return self.num_axes == self.UMP_3_NUM_AXES
|
ephys_link/utils/base_binding.py
CHANGED
|
@@ -99,8 +99,9 @@ class BaseBinding(ABC):
|
|
|
99
99
|
Number of shanks on the manipulator.
|
|
100
100
|
"""
|
|
101
101
|
|
|
102
|
+
@staticmethod
|
|
102
103
|
@abstractmethod
|
|
103
|
-
def get_movement_tolerance(
|
|
104
|
+
def get_movement_tolerance() -> float:
|
|
104
105
|
"""Get the tolerance for how close the final position must be to the target position in a movement (mm).
|
|
105
106
|
|
|
106
107
|
Returns:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ephys-link
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0b0
|
|
4
4
|
Summary: A Python Socket.IO server that allows any Socket.IO-compliant application to communicate with manipulators used in electrophysiology experiments.
|
|
5
5
|
Project-URL: Documentation, https://virtualbrainlab.org/ephys_link/installation_and_use.html
|
|
6
6
|
Project-URL: Issues, https://github.com/VirtualBrainLab/ephys-link/issues
|
|
@@ -28,7 +28,7 @@ Requires-Dist: keyboard==0.13.5
|
|
|
28
28
|
Requires-Dist: packaging==24.2
|
|
29
29
|
Requires-Dist: platformdirs==4.3.7
|
|
30
30
|
Requires-Dist: pyserial==3.5
|
|
31
|
-
Requires-Dist: python-socketio[asyncio-client]==5.
|
|
31
|
+
Requires-Dist: python-socketio[asyncio-client]==5.13.0
|
|
32
32
|
Requires-Dist: requests==2.32.3
|
|
33
33
|
Requires-Dist: rich==14.0.0
|
|
34
34
|
Requires-Dist: sensapex==1.400.3
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
ephys_link/__about__.py,sha256=
|
|
1
|
+
ephys_link/__about__.py,sha256=72sTjjXH8lKUBOrH9ADJVFplt-JL7YxNRjHxZ7aL25E,24
|
|
2
2
|
ephys_link/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
ephys_link/__main__.py,sha256=sbFdC6KJjTfXDgRraU_fmGRPcF4I1Ur9PRDiD86dkRI,1449
|
|
4
4
|
ephys_link/back_end/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
ephys_link/back_end/platform_handler.py,sha256=
|
|
5
|
+
ephys_link/back_end/platform_handler.py,sha256=gdiO6d0L-DWWLEJOL6eP6685tOC6otffmhfIBtPjhq0,12604
|
|
6
6
|
ephys_link/back_end/server.py,sha256=mb3K3pXSO-gHaSj1CGJ0v3CSOW5YCi-p0EOKoySRzKQ,10322
|
|
7
7
|
ephys_link/bindings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
ephys_link/bindings/fake_binding.py,sha256=
|
|
9
|
-
ephys_link/bindings/mpm_binding.py,sha256=
|
|
10
|
-
ephys_link/bindings/
|
|
8
|
+
ephys_link/bindings/fake_binding.py,sha256=PI7zYv-SjWsGFEs_FVu5Z5l9gykIqG3C7pQISdbwdoY,2357
|
|
9
|
+
ephys_link/bindings/mpm_binding.py,sha256=vn7IKqdiZ6_MX91zomqDXX08ONHwVVgWncRuJTxJpOM,10872
|
|
10
|
+
ephys_link/bindings/ump_binding.py,sha256=-RwzUggGYsznhV6yiXb96y6WD-e3E7-tcDk6NfesiWU,8080
|
|
11
11
|
ephys_link/front_end/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
ephys_link/front_end/cli.py,sha256=isIJs_sZbz7VbNvLgi-HyDlE-TyKD12auDhMTxAkWQU,3099
|
|
13
13
|
ephys_link/front_end/gui.py,sha256=MDcrTS_Xz9bopAgamh4HknqRC10W8E6eOS3Kss_2ZKQ,6864
|
|
14
14
|
ephys_link/resources/libum.dll,sha256=YaD4dwiSNohx-XxHjx2eQWPOBEVvUIXARvx37e_yqNw,316316
|
|
15
15
|
ephys_link/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
ephys_link/utils/base_binding.py,sha256=
|
|
16
|
+
ephys_link/utils/base_binding.py,sha256=rq0FtvkjAN287nX8rZwR-j6YPzV3Pkp8PqJfZlhIzqo,5413
|
|
17
17
|
ephys_link/utils/console.py,sha256=52SYvXv_7Fx8QDL3RMFQoggQ1n5W93Yu5aU7uuJQgfg,3904
|
|
18
18
|
ephys_link/utils/constants.py,sha256=1aML7zBNTM5onVSf6NDrYIR33VJy-dIHd1lFORVBGbM,725
|
|
19
19
|
ephys_link/utils/converters.py,sha256=ZdVmIX-LHCwM__F0SpjN_mfNGGetr1U97xvHd0hf8T0,2038
|
|
20
20
|
ephys_link/utils/startup.py,sha256=jZVed78tuWjUuZqWVgii_zumDr87T-ikEtOFa6KTE_E,2500
|
|
21
|
-
ephys_link-2.
|
|
22
|
-
ephys_link-2.
|
|
23
|
-
ephys_link-2.
|
|
24
|
-
ephys_link-2.
|
|
25
|
-
ephys_link-2.
|
|
21
|
+
ephys_link-2.1.0b0.dist-info/METADATA,sha256=i3-gX7xuUweGrkbvtoQQ01nNf2jsrd-hYtvLL_zF4tE,4609
|
|
22
|
+
ephys_link-2.1.0b0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
+
ephys_link-2.1.0b0.dist-info/entry_points.txt,sha256=o8wV3AdnJ9o47vg9ymKxPNVq9pMdPq8UZHE_iyAJx-k,124
|
|
24
|
+
ephys_link-2.1.0b0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
25
|
+
ephys_link-2.1.0b0.dist-info/RECORD,,
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
"""Bindings for Sensapex uMp-4 platform.
|
|
2
|
-
|
|
3
|
-
Usage: Instantiate Ump4Bindings to interact with the Sensapex uMp-4 platform.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from asyncio import get_running_loop
|
|
7
|
-
from typing import NoReturn, final, override
|
|
8
|
-
|
|
9
|
-
from sensapex import UMP, SensapexDevice # pyright: ignore [reportMissingTypeStubs]
|
|
10
|
-
from vbl_aquarium.models.unity import Vector4
|
|
11
|
-
|
|
12
|
-
from ephys_link.utils.base_binding import BaseBinding
|
|
13
|
-
from ephys_link.utils.constants import RESOURCES_DIRECTORY
|
|
14
|
-
from ephys_link.utils.converters import (
|
|
15
|
-
list_to_vector4,
|
|
16
|
-
scalar_mm_to_um,
|
|
17
|
-
um_to_mm,
|
|
18
|
-
vector4_to_array,
|
|
19
|
-
vector_mm_to_um,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@final
|
|
24
|
-
class Ump4Binding(BaseBinding):
|
|
25
|
-
"""Bindings for UMP-4 platform"""
|
|
26
|
-
|
|
27
|
-
def __init__(self) -> None:
|
|
28
|
-
"""Initialize UMP-4 bindings."""
|
|
29
|
-
|
|
30
|
-
# Establish connection to Sensapex API (exit if connection fails).
|
|
31
|
-
UMP.set_library_path(RESOURCES_DIRECTORY)
|
|
32
|
-
self._ump = UMP.get_ump() # pyright: ignore [reportUnknownMemberType]
|
|
33
|
-
|
|
34
|
-
@staticmethod
|
|
35
|
-
@override
|
|
36
|
-
def get_display_name() -> str:
|
|
37
|
-
return "Sensapex uMp-4"
|
|
38
|
-
|
|
39
|
-
@staticmethod
|
|
40
|
-
@override
|
|
41
|
-
def get_cli_name() -> str:
|
|
42
|
-
return "ump-4"
|
|
43
|
-
|
|
44
|
-
@override
|
|
45
|
-
async def get_manipulators(self) -> list[str]:
|
|
46
|
-
return list(map(str, self._ump.list_devices()))
|
|
47
|
-
|
|
48
|
-
@override
|
|
49
|
-
async def get_axes_count(self) -> int:
|
|
50
|
-
return 4
|
|
51
|
-
|
|
52
|
-
@override
|
|
53
|
-
def get_dimensions(self) -> Vector4:
|
|
54
|
-
return Vector4(x=20, y=20, z=20, w=20)
|
|
55
|
-
|
|
56
|
-
@override
|
|
57
|
-
async def get_position(self, manipulator_id: str) -> Vector4:
|
|
58
|
-
return um_to_mm(list_to_vector4(self._get_device(manipulator_id).get_pos(1))) # pyright: ignore [reportUnknownMemberType]
|
|
59
|
-
|
|
60
|
-
@override
|
|
61
|
-
async def get_angles(self, manipulator_id: str) -> NoReturn:
|
|
62
|
-
"""uMp-4 does not support getting angles so raise an error.
|
|
63
|
-
|
|
64
|
-
Raises:
|
|
65
|
-
AttributeError: uMp-4 does not support getting angles.
|
|
66
|
-
"""
|
|
67
|
-
error_message = "UMP-4 does not support getting angles"
|
|
68
|
-
raise AttributeError(error_message)
|
|
69
|
-
|
|
70
|
-
@override
|
|
71
|
-
async def get_shank_count(self, manipulator_id: str) -> NoReturn:
|
|
72
|
-
"""uMp-4 does not support getting shank count so raise an error.
|
|
73
|
-
|
|
74
|
-
Raises:
|
|
75
|
-
AttributeError: uMp-4 does not support getting shank count.
|
|
76
|
-
"""
|
|
77
|
-
error_message = "UMP-4 does not support getting shank count"
|
|
78
|
-
raise AttributeError(error_message)
|
|
79
|
-
|
|
80
|
-
@override
|
|
81
|
-
def get_movement_tolerance(self) -> float:
|
|
82
|
-
return 0.001
|
|
83
|
-
|
|
84
|
-
@override
|
|
85
|
-
async def set_position(self, manipulator_id: str, position: Vector4, speed: float) -> Vector4:
|
|
86
|
-
# Convert position to micrometers.
|
|
87
|
-
target_position_um = vector_mm_to_um(position)
|
|
88
|
-
|
|
89
|
-
# Request movement.
|
|
90
|
-
movement = self._get_device(manipulator_id).goto_pos( # pyright: ignore [reportUnknownMemberType]
|
|
91
|
-
vector4_to_array(target_position_um), scalar_mm_to_um(speed)
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
# Wait for movement to finish.
|
|
95
|
-
_ = await get_running_loop().run_in_executor(None, movement.finished_event.wait, None)
|
|
96
|
-
|
|
97
|
-
# Handle interrupted movement.
|
|
98
|
-
if movement.interrupted:
|
|
99
|
-
error_message = f"Manipulator {manipulator_id} interrupted: {movement.interrupt_reason}" # pyright: ignore [reportUnknownMemberType]
|
|
100
|
-
raise RuntimeError(error_message)
|
|
101
|
-
|
|
102
|
-
# Handle empty end position.
|
|
103
|
-
if movement.last_pos is None or len(movement.last_pos) == 0: # pyright: ignore [reportUnknownMemberType, reportUnknownArgumentType]
|
|
104
|
-
error_message = f"Manipulator {manipulator_id} did not reach target position"
|
|
105
|
-
raise RuntimeError(error_message)
|
|
106
|
-
|
|
107
|
-
return um_to_mm(list_to_vector4(movement.last_pos)) # pyright: ignore [reportArgumentType, reportUnknownMemberType]
|
|
108
|
-
|
|
109
|
-
@override
|
|
110
|
-
async def set_depth(self, manipulator_id: str, depth: float, speed: float) -> float:
|
|
111
|
-
# Augment current position with depth.
|
|
112
|
-
current_position = await self.get_position(manipulator_id)
|
|
113
|
-
new_platform_position = current_position.model_copy(update={"w": depth})
|
|
114
|
-
|
|
115
|
-
# Make the movement.
|
|
116
|
-
final_platform_position = await self.set_position(manipulator_id, new_platform_position, speed)
|
|
117
|
-
|
|
118
|
-
# Return the final depth.
|
|
119
|
-
return float(final_platform_position.w)
|
|
120
|
-
|
|
121
|
-
@override
|
|
122
|
-
async def stop(self, manipulator_id: str) -> None:
|
|
123
|
-
self._get_device(manipulator_id).stop()
|
|
124
|
-
|
|
125
|
-
@override
|
|
126
|
-
def platform_space_to_unified_space(self, platform_space: Vector4) -> Vector4:
|
|
127
|
-
# unified <- platform
|
|
128
|
-
# +x <- +y
|
|
129
|
-
# +y <- -z
|
|
130
|
-
# +z <- +x
|
|
131
|
-
# +d <- +d
|
|
132
|
-
|
|
133
|
-
return Vector4(
|
|
134
|
-
x=platform_space.y,
|
|
135
|
-
y=self.get_dimensions().z - platform_space.z,
|
|
136
|
-
z=platform_space.x,
|
|
137
|
-
w=platform_space.w,
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
@override
|
|
141
|
-
def unified_space_to_platform_space(self, unified_space: Vector4) -> Vector4:
|
|
142
|
-
# platform <- unified
|
|
143
|
-
# +x <- +z
|
|
144
|
-
# +y <- +x
|
|
145
|
-
# +z <- -y
|
|
146
|
-
# +d <- +d
|
|
147
|
-
|
|
148
|
-
return Vector4(
|
|
149
|
-
x=unified_space.z,
|
|
150
|
-
y=unified_space.x,
|
|
151
|
-
z=self.get_dimensions().z - unified_space.y,
|
|
152
|
-
w=unified_space.w,
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
# Helper methods.
|
|
156
|
-
def _get_device(self, manipulator_id: str) -> SensapexDevice:
|
|
157
|
-
return self._ump.get_device(int(manipulator_id)) # pyright: ignore [reportUnknownMemberType]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|