ephys-link 1.3.3__py3-none-any.whl → 2.0.0__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.
Files changed (42) hide show
  1. ephys_link/__about__.py +1 -1
  2. ephys_link/__main__.py +51 -105
  3. ephys_link/back_end/__init__.py +0 -0
  4. ephys_link/back_end/platform_handler.py +315 -0
  5. ephys_link/back_end/server.py +274 -0
  6. ephys_link/bindings/__init__.py +0 -0
  7. ephys_link/bindings/fake_binding.py +84 -0
  8. ephys_link/bindings/mpm_binding.py +315 -0
  9. ephys_link/bindings/ump_4_binding.py +157 -0
  10. ephys_link/front_end/__init__.py +0 -0
  11. ephys_link/front_end/cli.py +104 -0
  12. ephys_link/front_end/gui.py +204 -0
  13. ephys_link/utils/__init__.py +0 -0
  14. ephys_link/utils/base_binding.py +176 -0
  15. ephys_link/utils/console.py +127 -0
  16. ephys_link/utils/constants.py +23 -0
  17. ephys_link/utils/converters.py +86 -0
  18. ephys_link/utils/startup.py +65 -0
  19. ephys_link-2.0.0.dist-info/METADATA +91 -0
  20. ephys_link-2.0.0.dist-info/RECORD +25 -0
  21. {ephys_link-1.3.3.dist-info → ephys_link-2.0.0.dist-info}/WHEEL +1 -1
  22. {ephys_link-1.3.3.dist-info → ephys_link-2.0.0.dist-info}/licenses/LICENSE +674 -674
  23. ephys_link/common.py +0 -49
  24. ephys_link/emergency_stop.py +0 -67
  25. ephys_link/gui.py +0 -217
  26. ephys_link/platform_handler.py +0 -465
  27. ephys_link/platform_manipulator.py +0 -35
  28. ephys_link/platforms/__init__.py +0 -5
  29. ephys_link/platforms/new_scale_handler.py +0 -141
  30. ephys_link/platforms/new_scale_manipulator.py +0 -312
  31. ephys_link/platforms/new_scale_pathfinder_handler.py +0 -235
  32. ephys_link/platforms/sensapex_handler.py +0 -151
  33. ephys_link/platforms/sensapex_manipulator.py +0 -227
  34. ephys_link/platforms/ump3_handler.py +0 -57
  35. ephys_link/platforms/ump3_manipulator.py +0 -147
  36. ephys_link/resources/CP210xManufacturing.dll +0 -0
  37. ephys_link/resources/NstMotorCtrl.dll +0 -0
  38. ephys_link/resources/SiUSBXp.dll +0 -0
  39. ephys_link/server.py +0 -508
  40. ephys_link-1.3.3.dist-info/METADATA +0 -164
  41. ephys_link-1.3.3.dist-info/RECORD +0 -26
  42. {ephys_link-1.3.3.dist-info → ephys_link-2.0.0.dist-info}/entry_points.txt +0 -0
