ephys-link 2.0.0__py3-none-any.whl → 2.0.0b1__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.
@@ -0,0 +1,127 @@
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
+
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 RESOURCES_PATH, array_to_vector4, mm_to_um, mmps_to_umps, um_to_mm, vector4_to_array
13
+ from ephys_link.util.console import Console
14
+
15
+
16
+ class Ump4Bindings(BaseBindings):
17
+ """Bindings for UMP-4 platform"""
18
+
19
+ def __init__(self) -> None:
20
+ """Initialize UMP-4 bindings."""
21
+
22
+ # Establish connection to Sensapex API (exit if connection fails).
23
+ UMP.set_library_path(RESOURCES_PATH)
24
+ self._ump = UMP.get_ump()
25
+ if self._ump is None:
26
+ error_message = "Unable to connect to uMp"
27
+ Console.error_print(error_message)
28
+ raise ValueError(error_message)
29
+
30
+ async def get_manipulators(self) -> list[str]:
31
+ return list(map(str, self._ump.list_devices()))
32
+
33
+ async def get_num_axes(self) -> int:
34
+ return 4
35
+
36
+ def get_dimensions(self) -> Vector4:
37
+ return Vector4(x=20, y=20, z=20, w=20)
38
+
39
+ async def get_position(self, manipulator_id: str) -> Vector4:
40
+ return um_to_mm(array_to_vector4(self._get_device(manipulator_id).get_pos(1)))
41
+
42
+ # noinspection PyTypeChecker
43
+ async def get_angles(self, _: str) -> Vector3:
44
+ """uMp-4 does not support getting angles so raise an error.
45
+
46
+ :raises: AttributeError
47
+ """
48
+ error_message = "UMP-4 does not support getting angles"
49
+ raise AttributeError(error_message)
50
+
51
+ # noinspection PyTypeChecker
52
+ async def get_shank_count(self, _: str) -> int:
53
+ """uMp-4 does not support getting shank count so raise an error.
54
+
55
+ :raises: AttributeError
56
+ """
57
+ error_message = "UMP-4 does not support getting shank count"
58
+ raise AttributeError(error_message)
59
+
60
+ async def get_movement_tolerance(self) -> float:
61
+ return 0.001
62
+
63
+ async def set_position(self, manipulator_id: str, position: Vector4, speed: float) -> Vector4:
64
+ """Set the position of the manipulator.
65
+
66
+ Waits using Asyncio until the movement is finished. This assumes the application is running in an event loop.
67
+
68
+ :param manipulator_id: Manipulator ID.
69
+ :type manipulator_id: str
70
+ :param position: Platform space position to set the manipulator to (mm).
71
+ :type position: Vector4
72
+ :param speed: Speed to move the manipulator to the position (mm/s).
73
+ :type speed: float
74
+ :returns: Final position of the manipulator in platform space (mm).
75
+ :rtype: Vector4
76
+ :raises RuntimeError: If the movement is interrupted.
77
+ """
78
+ # Convert position to micrometers.
79
+ target_position_um = mm_to_um(position)
80
+
81
+ # Request movement.
82
+ movement = self._get_device(manipulator_id).goto_pos(vector4_to_array(target_position_um), mmps_to_umps(speed))
83
+
84
+ # Wait for movement to finish.
85
+ await get_running_loop().run_in_executor(None, movement.finished_event.wait)
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 stop(self, manipulator_id: str) -> None:
95
+ self._get_device(manipulator_id).stop()
96
+
97
+ def platform_space_to_unified_space(self, platform_space: Vector4) -> Vector4:
98
+ # unified <- platform
99
+ # +x <- +y
100
+ # +y <- -z
101
+ # +z <- +x
102
+ # +d <- +d
103
+
104
+ return Vector4(
105
+ x=platform_space.y,
106
+ y=self.get_dimensions().z - platform_space.z,
107
+ z=platform_space.x,
108
+ w=platform_space.w,
109
+ )
110
+
111
+ def unified_space_to_platform_space(self, unified_space: Vector4) -> Vector4:
112
+ # platform <- unified
113
+ # +x <- +z
114
+ # +y <- +x
115
+ # +z <- -y
116
+ # +d <- +d
117
+
118
+ return Vector4(
119
+ x=unified_space.z,
120
+ y=unified_space.x,
121
+ z=self.get_dimensions().z - unified_space.y,
122
+ w=unified_space.w,
123
+ )
124
+
125
+ # Helper methods.
126
+ def _get_device(self, manipulator_id: str) -> SensapexDevice:
127
+ return self._ump.get_device(int(manipulator_id))
@@ -1,104 +1,98 @@
1
- """Command-line interface for the Electrophysiology Manipulator Link.
2
-
3
- Usage:
4
- Instantiate CLI and call `parse_args()` to get the parsed arguments.
5
-
6
- ```python
7
- CLI().parse_args()
8
- ```
9
- """
10
-
11
- from argparse import ArgumentParser
12
- from typing import final
13
-
14
- from vbl_aquarium.models.ephys_link import EphysLinkOptions
15
-
16
- from ephys_link.__about__ import __version__ as version
17
-
18
-
19
- @final
20
- class CLI:
21
- """Command-line interface for the Electrophysiology Manipulator Link.
22
-
23
- Configures the CLI parser and options.
24
- """
25
-
26
- def __init__(self) -> None:
27
- """Initialize CLI parser."""
28
-
29
- self._parser = ArgumentParser(
30
- description="Electrophysiology Manipulator Link: a Socket.IO interface for manipulators in electrophysiology experiments.",
31
- prog="python -m ephys-link",
32
- )
33
-
34
- _ = self._parser.add_argument(
35
- "-b", "--background", dest="background", action="store_true", help="Skip configuration window."
36
- )
37
- _ = self._parser.add_argument(
38
- "-i",
39
- "--ignore-updates",
40
- dest="ignore_updates",
41
- action="store_true",
42
- help="Skip (ignore) checking for updates.",
43
- )
44
- _ = self._parser.add_argument(
45
- "-t",
46
- "--type",
47
- type=str,
48
- dest="type",
49
- default="ump-4",
50
- help='Manipulator type (i.e. "ump-4", "pathfinder-mpm", "fake"). Default: "ump-4".',
51
- )
52
- _ = self._parser.add_argument(
53
- "-d",
54
- "--debug",
55
- dest="debug",
56
- action="store_true",
57
- help="Enable debug mode.",
58
- )
59
- _ = self._parser.add_argument(
60
- "-p",
61
- "--use-proxy",
62
- dest="use_proxy",
63
- action="store_true",
64
- help="Enable proxy mode.",
65
- )
66
- _ = self._parser.add_argument(
67
- "-a",
68
- "--proxy-address",
69
- type=str,
70
- default="proxy2.virtualbrainlab.org",
71
- dest="proxy_address",
72
- help="Proxy IP address.",
73
- )
74
- _ = self._parser.add_argument(
75
- "--mpm-port",
76
- type=int,
77
- default=8080,
78
- dest="mpm_port",
79
- help="Port New Scale Pathfinder MPM's server is on. Default: 8080.",
80
- )
81
- _ = self._parser.add_argument(
82
- "-s",
83
- "--serial",
84
- type=str,
85
- default="no-e-stop",
86
- dest="serial",
87
- nargs="?",
88
- help="Emergency stop serial port (i.e. COM3). Default: disables emergency stop.",
89
- )
90
- _ = self._parser.add_argument(
91
- "-v",
92
- "--version",
93
- action="version",
94
- version=f"Electrophysiology Manipulator Link v{version}",
95
- help="Print version and exit.",
96
- )
97
-
98
- def parse_args(self) -> EphysLinkOptions:
99
- """Parse arguments and return them
100
-
101
- Returns:
102
- Parsed arguments
103
- """
104
- return EphysLinkOptions(**vars(self._parser.parse_args())) # pyright: ignore [reportAny]
1
+ """Command-line interface for the Electrophysiology Manipulator Link.
2
+
3
+ Usage: instantiate CLI and call parse_args() to get the parsed arguments.
4
+ """
5
+
6
+ from argparse import ArgumentParser
7
+
8
+ from vbl_aquarium.models.ephys_link import EphysLinkOptions
9
+
10
+ from ephys_link.__about__ import __version__ as version
11
+
12
+
13
+ class CLI:
14
+ """Command-line interface for the Electrophysiology Manipulator Link.
15
+
16
+ Configures the CLI parser and options.
17
+ """
18
+
19
+ def __init__(self) -> None:
20
+ """Initialize CLI parser."""
21
+
22
+ self._parser = ArgumentParser(
23
+ description="Electrophysiology Manipulator Link:"
24
+ " a Socket.IO interface for manipulators in electrophysiology experiments.",
25
+ prog="python -m ephys-link",
26
+ )
27
+
28
+ self._parser.add_argument(
29
+ "-b", "--background", dest="background", action="store_true", help="Skip configuration window."
30
+ )
31
+ self._parser.add_argument(
32
+ "-i",
33
+ "--ignore-updates",
34
+ dest="ignore_updates",
35
+ action="store_true",
36
+ help="Skip (ignore) checking for updates.",
37
+ )
38
+ self._parser.add_argument(
39
+ "-t",
40
+ "--type",
41
+ type=str,
42
+ dest="type",
43
+ default="ump-4",
44
+ help='Manipulator type (i.e. "ump-4", "ump-3", "pathfinder-mpm", "new-scale", "fake"). Default: "ump-4".',
45
+ )
46
+ self._parser.add_argument(
47
+ "-d",
48
+ "--debug",
49
+ dest="debug",
50
+ action="store_true",
51
+ help="Enable debug mode.",
52
+ )
53
+ self._parser.add_argument(
54
+ "-p",
55
+ "--use-proxy",
56
+ dest="use_proxy",
57
+ action="store_true",
58
+ help="Enable proxy mode.",
59
+ )
60
+ self._parser.add_argument(
61
+ "-a",
62
+ "--proxy-address",
63
+ type=str,
64
+ default="proxy2.virtualbrainlab.org",
65
+ dest="proxy_address",
66
+ help="Proxy IP address.",
67
+ )
68
+ self._parser.add_argument(
69
+ "--mpm-port",
70
+ type=int,
71
+ default=8080,
72
+ dest="mpm_port",
73
+ help="Port New Scale Pathfinder MPM's server is on. Default: 8080.",
74
+ )
75
+ self._parser.add_argument(
76
+ "-s",
77
+ "--serial",
78
+ type=str,
79
+ default="no-e-stop",
80
+ dest="serial",
81
+ nargs="?",
82
+ help="Emergency stop serial port (i.e. COM3). Default: disables emergency stop.",
83
+ )
84
+ self._parser.add_argument(
85
+ "-v",
86
+ "--version",
87
+ action="version",
88
+ version=f"Electrophysiology Manipulator Link v{version}",
89
+ help="Print version and exit.",
90
+ )
91
+
92
+ def parse_args(self) -> EphysLinkOptions:
93
+ """Parse arguments and return them
94
+
95
+ :returns: Parsed arguments
96
+ :rtype: EphysLinkOptions
97
+ """
98
+ return EphysLinkOptions(**vars(self._parser.parse_args()))