ephys-link 2.0.0b6__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,148 +1,176 @@
1
- """Binding methods for Ephys Link manipulator platforms.
2
-
3
- Definition of the methods a platform binding class must implement to be used by Ephys Link.
4
-
5
- Usage: Implement the BaseBindings class when defining a platform binding to ensure it supports the necessary methods.
6
- """
7
-
8
- from abc import ABC, abstractmethod
9
-
10
- from vbl_aquarium.models.unity import Vector3, Vector4
11
-
12
-
13
- class BaseBindings(ABC):
14
- """Base class to enforce bindings manipulator platforms will support.
15
-
16
- No need to catch exceptions as the Platform Handler will catch them.
17
- """
18
-
19
- @abstractmethod
20
- async def get_manipulators(self) -> list[str]:
21
- """Get a list of available manipulators on the current platform.
22
-
23
- :returns: List of manipulator IDs.
24
- :rtype: list[str]
25
- """
26
-
27
- @abstractmethod
28
- async def get_axes_count(self) -> int:
29
- """Get the number of axes for the current platform.
30
-
31
- :returns: Number of axes.
32
- :rtype: int
33
- """
34
-
35
- @abstractmethod
36
- def get_dimensions(self) -> Vector4:
37
- """Get the dimensions of the manipulators on the current platform (mm).
38
-
39
- For 3-axis manipulators, copy the dimension of the axis parallel to the probe into w.
40
-
41
- :returns: Dimensions of the manipulators.
42
- :rtype: Vector4
43
- """
44
-
45
- @abstractmethod
46
- async def get_position(self, manipulator_id: str) -> Vector4:
47
- """Get the current position of a manipulator.
48
-
49
- These will be the translation values of the manipulator (mm), so they may need to be rotated to unified space.
50
- For 3-axis manipulators, copy the position of the axis parallel to the probe into w.
51
-
52
- :param manipulator_id: Manipulator ID.
53
- :type manipulator_id: str
54
- :returns: Current position of the manipulator in platform space (mm).
55
- :rtype: Vector4
56
- """
57
-
58
- @abstractmethod
59
- async def get_angles(self, manipulator_id: str) -> Vector3:
60
- """Get the current rotation angles of a manipulator in Yaw, Pitch, Roll (degrees).
61
-
62
- :param manipulator_id: Manipulator ID.
63
- :type manipulator_id: str
64
- :returns: Current angles of the manipulator.
65
- :rtype: Vector3
66
- """
67
-
68
- @abstractmethod
69
- async def get_shank_count(self, manipulator_id: str) -> int:
70
- """Get the number of shanks on a manipulator.
71
-
72
- :param manipulator_id: Manipulator ID.
73
- :type manipulator_id: str
74
- :returns: Number of shanks on the manipulator.
75
- :rtype: int
76
- """
77
-
78
- @abstractmethod
79
- def get_movement_tolerance(self) -> float:
80
- """Get the tolerance for how close the final position must be to the target position in a movement (mm).
81
-
82
- :returns: Movement tolerance (mm).
83
- :rtype: float
84
- """
85
-
86
- @abstractmethod
87
- async def set_position(self, manipulator_id: str, position: Vector4, speed: float) -> Vector4:
88
- """Set the position of a manipulator.
89
-
90
- This will directly set the position in the original platform space.
91
- For 3-axis manipulators, the first 3 values of the position will be used.
92
-
93
- :param manipulator_id: Manipulator ID.
94
- :type manipulator_id: str
95
- :param position: Platform space position to set the manipulator to (mm).
96
- :type position: Vector4
97
- :param speed: Speed to move the manipulator to the position (mm/s).
98
- :type speed: float
99
- :returns: Final position of the manipulator in platform space (mm).
100
- :rtype: Vector4
101
- """
102
-
103
- @abstractmethod
104
- async def set_depth(self, manipulator_id: str, depth: float, speed: float) -> float:
105
- """Set the depth of a manipulator.
106
-
107
- This will directly set the depth stage in the original platform space.
108
-
109
- :param manipulator_id: Manipulator ID.
110
- :type manipulator_id: str
111
- :param depth: Depth to set the manipulator to (mm).
112
- :type depth: float
113
- :param speed: Speed to move the manipulator to the depth (mm/s).
114
- :type speed: float
115
- :returns: Final depth of the manipulator in platform space (mm).
116
- :rtype: float
117
- """
118
-
119
- @abstractmethod
120
- async def stop(self, manipulator_id: str) -> None:
121
- """Stop a manipulator."""
122
-
123
- @abstractmethod
124
- def platform_space_to_unified_space(self, platform_space: Vector4) -> Vector4:
125
- """Convert platform space coordinates to unified space coordinates.
126
-
127
- This is an axes-swapping transformation.
128
-
129
- Unified coordinate space is the standard left-handed cartesian coordinate system
130
- with an additional depth axis pointing from the base of the probe to the tip.
131
-
132
- :param platform_space: Platform space coordinates.
133
- :type platform_space: Vector4
134
- :returns: Unified space coordinates.
135
- :rtype: Vector4
136
- """
137
-
138
- @abstractmethod
139
- def unified_space_to_platform_space(self, unified_space: Vector4) -> Vector4:
140
- """Convert unified space coordinates to platform space coordinates.
141
-
142
- This is an axes-swapping transformation.
143
-
144
- :param unified_space: Unified space coordinates.
145
- :type unified_space: Vector4
146
- :returns: Platform space coordinates.
147
- :rtype: Vector4
148
- """
1
+ """Binding methods for Ephys Link manipulator platforms.
2
+
3
+ Definition of the methods a platform binding class must implement to be used by Ephys Link.
4
+
5
+ Usage:
6
+ Implement the BaseBindings class when defining a platform binding to ensure it supports the necessary methods.
7
+ """
8
+
9
+ from abc import ABC, abstractmethod
10
+
11
+ from vbl_aquarium.models.unity import Vector3, Vector4
12
+
13
+
14
+ class BaseBinding(ABC):
15
+ """Base class to enforce bindings manipulator platforms will support.
16
+
17
+ No need to catch exceptions as the [Platform Handler][ephys_link.back_end.platform_handler] will catch them.
18
+ """
19
+
20
+ @staticmethod
21
+ @abstractmethod
22
+ def get_display_name() -> str:
23
+ """Get the full display name of the platform.
24
+
25
+ Returns:
26
+ Full display name of the platform.
27
+ """
28
+
29
+ @staticmethod
30
+ @abstractmethod
31
+ def get_cli_name() -> str:
32
+ """Get the name of the platform for CLI usage.
33
+
34
+ This is the value used to identify the platform when using the `-t` flag in the CLI.
35
+
36
+ Returns:
37
+ Name of the platform to use on the CLI.
38
+ """
39
+
40
+ @abstractmethod
41
+ async def get_axes_count(self) -> int:
42
+ """Get the number of axes for the current platform.
43
+
44
+ Returns:
45
+ Number of axes.
46
+ """
47
+
48
+ @abstractmethod
49
+ def get_dimensions(self) -> Vector4:
50
+ """Get the dimensions of the manipulators on the current platform (mm).
51
+
52
+ For 3-axis manipulators, copy the dimension of the axis parallel to the probe into w.
53
+
54
+ Returns:
55
+ Dimensions of the manipulators.
56
+ """
57
+
58
+ @abstractmethod
59
+ async def get_manipulators(self) -> list[str]:
60
+ """Get a list of available manipulators on the current platform.
61
+
62
+ Returns:
63
+ List of manipulator IDs.
64
+ """
65
+
66
+ @abstractmethod
67
+ async def get_position(self, manipulator_id: str) -> Vector4:
68
+ """Get the current position of a manipulator.
69
+
70
+ These will be the translation values of the manipulator (mm), so they may need to be rotated to unified space.
71
+ For 3-axis manipulators, copy the position of the axis parallel to the probe into w.
72
+
73
+ Args:
74
+ manipulator_id: Manipulator ID.
75
+
76
+ Returns:
77
+ Current position of the manipulator in platform space (mm).
78
+ """
79
+
80
+ @abstractmethod
81
+ async def get_angles(self, manipulator_id: str) -> Vector3:
82
+ """Get the current rotation angles of a manipulator in Yaw, Pitch, Roll (degrees).
83
+
84
+ Args:
85
+ manipulator_id: Manipulator ID.
86
+
87
+ Returns:
88
+ Current angles of the manipulator.
89
+ """
90
+
91
+ @abstractmethod
92
+ async def get_shank_count(self, manipulator_id: str) -> int:
93
+ """Get the number of shanks on a manipulator.
94
+
95
+ Args:
96
+ manipulator_id: Manipulator ID.
97
+
98
+ Returns:
99
+ Number of shanks on the manipulator.
100
+ """
101
+
102
+ @abstractmethod
103
+ def get_movement_tolerance(self) -> float:
104
+ """Get the tolerance for how close the final position must be to the target position in a movement (mm).
105
+
106
+ Returns:
107
+ Movement tolerance (mm).
108
+ """
109
+
110
+ @abstractmethod
111
+ async def set_position(self, manipulator_id: str, position: Vector4, speed: float) -> Vector4:
112
+ """Set the position of a manipulator.
113
+
114
+ This will directly set the position in the original platform space.
115
+ For 3-axis manipulators, the first 3 values of the position will be used.
116
+
117
+ Args:
118
+ manipulator_id: Manipulator ID.
119
+ position: Platform space position to set the manipulator to (mm).
120
+ speed: Speed to move the manipulator to the position (mm/s).
121
+
122
+ Returns:
123
+ Final position of the manipulator in platform space (mm).
124
+ """
125
+
126
+ @abstractmethod
127
+ async def set_depth(self, manipulator_id: str, depth: float, speed: float) -> float:
128
+ """Set the depth of a manipulator.
129
+
130
+ This will directly set the depth stage in the original platform space.
131
+
132
+ Args:
133
+ manipulator_id: Manipulator ID.
134
+ depth: Depth to set the manipulator to (mm).
135
+ speed: Speed to move the manipulator to the depth (mm/s).
136
+
137
+ Returns:
138
+ Final depth of the manipulator in platform space (mm).
139
+ """
140
+
141
+ @abstractmethod
142
+ async def stop(self, manipulator_id: str) -> None:
143
+ """Stop a manipulator.
144
+
145
+ Args:
146
+ manipulator_id: Manipulator ID.
147
+ """
148
+
149
+ @abstractmethod
150
+ def platform_space_to_unified_space(self, platform_space: Vector4) -> Vector4:
151
+ """Convert platform space coordinates to unified space coordinates.
152
+
153
+ This is an axes-swapping transformation.
154
+
155
+ Unified coordinate space is the standard left-handed cartesian coordinate system
156
+ with an additional depth axis pointing from the base of the probe to the tip.
157
+
158
+ Args:
159
+ platform_space: Platform space coordinates.
160
+
161
+ Returns:
162
+ Unified space coordinates.
163
+ """
164
+
165
+ @abstractmethod
166
+ def unified_space_to_platform_space(self, unified_space: Vector4) -> Vector4:
167
+ """Convert unified space coordinates to platform space coordinates.
168
+
169
+ This is an axes-swapping transformation.
170
+
171
+ Args:
172
+ unified_space: Unified space coordinates.
173
+
174
+ Returns:
175
+ Platform space coordinates.
176
+ """
@@ -1,130 +1,127 @@
1
- # ruff: noqa: T201
2
- """Console class for printing messages to the console.
3
-
4
- Configure the console to print error and debug messages.
5
-
6
- Usage: Create a Console object and call the appropriate method to print messages.
7
- """
8
-
9
- from logging import DEBUG, ERROR, INFO, basicConfig, getLogger
10
-
11
- from rich.logging import RichHandler
12
- from rich.traceback import install
13
-
14
-
15
- class Console:
16
- def __init__(self, *, enable_debug: bool) -> None:
17
- """Initialize console properties.
18
-
19
- :param enable_debug: Enable debug mode.
20
- :type enable_debug: bool
21
- """
22
- # Repeat message fields.
23
- self._last_message = (0, "", "")
24
- self._repeat_counter = 0
25
-
26
- # Config logger.
27
- basicConfig(
28
- format="%(message)s",
29
- datefmt="[%I:%M:%S %p]",
30
- handlers=[RichHandler(rich_tracebacks=True, markup=True)],
31
- )
32
- self._log = getLogger("rich")
33
- self._log.setLevel(DEBUG if enable_debug else INFO)
34
-
35
- # Install Rich traceback.
36
- install()
37
-
38
- def debug_print(self, label: str, msg: str) -> None:
39
- """Print a debug message to the console.
40
-
41
- :param label: Label for the debug message.
42
- :type label: str
43
- :param msg: Debug message to print.
44
- :type msg: str
45
- """
46
- self._repeatable_log(DEBUG, f"[b green]{label}", f"[green]{msg}")
47
-
48
- def info_print(self, label: str, msg: str) -> None:
49
- """Print info to console.
50
-
51
- :param label: Label for the message.
52
- :type label: str
53
- :param msg: Message to print.
54
- :type msg: str
55
- """
56
- self._repeatable_log(INFO, f"[b blue]{label}", msg)
57
-
58
- def error_print(self, label: str, msg: str) -> None:
59
- """Print an error message to the console.
60
-
61
- :param label: Label for the error message.
62
- :type label: str
63
- :param msg: Error message to print.
64
- :type msg: str
65
- """
66
- self._repeatable_log(ERROR, f"[b red]{label}", f"[red]{msg}")
67
-
68
- def critical_print(self, msg: str) -> None:
69
- """Print a critical message to the console.
70
-
71
- :param msg: Critical message to print.
72
- :type msg: str
73
- """
74
- self._log.critical(f"[b i red]{msg}")
75
-
76
- @staticmethod
77
- def pretty_exception(exception: Exception) -> str:
78
- """Pretty print an exception.
79
-
80
- :param exception: Exception to pretty print.
81
- :type exception: Exception
82
- :return: Pretty printed exception.
83
- :rtype: str
84
- """
85
- return f"{type(exception).__name__}: {exception}"
86
-
87
- def exception_error_print(self, label: str, exception: Exception) -> None:
88
- """Print an error message with exception details to the console.
89
-
90
- :param label: Label for the error message.
91
- :type label: str
92
- :param exception: Exception to print.
93
- :type exception: Exception
94
- """
95
- self._log.exception(f"[b magenta]{label}:[/] [magenta]{Console.pretty_exception(exception)}")
96
-
97
- # Helper methods.
98
- def _repeatable_log(self, log_type: int, label: str, message: str) -> None:
99
- """Add a row to the output table.
100
-
101
- :param log_type: Type of log.
102
- :type log_type: int
103
- :param label: Label for the message.
104
- :type label: str
105
- :param message: Message.
106
- :type message: str
107
- """
108
-
109
- # Compute if this is a repeated message.
110
- message_set = (log_type, label, message)
111
- if message_set == self._last_message:
112
- # Handle repeat.
113
- self._repeat_counter += 1
114
-
115
- # Add an ellipsis row for first repeat.
116
- if self._repeat_counter == 1:
117
- self._log.log(log_type, "...")
118
- else:
119
- # Handle novel message.
120
- if self._repeat_counter > 0:
121
- # Complete previous repeat.
122
- self._log.log(
123
- self._last_message[0],
124
- f"{self._last_message[1]}:[/] {self._last_message[2]}[/] x {self._repeat_counter}",
125
- )
126
- self._repeat_counter = 0
127
-
128
- # Log new message.
129
- self._log.log(log_type, f"{label}:[/] {message}")
130
- self._last_message = message_set
1
+ """Console class for printing messages to the console.
2
+
3
+ Configure the console to print error and debug messages.
4
+
5
+ Usage:
6
+ Create a Console object and call the appropriate method to print messages.
7
+ """
8
+
9
+ from logging import DEBUG, ERROR, INFO, basicConfig, getLogger
10
+ from typing import final
11
+
12
+ from rich.logging import RichHandler
13
+ from rich.traceback import install
14
+
15
+
16
+ @final
17
+ class Console:
18
+ def __init__(self, *, enable_debug: bool) -> None:
19
+ """Initialize console properties.
20
+
21
+ Args:
22
+ enable_debug: Enable debug mode.
23
+ """
24
+ # Repeat message fields.
25
+ self._last_message = (0, "", "")
26
+ self._repeat_counter = 0
27
+
28
+ # Config logger.
29
+ basicConfig(
30
+ format="%(message)s",
31
+ datefmt="[%I:%M:%S %p]",
32
+ handlers=[RichHandler(rich_tracebacks=True, markup=True)],
33
+ )
34
+ self._log = getLogger("rich")
35
+ self._log.setLevel(DEBUG if enable_debug else INFO)
36
+
37
+ # Install Rich traceback.
38
+ _ = install()
39
+
40
+ def debug_print(self, label: str, msg: str) -> None:
41
+ """Print a debug message to the console.
42
+
43
+ Args:
44
+ label: Label for the debug message.
45
+ msg: Debug message to print.
46
+ """
47
+ self._repeatable_log(DEBUG, f"[b green]{label}", f"[green]{msg}")
48
+
49
+ def info_print(self, label: str, msg: str) -> None:
50
+ """Print info to console.
51
+
52
+ Args:
53
+ label: Label for the message.
54
+ msg: Message to print.
55
+ """
56
+ self._repeatable_log(INFO, f"[b blue]{label}", msg)
57
+
58
+ def error_print(self, label: str, msg: str) -> None:
59
+ """Print an error message to the console.
60
+
61
+ Args:
62
+ label: Label for the error message.
63
+ msg: Error message to print.
64
+ """
65
+ self._repeatable_log(ERROR, f"[b red]{label}", f"[red]{msg}")
66
+
67
+ def critical_print(self, msg: str) -> None:
68
+ """Print a critical message to the console.
69
+
70
+ Args:
71
+ msg: Critical message to print.
72
+ """
73
+ self._log.critical(f"[b i red]{msg}")
74
+
75
+ @staticmethod
76
+ def pretty_exception(exception: Exception) -> str:
77
+ """Pretty print an exception.
78
+
79
+ Args:
80
+ exception: Exception to pretty print.
81
+
82
+ Returns:
83
+ Pretty printed exception.
84
+ """
85
+ return f"{type(exception).__name__}: {exception}"
86
+
87
+ def exception_error_print(self, label: str, exception: Exception) -> None:
88
+ """Print an error message with exception details to the console.
89
+
90
+ Args:
91
+ label: Label for the error message.
92
+ exception: Exception to print.
93
+ """
94
+ self._log.exception(f"[b magenta]{label}:[/] [magenta]{Console.pretty_exception(exception)}")
95
+
96
+ # Helper methods.
97
+ def _repeatable_log(self, log_type: int, label: str, message: str) -> None:
98
+ """Add a row to the output table.
99
+
100
+ Args:
101
+ log_type: Type of log.
102
+ label: Label for the message.
103
+ message: Message.
104
+ """
105
+
106
+ # Compute if this is a repeated message.
107
+ message_set = (log_type, label, message)
108
+ if message_set == self._last_message:
109
+ # Handle repeat.
110
+ self._repeat_counter += 1
111
+
112
+ # Add an ellipsis row for first repeat.
113
+ if self._repeat_counter == 1:
114
+ self._log.log(log_type, "...")
115
+ else:
116
+ # Handle novel message.
117
+ if self._repeat_counter > 0:
118
+ # Complete previous repeat.
119
+ self._log.log(
120
+ self._last_message[0],
121
+ f"{self._last_message[1]}:[/] {self._last_message[2]}[/] x {self._repeat_counter}",
122
+ )
123
+ self._repeat_counter = 0
124
+
125
+ # Log new message.
126
+ self._log.log(log_type, f"{label}:[/] {message}")
127
+ self._last_message = message_set
@@ -0,0 +1,23 @@
1
+ """Globally accessible constants"""
2
+
3
+ from os.path import abspath, dirname, join
4
+
5
+ # Ephys Link ASCII.
6
+ ASCII = r"""
7
+ ______ _ _ _ _
8
+ | ____| | | | | (_) | |
9
+ | |__ _ __ | |__ _ _ ___ | | _ _ __ | | __
10
+ | __| | '_ \| '_ \| | | / __| | | | | '_ \| |/ /
11
+ | |____| |_) | | | | |_| \__ \ | |____| | | | | <
12
+ |______| .__/|_| |_|\__, |___/ |______|_|_| |_|_|\_\
13
+ | | __/ |
14
+ |_| |___/
15
+ """
16
+
17
+ # Absolute path to the resource folder.
18
+ PACKAGE_DIRECTORY = dirname(dirname(abspath(__file__)))
19
+ RESOURCES_DIRECTORY = join(PACKAGE_DIRECTORY, "resources")
20
+ BINDINGS_DIRECTORY = join(PACKAGE_DIRECTORY, "bindings")
21
+
22
+ # Ephys Link Port
23
+ PORT = 3000