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 CHANGED
@@ -1 +1 @@
1
- __version__ = "2.0.0b5"
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.0b5
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.9.5
29
+ Requires-Dist: aiohttp==3.10.8
30
30
  Requires-Dist: colorama==0.4.6
31
- Requires-Dist: platformdirs==4.2.2
31
+ Requires-Dist: platformdirs==4.3.6
32
32
  Requires-Dist: pyserial==3.5
33
- Requires-Dist: python-socketio[asyncio-client]==5.11.3
34
- Requires-Dist: pythonnet==3.0.3
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.7.1
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=W8_pwZswCUyYGNDMBbTV2lPT7qpoSD2adR4yUwvZfkU,25
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=cAHZ3gqA-Y33vb0sfsH085BPVtIfSUGaFVqvTzpA2MA,13168
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/ump_4_bindings.py,sha256=rmkniiH5KyWjyXGpmUW3RCR_rar40b3vxHwYakrwbqg,4494
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.0b5.dist-info/METADATA,sha256=PvHqwUICg5mNPrvMvmGjpwcDQqQEtDzDacv4AuIsj88,7943
23
- ephys_link-2.0.0b5.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
24
- ephys_link-2.0.0b5.dist-info/entry_points.txt,sha256=o8wV3AdnJ9o47vg9ymKxPNVq9pMdPq8UZHE_iyAJx-k,124
25
- ephys_link-2.0.0b5.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
26
- ephys_link-2.0.0b5.dist-info/RECORD,,
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,,