ephys-link 2.0.0b5__py3-none-any.whl → 2.0.0b6__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 +3 -0
- ephys_link/bindings/ump_3_bindings.py +138 -0
- ephys_link/bindings/ump_4_bindings.py +2 -1
- {ephys_link-2.0.0b5.dist-info → ephys_link-2.0.0b6.dist-info}/METADATA +6 -6
- {ephys_link-2.0.0b5.dist-info → ephys_link-2.0.0b6.dist-info}/RECORD +9 -8
- {ephys_link-2.0.0b5.dist-info → ephys_link-2.0.0b6.dist-info}/WHEEL +0 -0
- {ephys_link-2.0.0b5.dist-info → ephys_link-2.0.0b6.dist-info}/entry_points.txt +0 -0
- {ephys_link-2.0.0b5.dist-info → ephys_link-2.0.0b6.dist-info}/licenses/LICENSE +0 -0
ephys_link/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.0.
|
|
1
|
+
__version__ = "2.0.0b6"
|
|
@@ -27,6 +27,7 @@ from vbl_aquarium.models.unity import Vector4
|
|
|
27
27
|
from ephys_link.__about__ import __version__
|
|
28
28
|
from ephys_link.bindings.fake_bindings import FakeBindings
|
|
29
29
|
from ephys_link.bindings.mpm_bindings import MPMBinding
|
|
30
|
+
from ephys_link.bindings.ump_3_bindings import Ump3Bindings
|
|
30
31
|
from ephys_link.bindings.ump_4_bindings import Ump4Bindings
|
|
31
32
|
from ephys_link.util.base_bindings import BaseBindings
|
|
32
33
|
from ephys_link.util.common import vector4_to_array
|
|
@@ -69,6 +70,8 @@ class PlatformHandler:
|
|
|
69
70
|
match options.type:
|
|
70
71
|
case "ump-4":
|
|
71
72
|
return Ump4Bindings()
|
|
73
|
+
case "ump-3":
|
|
74
|
+
return Ump3Bindings()
|
|
72
75
|
case "pathfinder-mpm":
|
|
73
76
|
return MPMBinding(options.mpm_port)
|
|
74
77
|
case "fake":
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Bindings for Sensapex uMp-3 platform.
|
|
2
|
+
|
|
3
|
+
Usage: Instantiate Ump4Bindings to interact with the Sensapex uMp-4 platform.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from asyncio import get_running_loop
|
|
7
|
+
|
|
8
|
+
from sensapex import UMP, SensapexDevice
|
|
9
|
+
from vbl_aquarium.models.unity import Vector3, Vector4
|
|
10
|
+
|
|
11
|
+
from ephys_link.util.base_bindings import BaseBindings
|
|
12
|
+
from ephys_link.util.common import (
|
|
13
|
+
RESOURCES_PATH,
|
|
14
|
+
array_to_vector4,
|
|
15
|
+
scalar_mm_to_um,
|
|
16
|
+
um_to_mm,
|
|
17
|
+
vector4_to_array,
|
|
18
|
+
vector_mm_to_um,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Ump3Bindings(BaseBindings):
|
|
23
|
+
"""Bindings for UMP-3 platform"""
|
|
24
|
+
|
|
25
|
+
def __init__(self) -> None:
|
|
26
|
+
"""Initialize UMP-3 bindings."""
|
|
27
|
+
|
|
28
|
+
# Establish connection to Sensapex API (exit if connection fails).
|
|
29
|
+
UMP.set_library_path(RESOURCES_PATH)
|
|
30
|
+
self._ump = UMP.get_ump()
|
|
31
|
+
if self._ump is None:
|
|
32
|
+
error_message = "Unable to connect to uMp"
|
|
33
|
+
raise ValueError(error_message)
|
|
34
|
+
|
|
35
|
+
async def get_manipulators(self) -> list[str]:
|
|
36
|
+
return list(map(str, self._ump.list_devices()))
|
|
37
|
+
|
|
38
|
+
async def get_axes_count(self) -> int:
|
|
39
|
+
return 3
|
|
40
|
+
|
|
41
|
+
def get_dimensions(self) -> Vector4:
|
|
42
|
+
return Vector4(x=20, y=20, z=20, w=20)
|
|
43
|
+
|
|
44
|
+
async def get_position(self, manipulator_id: str) -> Vector4:
|
|
45
|
+
manipulator_position = self._get_device(manipulator_id).get_pos(1)
|
|
46
|
+
|
|
47
|
+
# Add the depth axis to the end of the position.
|
|
48
|
+
manipulator_position.append(manipulator_position[0])
|
|
49
|
+
|
|
50
|
+
# Convert and return.
|
|
51
|
+
return um_to_mm(array_to_vector4(manipulator_position))
|
|
52
|
+
|
|
53
|
+
# noinspection PyTypeChecker
|
|
54
|
+
async def get_angles(self, _: str) -> Vector3:
|
|
55
|
+
"""uMp-3 does not support getting angles so raise an error.
|
|
56
|
+
|
|
57
|
+
:raises: AttributeError
|
|
58
|
+
"""
|
|
59
|
+
error_message = "UMP-3 does not support getting angles"
|
|
60
|
+
raise AttributeError(error_message)
|
|
61
|
+
|
|
62
|
+
# noinspection PyTypeChecker
|
|
63
|
+
async def get_shank_count(self, _: str) -> int:
|
|
64
|
+
"""uMp-3 does not support getting shank count so raise an error.
|
|
65
|
+
|
|
66
|
+
:raises: AttributeError
|
|
67
|
+
"""
|
|
68
|
+
error_message = "UMP-3 does not support getting shank count"
|
|
69
|
+
raise AttributeError(error_message)
|
|
70
|
+
|
|
71
|
+
def get_movement_tolerance(self) -> float:
|
|
72
|
+
return 0.001
|
|
73
|
+
|
|
74
|
+
# noinspection DuplicatedCode
|
|
75
|
+
async def set_position(self, manipulator_id: str, position: Vector4, speed: float) -> Vector4:
|
|
76
|
+
# Convert position to micrometers.
|
|
77
|
+
target_position_um = vector_mm_to_um(position)
|
|
78
|
+
|
|
79
|
+
# Request movement.
|
|
80
|
+
movement = self._get_device(manipulator_id).goto_pos(
|
|
81
|
+
vector4_to_array(target_position_um), scalar_mm_to_um(speed)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Wait for movement to complete.
|
|
85
|
+
await get_running_loop().run_in_executor(None, movement.finished_event.wait, None)
|
|
86
|
+
|
|
87
|
+
# Handle interrupted movement.
|
|
88
|
+
if movement.interrupted:
|
|
89
|
+
error_message = f"Manipulator {manipulator_id} interrupted: {movement.interrupt_reason}"
|
|
90
|
+
raise RuntimeError(error_message)
|
|
91
|
+
|
|
92
|
+
return um_to_mm(array_to_vector4(movement.last_pos))
|
|
93
|
+
|
|
94
|
+
async def set_depth(self, manipulator_id: str, depth: float, speed: float) -> float:
|
|
95
|
+
# Augment current position with depth.
|
|
96
|
+
current_position = await self.get_position(manipulator_id)
|
|
97
|
+
new_platform_position = current_position.model_copy(update={"x": depth})
|
|
98
|
+
|
|
99
|
+
# Make the movement.
|
|
100
|
+
final_platform_position = await self.set_position(manipulator_id, new_platform_position, speed)
|
|
101
|
+
|
|
102
|
+
# Return the final depth.
|
|
103
|
+
return float(final_platform_position.w)
|
|
104
|
+
|
|
105
|
+
async def stop(self, manipulator_id: str) -> None:
|
|
106
|
+
self._get_device(manipulator_id).stop()
|
|
107
|
+
|
|
108
|
+
def platform_space_to_unified_space(self, platform_space: Vector4) -> Vector4:
|
|
109
|
+
# unified <- platform
|
|
110
|
+
# +x <- +y
|
|
111
|
+
# +y <- -x
|
|
112
|
+
# +z <- -z
|
|
113
|
+
# +d <- +d/x
|
|
114
|
+
|
|
115
|
+
return Vector4(
|
|
116
|
+
x=platform_space.y,
|
|
117
|
+
y=self.get_dimensions().x - platform_space.x,
|
|
118
|
+
z=self.get_dimensions().z - platform_space.z,
|
|
119
|
+
w=platform_space.w,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def unified_space_to_platform_space(self, unified_space: Vector4) -> Vector4:
|
|
123
|
+
# platform <- unified
|
|
124
|
+
# +x <- -y
|
|
125
|
+
# +y <- +x
|
|
126
|
+
# +z <- -z
|
|
127
|
+
# +d/x <- +d
|
|
128
|
+
|
|
129
|
+
return Vector4(
|
|
130
|
+
x=self.get_dimensions().y - unified_space.y,
|
|
131
|
+
y=unified_space.x,
|
|
132
|
+
z=self.get_dimensions().z - unified_space.z,
|
|
133
|
+
w=unified_space.w,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Helper methods.
|
|
137
|
+
def _get_device(self, manipulator_id: str) -> SensapexDevice:
|
|
138
|
+
return self._ump.get_device(int(manipulator_id))
|
|
@@ -65,6 +65,7 @@ class Ump4Bindings(BaseBindings):
|
|
|
65
65
|
def get_movement_tolerance(self) -> float:
|
|
66
66
|
return 0.001
|
|
67
67
|
|
|
68
|
+
# noinspection DuplicatedCode
|
|
68
69
|
async def set_position(self, manipulator_id: str, position: Vector4, speed: float) -> Vector4:
|
|
69
70
|
# Convert position to micrometers.
|
|
70
71
|
target_position_um = vector_mm_to_um(position)
|
|
@@ -75,7 +76,7 @@ class Ump4Bindings(BaseBindings):
|
|
|
75
76
|
)
|
|
76
77
|
|
|
77
78
|
# Wait for movement to finish.
|
|
78
|
-
await get_running_loop().run_in_executor(None, movement.finished_event.wait)
|
|
79
|
+
await get_running_loop().run_in_executor(None, movement.finished_event.wait, None)
|
|
79
80
|
|
|
80
81
|
# Handle interrupted movement.
|
|
81
82
|
if movement.interrupted:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: ephys-link
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0b6
|
|
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
|
|
@@ -26,14 +26,14 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
|
26
26
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
27
27
|
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
28
28
|
Requires-Python: <3.13,>=3.10
|
|
29
|
-
Requires-Dist: aiohttp==3.
|
|
29
|
+
Requires-Dist: aiohttp==3.10.8
|
|
30
30
|
Requires-Dist: colorama==0.4.6
|
|
31
|
-
Requires-Dist: platformdirs==4.
|
|
31
|
+
Requires-Dist: platformdirs==4.3.6
|
|
32
32
|
Requires-Dist: pyserial==3.5
|
|
33
|
-
Requires-Dist: python-socketio[asyncio-client]==5.11.
|
|
34
|
-
Requires-Dist: pythonnet==3.0.
|
|
33
|
+
Requires-Dist: python-socketio[asyncio-client]==5.11.4
|
|
34
|
+
Requires-Dist: pythonnet==3.0.4
|
|
35
35
|
Requires-Dist: requests==2.32.3
|
|
36
|
-
Requires-Dist: rich==13.
|
|
36
|
+
Requires-Dist: rich==13.8.1
|
|
37
37
|
Requires-Dist: sensapex==1.400.1
|
|
38
38
|
Requires-Dist: vbl-aquarium==0.0.22
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
ephys_link/__about__.py,sha256=
|
|
1
|
+
ephys_link/__about__.py,sha256=Fnner2tzgTA5rbbqCtZskrgDUNN_ccYtXn_spXP2SvY,25
|
|
2
2
|
ephys_link/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
ephys_link/__main__.py,sha256=KSwJO4gPQAZitNSNChD1NjkO2j3pc8RA2dkRbiaq32w,1368
|
|
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=zPcQux1yEaXEbwkWt2TcnrmpZ0NNXHWXwQhP0_wABwA,13295
|
|
6
6
|
ephys_link/back_end/server.py,sha256=QZu8deE57BxULfUutPQ41q3HTmr94nPyzaIT-JoOK2I,8026
|
|
7
7
|
ephys_link/bindings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
ephys_link/bindings/fake_bindings.py,sha256=Dk9Bpv4XTYa6ooh5Ge_5-o3IRzdazj5pYMcp3O41UJI,1936
|
|
9
9
|
ephys_link/bindings/mpm_bindings.py,sha256=e4whLU7mJm-Mbw6O2ua6fPHBZNnCiyyGiePEVBr3zzY,9818
|
|
10
|
-
ephys_link/bindings/
|
|
10
|
+
ephys_link/bindings/ump_3_bindings.py,sha256=7u97x3yAYdX-1PgLlO-psJvsOfCxJMCLt9-Jpc1u1a8,4799
|
|
11
|
+
ephys_link/bindings/ump_4_bindings.py,sha256=SaDToxDLKK3LBfufgukjl5WxV4JHhTF9oO4vg7l93To,4535
|
|
11
12
|
ephys_link/front_end/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
13
|
ephys_link/front_end/cli.py,sha256=KJBSWqdz4T5z0Zor1tJSHTJKZeMcHAJf5gXXu38wQPU,3105
|
|
13
14
|
ephys_link/front_end/gui.py,sha256=_gE6zFhFnzHPSyYd9MBvfK5xmDZHsUXcETDHzH66QzU,7518
|
|
@@ -19,8 +20,8 @@ ephys_link/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
19
20
|
ephys_link/util/base_bindings.py,sha256=ukomZftpwa8s_SV-jcwPSXmox0neXf8-uyie_NmiRro,5349
|
|
20
21
|
ephys_link/util/common.py,sha256=Pk7uVqEMFMPKNpeucWda_GfnHogRszej5G5qYkt43d8,3455
|
|
21
22
|
ephys_link/util/console.py,sha256=NvUH-Fp4nzkgrqQOcylctf46x4AW-qAphrtisepk1xY,4325
|
|
22
|
-
ephys_link-2.0.
|
|
23
|
-
ephys_link-2.0.
|
|
24
|
-
ephys_link-2.0.
|
|
25
|
-
ephys_link-2.0.
|
|
26
|
-
ephys_link-2.0.
|
|
23
|
+
ephys_link-2.0.0b6.dist-info/METADATA,sha256=MFmvagTs4lKlDgJ3rveMkpQ4qQJsGr6ik-PJejh0Ay4,7944
|
|
24
|
+
ephys_link-2.0.0b6.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
25
|
+
ephys_link-2.0.0b6.dist-info/entry_points.txt,sha256=o8wV3AdnJ9o47vg9ymKxPNVq9pMdPq8UZHE_iyAJx-k,124
|
|
26
|
+
ephys_link-2.0.0b6.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
27
|
+
ephys_link-2.0.0b6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|