ephys-link 1.3.0b2__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 -90
  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.0b2.dist-info → ephys_link-2.0.0.dist-info}/WHEEL +1 -1
  22. {ephys_link-1.3.0b2.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 -163
  26. ephys_link/platform_handler.py +0 -465
  27. ephys_link/platform_manipulator.py +0 -34
  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 -221
  34. ephys_link/platforms/ump3_handler.py +0 -57
  35. ephys_link/platforms/ump3_manipulator.py +0 -145
  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 -436
  40. ephys_link-1.3.0b2.dist-info/METADATA +0 -163
  41. ephys_link-1.3.0b2.dist-info/RECORD +0 -26
  42. {ephys_link-1.3.0b2.dist-info → ephys_link-2.0.0.dist-info}/entry_points.txt +0 -0
@@ -1,221 +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(zip(Vector4.model_fields.keys(), [axis / MM_TO_UM for axis in self._device.get_pos(1)]))
63
- )
64
- )
65
- except Exception as e:
66
- print(f"[ERROR]\t\t Getting position of manipulator {self._id}")
67
- print(f"{e}\n")
68
- return PositionalResponse(error="Error getting position")
69
-
70
- async def goto_pos(self, request: GotoPositionRequest) -> PositionalResponse:
71
- """Move manipulator to position.
72
-
73
- :param request: The goto request parsed from the server.
74
- :type request: :class:`vbl_aquarium.models.ephys_link.GotoPositionRequest`
75
- :return: Resulting position in (x, y, z, w) (or an empty array on error) in mm and error message (if any).
76
- :rtype: :class:`ephys_link.common.PositionalOutputData`
77
- """
78
- # Check if able to write
79
- if not self._can_write:
80
- print(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
81
- return PositionalResponse(error="Manipulator movement canceled")
82
-
83
- # Stop current movement
84
- if self._is_moving:
85
- self._device.stop()
86
- self._is_moving = False
87
-
88
- try:
89
- target_position_um = request.position * MM_TO_UM
90
-
91
- # Restrict target position to just depth-axis if inside brain
92
- if self._inside_brain:
93
- d_axis = target_position_um.w
94
- target_position_um = target_position_um.model_copy(
95
- update={**self.get_pos().position.model_dump(), "w": d_axis}
96
- )
97
-
98
- # Mark movement as started
99
- self._is_moving = True
100
-
101
- # Send move command
102
- movement = self._device.goto_pos(
103
- vector4_to_array(target_position_um),
104
- request.speed * MM_TO_UM,
105
- )
106
-
107
- # Wait for movement to finish
108
- while not movement.finished:
109
- await asyncio.sleep(POSITION_POLL_DELAY)
110
-
111
- # Get position
112
- final_position = self.get_pos().position
113
-
114
- # Mark movement as finished.
115
- self._is_moving = False
116
-
117
- # Return success unless write was disabled during movement (meaning a stop occurred).
118
- if not self._can_write:
119
- dprint(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
120
- return PositionalResponse(error="Manipulator movement canceled")
121
-
122
- # Return error if movement did not reach target.
123
- if not all(
124
- abs(axis) < self._movement_tolerance for axis in vector4_to_array(final_position - request.position)
125
- ):
126
- dprint(f"[ERROR]\t\t Manipulator {self._id} did not reach target position")
127
- dprint(f"\t\t\t Expected: {request.position}, Got: {final_position}")
128
- return PositionalResponse(error="Manipulator did not reach target position")
129
-
130
- # Made it to the target.
131
- dprint(f"[SUCCESS]\t Moved manipulator {self._id} to position {final_position}\n")
132
- return PositionalResponse(position=final_position)
133
- except Exception as e:
134
- print(f"[ERROR]\t\t Moving manipulator {self._id} to position {request.position}")
135
- print(f"{e}\n")
136
- return PositionalResponse(error="Error moving manipulator")
137
-
138
- async def drive_to_depth(self, request: DriveToDepthRequest) -> DriveToDepthResponse:
139
- """Drive the manipulator to a certain depth.
140
-
141
- :param request: The drive to depth request parsed from the server.
142
- :type request: :class:`vbl_aquarium.models.ephys_link.DriveToDepthRequest`
143
- :return: Resulting depth in mm (or 0 on error) and error message (if any).
144
- :rtype: :class:`ephys_link.common.DriveToDepthOutputData`
145
- """
146
- # Get position before this movement
147
- target_pos = self.get_pos().position
148
-
149
- target_pos = target_pos.model_copy(update={"w": request.depth})
150
- movement_result = await self.goto_pos(GotoPositionRequest(**request.model_dump(), position=target_pos))
151
- return DriveToDepthResponse(depth=movement_result.position.w, error=movement_result.error)
152
-
153
- def set_inside_brain(self, inside: bool) -> None:
154
- """Set if the manipulator is inside the brain.
155
-
156
- Used to signal that the brain should move at :const:`INSIDE_BRAIN_SPEED_LIMIT`
157
-
158
- :param inside: True if the manipulator is inside the brain, False otherwise.
159
- :type inside: bool
160
- :return: None
161
- """
162
- self._inside_brain = inside
163
-
164
- def get_can_write(self) -> bool:
165
- """Return if the manipulator can move.
166
-
167
- :return: True if the manipulator can move, False otherwise.
168
- :rtype: bool
169
- """
170
- return self._can_write
171
-
172
- def set_can_write(self, request: CanWriteRequest) -> None:
173
- """Set if the manipulator can move.
174
-
175
- :param request: The can write request parsed from the server.
176
- :type request: :class:`vbl_aquarium.models.ephys_link.CanWriteRequest`
177
- :return: None
178
- """
179
- self._can_write = request.can_write
180
-
181
- if request.can_write and request.hours > 0:
182
- if self._reset_timer:
183
- self._reset_timer.cancel()
184
- self._reset_timer = threading.Timer(request.hours * HOURS_TO_SECONDS, self.reset_can_write)
185
- self._reset_timer.start()
186
-
187
- def reset_can_write(self) -> None:
188
- """Reset the :attr:`can_write` flag."""
189
- self._can_write = False
190
-
191
- # Calibration
192
- def call_calibrate(self) -> None:
193
- """Calibrate the manipulator.
194
-
195
- :return: None
196
- """
197
- self._device.calibrate_zero_position()
198
-
199
- def get_calibrated(self) -> bool:
200
- """Return the calibration state of the manipulator.
201
-
202
- :return: True if the manipulator is calibrated, False otherwise.
203
- :rtype: bool
204
- """
205
- return self._calibrated
206
-
207
- def set_calibrated(self) -> None:
208
- """Set the manipulator to be calibrated.
209
-
210
- :return: None
211
- """
212
- self._calibrated = True
213
-
214
- def stop(self) -> None:
215
- """Stop the manipulator
216
-
217
- :return: None
218
- """
219
- self._can_write = False
220
- self._device.stop()
221
- 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: list[float]) -> list[float]:
32
- # unified <- platform
33
- # +x <- +y
34
- # +y <- -x
35
- # +z <- -z
36
- # +d <- +d/x
37
-
38
- return [
39
- platform_position[1],
40
- self.dimensions[0] - platform_position[0],
41
- self.dimensions[2] - platform_position[2],
42
- platform_position[3],
43
- ]
44
-
45
- def _unified_space_to_platform_space(self, unified_position: list[float]) -> list[float]:
46
- # platform <- unified
47
- # +x <- -y
48
- # +y <- +x
49
- # +z <- -z
50
- # +d/x <- +d
51
-
52
- return [
53
- self.dimensions[1] - unified_position[1],
54
- unified_position[0],
55
- self.dimensions[2] - unified_position[2],
56
- unified_position[3],
57
- ]
@@ -1,145 +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(position=Vector4(**dict(zip(Vector4.model_fields.keys(), position))))
59
- except Exception as e:
60
- print(f"[ERROR]\t\t Getting position of manipulator {self._id}")
61
- print(f"{e}\n")
62
- return PositionalResponse(error="Error getting position")
63
-
64
- async def goto_pos(self, request: GotoPositionRequest) -> PositionalResponse:
65
- """Move manipulator to position.
66
-
67
- :param request: The goto request parsed from the server.
68
- :type request: :class:`vbl_aquarium.models.ephys_link.GotoPositionRequest`
69
- :return: Resulting position in (x, y, z, x) (or an empty array on error) in mm and error message (if any).
70
- :rtype: :class:`ephys_link.common.PositionalOutputData`
71
- """
72
- # Check if able to write
73
- if not self._can_write:
74
- print(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
75
- return PositionalResponse(error="Manipulator movement canceled")
76
-
77
- # Stop current movement
78
- if self._is_moving:
79
- self._device.stop()
80
- self._is_moving = False
81
-
82
- try:
83
- target_position_um = request.position * MM_TO_UM
84
-
85
- # Restrict target position to just depth-axis if inside brain
86
- if self._inside_brain:
87
- d_axis = target_position_um.x
88
- target_position_um = target_position_um.model_copy(
89
- update={**self.get_pos().position.model_dump(), "x": d_axis}
90
- )
91
-
92
- # Mark movement as started
93
- self._is_moving = True
94
-
95
- # Send move command
96
- movement = self._device.goto_pos(
97
- vector4_to_array(target_position_um),
98
- request.speed * MM_TO_UM,
99
- )
100
-
101
- # Wait for movement to finish
102
- while not movement.finished:
103
- await asyncio.sleep(POSITION_POLL_DELAY)
104
-
105
- # Get position
106
- final_position = self.get_pos().position
107
-
108
- # Mark movement as finished
109
- self._is_moving = False
110
-
111
- # Return success unless write was disabled during movement (meaning a stop occurred)
112
- if not self._can_write:
113
- dprint(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
114
- return PositionalResponse(error="Manipulator movement canceled")
115
-
116
- # Return error if movement did not reach target.
117
- if not all(
118
- abs(axis) < self._movement_tolerance for axis in vector4_to_array(final_position - request.position)
119
- ):
120
- dprint(f"[ERROR]\t\t Manipulator {self._id} did not reach target position")
121
- dprint(f"\t\t\t Expected: {request.position}, Got: {final_position}")
122
- return PositionalResponse(error="Manipulator did not reach target position")
123
-
124
- # Made it to the target.
125
- dprint(f"[SUCCESS]\t Moved manipulator {self._id} to position" f" {final_position}\n")
126
- return PositionalResponse(position=final_position)
127
- except Exception as e:
128
- print(f"[ERROR]\t\t Moving manipulator {self._id} to position {request.position}")
129
- print(f"{e}\n")
130
- return PositionalResponse(error="Error moving manipulator")
131
-
132
- async def drive_to_depth(self, request: DriveToDepthRequest) -> DriveToDepthResponse:
133
- """Drive the manipulator to a certain depth.
134
-
135
- :param request: The drive to depth request parsed from the server.
136
- :type request: :class:`vbl_aquarium.models.ephys_link.DriveToDepthRequest`
137
- :return: Resulting depth in mm (or 0 on error) and error message (if any).
138
- :rtype: :class:`ephys_link.common.DriveToDepthOutputData`
139
- """
140
- # Get position before this movement
141
- target_pos = self.get_pos().position
142
-
143
- target_pos = target_pos.model_copy(update={"x": request.depth})
144
- movement_result = await self.goto_pos(GotoPositionRequest(**request.model_dump(), position=target_pos))
145
- return DriveToDepthResponse(depth=movement_result.position.w, error=movement_result.error)
Binary file
Binary file