@@ -1,227 +0,0 @@
1
- """Sensapex Manipulator class
2
-
3
- Handles logic for calling Sensapex API functions. Also includes extra logic for safe
4
- function calls, error handling, managing per-manipulator attributes, and returning the
5
- appropriate callback parameters like in :mod:`ephys_link.sensapex_handler`.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import asyncio
11
- import threading
12
- from typing import TYPE_CHECKING
13
-
14
- from vbl_aquarium.models.ephys_link import (
15
- CanWriteRequest,
16
- DriveToDepthRequest,
17
- DriveToDepthResponse,
18
- GotoPositionRequest,
19
- PositionalResponse,
20
- )
21
- from vbl_aquarium.models.unity import Vector4
22
-
23
- from ephys_link.common import dprint, vector4_to_array
24
- from ephys_link.platform_manipulator import (
25
- HOURS_TO_SECONDS,
26
- MM_TO_UM,
27
- POSITION_POLL_DELAY,
28
- PlatformManipulator,
29
- )
30
-
31
- if TYPE_CHECKING:
32
- from sensapex import SensapexDevice
33
-
34
-
35
- class SensapexManipulator(PlatformManipulator):
36
- """Representation of a single Sensapex manipulator
37
-
38
- :param device: A Sensapex device
39
- :type device: :class: `sensapex.SensapexDevice`
40
- """
41
-
42
- def __init__(self, device: SensapexDevice) -> None:
43
- """Construct a new Manipulator object
44
-
45
- :param device: A Sensapex device
46
- """
47
- super().__init__()
48
- self._device = device
49
- self._id = device.dev_id
50
-
51
- # Device functions
52
- def get_pos(self) -> PositionalResponse:
53
- """Get the current position of the manipulator and convert it into mm.
54
-
55
- :return: Position in (x, y, z, w) (or an empty array on error) in mm and error message (if any).
56
- :rtype: :class:`ephys_link.common.PositionalOutputData`
57
- """
58
- try:
59
- # com.dprint(f"[SUCCESS]\t Got position of manipulator {self._id}\n")
60
- return PositionalResponse(
61
- position=Vector4(
62
- **dict(
63
- zip(
64
- Vector4.model_fields.keys(),
65
- [axis / MM_TO_UM for axis in self._device.get_pos(1)],
66
- strict=False,
67
- )
68
- )
69
- )
70
- )
71
- except Exception as e:
72
- print(f"[ERROR]\t\t Getting position of manipulator {self._id}")
73
- print(f"{e}\n")
74
- return PositionalResponse(error="Error getting position")
75
-
76
- async def goto_pos(self, request: GotoPositionRequest) -> PositionalResponse:
77
- """Move manipulator to position.
78
-
79
- :param request: The goto request parsed from the server.
80
- :type request: :class:`vbl_aquarium.models.ephys_link.GotoPositionRequest`
81
- :return: Resulting position in (x, y, z, w) (or an empty array on error) in mm and error message (if any).
82
- :rtype: :class:`ephys_link.common.PositionalOutputData`
83
- """
84
- # Check if able to write
85
- if not self._can_write:
86
- print(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
87
- return PositionalResponse(error="Manipulator movement canceled")
88
-
89
- # Stop current movement
90
- if self._is_moving:
91
- self._device.stop()
92
- self._is_moving = False
93
-
94
- try:
95
- target_position_um = request.position * MM_TO_UM
96
-
97
- # Restrict target position to just depth-axis if inside brain
98
- if self._inside_brain:
99
- d_axis = target_position_um.w
100
- target_position_um = target_position_um.model_copy(
101
- update={**self.get_pos().position.model_dump(), "w": d_axis}
102
- )
103
-
104
- # Mark movement as started
105
- self._is_moving = True
106
-
107
- # Send move command
108
- movement = self._device.goto_pos(
109
- vector4_to_array(target_position_um),
110
- request.speed * MM_TO_UM,
111
- )
112
-
113
- # Wait for movement to finish
114
- while not movement.finished:
115
- await asyncio.sleep(POSITION_POLL_DELAY)
116
-
117
- # Get position
118
- final_position = self.get_pos().position
119
-
120
- # Mark movement as finished.
121
- self._is_moving = False
122
-
123
- # Return success unless write was disabled during movement (meaning a stop occurred).
124
- if not self._can_write:
125
- dprint(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
126
- return PositionalResponse(error="Manipulator movement canceled")
127
-
128
- # Return error if movement did not reach target.
129
- if not all(
130
- abs(axis) < self._movement_tolerance for axis in vector4_to_array(final_position - request.position)
131
- ):
132
- dprint(f"[ERROR]\t\t Manipulator {self._id} did not reach target position")
133
- dprint(f"\t\t\t Expected: {request.position}, Got: {final_position}")
134
- return PositionalResponse(error="Manipulator did not reach target position")
135
-
136
- # Made it to the target.
137
- dprint(f"[SUCCESS]\t Moved manipulator {self._id} to position {final_position}\n")
138
- return PositionalResponse(position=final_position)
139
- except Exception as e:
140
- print(f"[ERROR]\t\t Moving manipulator {self._id} to position {request.position}")
141
- print(f"{e}\n")
142
- return PositionalResponse(error="Error moving manipulator")
143
-
144
- async def drive_to_depth(self, request: DriveToDepthRequest) -> DriveToDepthResponse:
145
- """Drive the manipulator to a certain depth.
146
-
147
- :param request: The drive to depth request parsed from the server.
148
- :type request: :class:`vbl_aquarium.models.ephys_link.DriveToDepthRequest`
149
- :return: Resulting depth in mm (or 0 on error) and error message (if any).
150
- :rtype: :class:`ephys_link.common.DriveToDepthOutputData`
151
- """
152
- # Get position before this movement
153
- target_pos = self.get_pos().position
154
-
155
- target_pos = target_pos.model_copy(update={"w": request.depth})
156
- movement_result = await self.goto_pos(GotoPositionRequest(**request.model_dump(), position=target_pos))
157
- return DriveToDepthResponse(depth=movement_result.position.w, error=movement_result.error)
158
-
159
- def set_inside_brain(self, inside: bool) -> None:
160
- """Set if the manipulator is inside the brain.
161
-
162
- Used to signal that the brain should move at :const:`INSIDE_BRAIN_SPEED_LIMIT`
163
-
164
- :param inside: True if the manipulator is inside the brain, False otherwise.
165
- :type inside: bool
166
- :return: None
167
- """
168
- self._inside_brain = inside
169
-
170
- def get_can_write(self) -> bool:
171
- """Return if the manipulator can move.
172
-
173
- :return: True if the manipulator can move, False otherwise.
174
- :rtype: bool
175
- """
176
- return self._can_write
177
-
178
- def set_can_write(self, request: CanWriteRequest) -> None:
179
- """Set if the manipulator can move.
180
-
181
- :param request: The can write request parsed from the server.
182
- :type request: :class:`vbl_aquarium.models.ephys_link.CanWriteRequest`
183
- :return: None
184
- """
185
- self._can_write = request.can_write
186
-
187
- if request.can_write and request.hours > 0:
188
- if self._reset_timer:
189
- self._reset_timer.cancel()
190
- self._reset_timer = threading.Timer(request.hours * HOURS_TO_SECONDS, self.reset_can_write)
191
- self._reset_timer.start()
192
-
193
- def reset_can_write(self) -> None:
194
- """Reset the :attr:`can_write` flag."""
195
- self._can_write = False
196
-
197
- # Calibration
198
- def call_calibrate(self) -> None:
199
- """Calibrate the manipulator.
200
-
201
- :return: None
202
- """
203
- self._device.calibrate_zero_position()
204
-
205
- def get_calibrated(self) -> bool:
206
- """Return the calibration state of the manipulator.
207
-
208
- :return: True if the manipulator is calibrated, False otherwise.
209
- :rtype: bool
210
- """
211
- return self._calibrated
212
-
213
- def set_calibrated(self) -> None:
214
- """Set the manipulator to be calibrated.
215
-
216
- :return: None
217
- """
218
- self._calibrated = True
219
-
220
- def stop(self) -> None:
221
- """Stop the manipulator
222
-
223
- :return: None
224
- """
225
- self._can_write = False
226
- self._device.stop()
227
- dprint(f"[SUCCESS]\t Stopped manipulator {self._id}")
@@ -1,57 +0,0 @@
1
- """Handle communications with Sensapex uMp-3 API
2
-
3
- Uses the Sensapex uMp handler which implements for the uMp-4 manipulator
4
- and extends it to support the uMp-3 manipulator.
5
-
6
- This is a subclass of :class:`ephys_link.platforms.sensapex_handler`.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from vbl_aquarium.models.unity import Vector4
12
-
13
- from ephys_link.platforms.sensapex_handler import SensapexHandler
14
- from ephys_link.platforms.ump3_manipulator import UMP3Manipulator
15
-
16
-
17
- class UMP3Handler(SensapexHandler):
18
- def __init__(self):
19
- super().__init__()
20
-
21
- self.num_axes = 3
22
- self.dimensions = Vector4(x=20, y=20, z=20, w=0)
23
-
24
- def _register_manipulator(self, manipulator_id: str) -> None:
25
- if not manipulator_id.isnumeric():
26
- msg = "Manipulator ID must be numeric"
27
- raise ValueError(msg)
28
-
29
- self.manipulators[manipulator_id] = UMP3Manipulator(self.ump.get_device(int(manipulator_id)))
30
-
31
- def _platform_space_to_unified_space(self, platform_position: Vector4) -> Vector4:
32
- # unified <- platform
33
- # +x <- +y
34
- # +y <- -x
35
- # +z <- -z
36
- # +d <- +d/x
37
-
38
- return Vector4(
39
- x=platform_position.y,
40
- y=self.dimensions.x - platform_position.x,
41
- z=self.dimensions.z - platform_position.z,
42
- w=platform_position.w,
43
- )
44
-
45
- def _unified_space_to_platform_space(self, unified_position: Vector4) -> Vector4:
46
- # platform <- unified
47
- # +x <- -y
48
- # +y <- +x
49
- # +z <- -z
50
- # +d/x <- +d
51
-
52
- return Vector4(
53
- x=self.dimensions.y - unified_position.y,
54
- y=unified_position.x,
55
- z=self.dimensions.z - unified_position.z,
56
- w=unified_position.w,
57
- )
@@ -1,147 +0,0 @@
1
- """Sensapex uMp-3 Manipulator class
2
-
3
- Extends from :class:`ephys_link.platforms.sensapex_manipulator.SensapexManipulator` to support the uMp-3 manipulator.
4
- """
5
-
6
- from __future__ import annotations
7
-
8
- import asyncio
9
- from typing import TYPE_CHECKING
10
-
11
- from vbl_aquarium.models.ephys_link import (
12
- DriveToDepthRequest,
13
- DriveToDepthResponse,
14
- GotoPositionRequest,
15
- PositionalResponse,
16
- )
17
- from vbl_aquarium.models.unity import Vector4
18
-
19
- from ephys_link.common import dprint, vector4_to_array
20
- from ephys_link.platform_manipulator import (
21
- MM_TO_UM,
22
- POSITION_POLL_DELAY,
23
- )
24
- from ephys_link.platforms.sensapex_manipulator import SensapexManipulator
25
-
26
- if TYPE_CHECKING:
27
- from sensapex import SensapexDevice
28
-
29
-
30
- class UMP3Manipulator(SensapexManipulator):
31
- """Representation of a single Sensapex manipulator
32
-
33
- :param device: A Sensapex device
34
- :type device: :class: `sensapex.SensapexDevice`
35
- """
36
-
37
- def __init__(self, device: SensapexDevice) -> None:
38
- """Construct a new Manipulator object
39
-
40
- :param device: A Sensapex device
41
- """
42
- super().__init__(device)
43
-
44
- # Device functions
45
- def get_pos(self) -> PositionalResponse:
46
- """Get the current position of the manipulator and convert it into mm.
47
-
48
- :return: Position in (x, y, z, x) (or an empty array on error) in mm and error message (if any).
49
- :rtype: :class:`ephys_link.common.PositionalOutputData`
50
- """
51
- try:
52
- position = [axis / MM_TO_UM for axis in self._device.get_pos(1)]
53
-
54
- # Add the depth axis to the end of the position.
55
- position.append(position[0])
56
-
57
- # com.dprint(f"[SUCCESS]\t Got position of manipulator {self._id}\n")
58
- return PositionalResponse(
59
- position=Vector4(**dict(zip(Vector4.model_fields.keys(), position, strict=False)))
60
- )
61
- except Exception as e:
62
- print(f"[ERROR]\t\t Getting position of manipulator {self._id}")
63
- print(f"{e}\n")
64
- return PositionalResponse(error="Error getting position")
65
-
66
- async def goto_pos(self, request: GotoPositionRequest) -> PositionalResponse:
67
- """Move manipulator to position.
68
-
69
- :param request: The goto request parsed from the server.
70
- :type request: :class:`vbl_aquarium.models.ephys_link.GotoPositionRequest`
71
- :return: Resulting position in (x, y, z, x) (or an empty array on error) in mm and error message (if any).
72
- :rtype: :class:`ephys_link.common.PositionalOutputData`
73
- """
74
- # Check if able to write
75
- if not self._can_write:
76
- print(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
77
- return PositionalResponse(error="Manipulator movement canceled")
78
-
79
- # Stop current movement
80
- if self._is_moving:
81
- self._device.stop()
82
- self._is_moving = False
83
-
84
- try:
85
- target_position_um = request.position * MM_TO_UM
86
-
87
- # Restrict target position to just depth-axis if inside brain
88
- if self._inside_brain:
89
- d_axis = target_position_um.x
90
- target_position_um = target_position_um.model_copy(
91
- update={**self.get_pos().position.model_dump(), "x": d_axis}
92
- )
93
-
94
- # Mark movement as started
95
- self._is_moving = True
96
-
97
- # Send move command
98
- movement = self._device.goto_pos(
99
- vector4_to_array(target_position_um),
100
- request.speed * MM_TO_UM,
101
- )
102
-
103
- # Wait for movement to finish
104
- while not movement.finished:
105
- await asyncio.sleep(POSITION_POLL_DELAY)
106
-
107
- # Get position
108
- final_position = self.get_pos().position
109
-
110
- # Mark movement as finished
111
- self._is_moving = False
112
-
113
- # Return success unless write was disabled during movement (meaning a stop occurred)
114
- if not self._can_write:
115
- dprint(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
116
- return PositionalResponse(error="Manipulator movement canceled")
117
-
118
- # Return error if movement did not reach target.
119
- if not all(
120
- abs(axis) < self._movement_tolerance for axis in vector4_to_array(final_position - request.position)
121
- ):
122
- dprint(f"[ERROR]\t\t Manipulator {self._id} did not reach target position")
123
- dprint(f"\t\t\t Expected: {request.position}, Got: {final_position}")
124
- return PositionalResponse(error="Manipulator did not reach target position")
125
-
126
- # Made it to the target.
127
- dprint(f"[SUCCESS]\t Moved manipulator {self._id} to position" f" {final_position}\n")
128
- return PositionalResponse(position=final_position)
129
- except Exception as e:
130
- print(f"[ERROR]\t\t Moving manipulator {self._id} to position {request.position}")
131
- print(f"{e}\n")
132
- return PositionalResponse(error="Error moving manipulator")
133
-
134
- async def drive_to_depth(self, request: DriveToDepthRequest) -> DriveToDepthResponse:
135
- """Drive the manipulator to a certain depth.
136
-
137
- :param request: The drive to depth request parsed from the server.
138
- :type request: :class:`vbl_aquarium.models.ephys_link.DriveToDepthRequest`
139
- :return: Resulting depth in mm (or 0 on error) and error message (if any).
140
- :rtype: :class:`ephys_link.common.DriveToDepthOutputData`
141
- """
142
- # Get position before this movement
143
- target_pos = self.get_pos().position
144
-
145
- target_pos = target_pos.model_copy(update={"x": request.depth})
146
- movement_result = await self.goto_pos(GotoPositionRequest(**request.model_dump(), position=target_pos))
147
- return DriveToDepthResponse(depth=movement_result.position.w, error=movement_result.error)
Binary file
Binary file