ephys-link 2.0.0b5__py3-none-any.whl → 2.0.0b9__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.
@@ -1,131 +1,157 @@
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 (
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 Ump4Bindings(BaseBindings):
23
- """Bindings for UMP-4 platform"""
24
-
25
- def __init__(self) -> None:
26
- """Initialize UMP-4 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 4
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
- return um_to_mm(array_to_vector4(self._get_device(manipulator_id).get_pos(1)))
46
-
47
- # noinspection PyTypeChecker
48
- async def get_angles(self, _: str) -> Vector3:
49
- """uMp-4 does not support getting angles so raise an error.
50
-
51
- :raises: AttributeError
52
- """
53
- error_message = "UMP-4 does not support getting angles"
54
- raise AttributeError(error_message)
55
-
56
- # noinspection PyTypeChecker
57
- async def get_shank_count(self, _: str) -> int:
58
- """uMp-4 does not support getting shank count so raise an error.
59
-
60
- :raises: AttributeError
61
- """
62
- error_message = "UMP-4 does not support getting shank count"
63
- raise AttributeError(error_message)
64
-
65
- def get_movement_tolerance(self) -> float:
66
- return 0.001
67
-
68
- async def set_position(self, manipulator_id: str, position: Vector4, speed: float) -> Vector4:
69
- # Convert position to micrometers.
70
- target_position_um = vector_mm_to_um(position)
71
-
72
- # Request movement.
73
- movement = self._get_device(manipulator_id).goto_pos(
74
- vector4_to_array(target_position_um), scalar_mm_to_um(speed)
75
- )
76
-
77
- # Wait for movement to finish.
78
- await get_running_loop().run_in_executor(None, movement.finished_event.wait)
79
-
80
- # Handle interrupted movement.
81
- if movement.interrupted:
82
- error_message = f"Manipulator {manipulator_id} interrupted: {movement.interrupt_reason}"
83
- raise RuntimeError(error_message)
84
-
85
- return um_to_mm(array_to_vector4(movement.last_pos))
86
-
87
- async def set_depth(self, manipulator_id: str, depth: float, speed: float) -> float:
88
- # Augment current position with depth.
89
- current_position = await self.get_position(manipulator_id)
90
- new_platform_position = current_position.model_copy(update={"w": depth})
91
-
92
- # Make the movement.
93
- final_platform_position = await self.set_position(manipulator_id, new_platform_position, speed)
94
-
95
- # Return the final depth.
96
- return float(final_platform_position.w)
97
-
98
- async def stop(self, manipulator_id: str) -> None:
99
- self._get_device(manipulator_id).stop()
100
-
101
- def platform_space_to_unified_space(self, platform_space: Vector4) -> Vector4:
102
- # unified <- platform
103
- # +x <- +y
104
- # +y <- -z
105
- # +z <- +x
106
- # +d <- +d
107
-
108
- return Vector4(
109
- x=platform_space.y,
110
- y=self.get_dimensions().z - platform_space.z,
111
- z=platform_space.x,
112
- w=platform_space.w,
113
- )
114
-
115
- def unified_space_to_platform_space(self, unified_space: Vector4) -> Vector4:
116
- # platform <- unified
117
- # +x <- +z
118
- # +y <- +x
119
- # +z <- -y
120
- # +d <- +d
121
-
122
- return Vector4(
123
- x=unified_space.z,
124
- y=unified_space.x,
125
- z=self.get_dimensions().z - unified_space.y,
126
- w=unified_space.w,
127
- )
128
-
129
- # Helper methods.
130
- def _get_device(self, manipulator_id: str) -> SensapexDevice:
131
- return self._ump.get_device(int(manipulator_id))
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 not movement.last_pos: # pyright: ignore [reportUnknownMemberType]
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]
@@ -1,98 +1,104 @@
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()))
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]