diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..a4cb319
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index a55e7a1..79ee123 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/.idea/ephys-link.iml b/.idea/ephys-link.iml
index 00c6b7e..e0036ab 100644
--- a/.idea/ephys-link.iml
+++ b/.idea/ephys-link.iml
@@ -2,6 +2,7 @@
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 060f1a7..5d9fced 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,7 @@
+
diff --git a/pyproject.toml b/pyproject.toml
index ce99620..c6daa08 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "ephys-link"
-version = "0.9.14"
+version = "0.9.15"
license = { file = "LICENSE" }
authors = [{ name = "Kenneth Yang", email = "kjy5@uw.edu" }]
@@ -24,7 +24,7 @@ classifiers = [
"Topic :: Scientific/Engineering :: Medical Science Apps.",
]
-requires-python = ">=3.8"
+requires-python = ">=3.8, <3.12"
dependencies = [
"aiohttp==3.8.6",
"pyserial==3.5",
diff --git a/src/ephys_link/common.py b/src/ephys_link/common.py
index 55a84d9..162a8db 100644
--- a/src/ephys_link/common.py
+++ b/src/ephys_link/common.py
@@ -70,8 +70,10 @@ class GetManipulatorsOutputData(dict):
:param manipulators: Tuple of manipulator IDs (as strings)
:type manipulators: list
- :param manipulator_type: Type of the output data (temporary solution until #165 is implemented)
- :type manipulator_type: str
+ :param num_axes: Number of axes this manipulator has
+ :type num_axes: int
+ :param dimensions: Size of the movement space in mm (first 3 axes)
+ :type dimensions: list
:param error: Error message
:type error: str
@@ -79,10 +81,15 @@ class GetManipulatorsOutputData(dict):
:code:`{"manipulators": ["1", "2"], "error": ""}`
"""
- def __init__(self, manipulators: list, manipulator_type: str, error: str) -> None:
+ def __init__(
+ self, manipulators: list, num_axes: int, dimensions: list, error: str
+ ) -> None:
"""Constructor"""
super(GetManipulatorsOutputData, self).__init__(
- manipulators=manipulators, type=manipulator_type, error=error
+ manipulators=manipulators,
+ num_axes=num_axes,
+ dimensions=dimensions,
+ error=error,
)
diff --git a/src/ephys_link/platform_handler.py b/src/ephys_link/platform_handler.py
index 5f2460f..326b989 100644
--- a/src/ephys_link/platform_handler.py
+++ b/src/ephys_link/platform_handler.py
@@ -29,7 +29,10 @@ def __init__(self):
# Registered manipulators are stored as a dictionary of IDs (string) to
# manipulator objects
self.manipulators = {}
- self.type = "sensapex" # Remove this after #165
+ self.num_axes = 4
+
+ # Platform axes dimensions in mm
+ self.dimensions = [20, 20, 20, 20]
# Platform Handler Methods
@@ -73,7 +76,9 @@ def get_manipulators(self) -> com.GetManipulatorsOutputData:
except Exception as e:
print(f"[ERROR]\t\t Getting manipulators: {type(e)}: {e}\n")
finally:
- return com.GetManipulatorsOutputData(devices, self.type, error)
+ return com.GetManipulatorsOutputData(
+ devices, self.num_axes, self.dimensions, error
+ )
def register_manipulator(self, manipulator_id: str) -> str:
"""Register a manipulator
@@ -147,8 +152,13 @@ def get_pos(self, manipulator_id: str) -> com.PositionalOutputData:
print(f"[ERROR]\t\t Calibration not complete: {manipulator_id}\n")
return com.PositionalOutputData([], "Manipulator not calibrated")
- # Get position
- return self._get_pos(manipulator_id)
+ # Get position and convert to unified space
+ manipulator_pos = self._get_pos(manipulator_id)
+ if manipulator_pos["error"] != "":
+ return manipulator_pos
+ return com.PositionalOutputData(
+ self._platform_space_to_unified_space(manipulator_pos["position"]), ""
+ )
except KeyError:
# Manipulator not found in registered manipulators
@@ -207,7 +217,16 @@ async def goto_pos(
print(f"[ERROR]\t\t Cannot write to manipulator: {manipulator_id}")
return com.PositionalOutputData([], "Cannot write to manipulator")
- return await self._goto_pos(manipulator_id, position, speed)
+ # Convert position to platform space, move, and convert final position back to
+ # unified space
+ end_position = await self._goto_pos(
+ manipulator_id, self._unified_space_to_platform_space(position), speed
+ )
+ if end_position["error"] != "":
+ return end_position
+ return com.PositionalOutputData(
+ self._platform_space_to_unified_space(end_position["position"]), ""
+ )
except KeyError:
# Manipulator not found in registered manipulators
@@ -240,7 +259,17 @@ async def drive_to_depth(
print(f"[ERROR]\t\t Cannot write to manipulator: {manipulator_id}")
return com.DriveToDepthOutputData(0, "Cannot write to manipulator")
- return await self._drive_to_depth(manipulator_id, depth, speed)
+ end_depth = await self._drive_to_depth(
+ manipulator_id,
+ self._unified_space_to_platform_space([0, 0, 0, depth])[3],
+ speed,
+ )
+ if end_depth["error"] != "":
+ return end_depth
+ return com.DriveToDepthOutputData(
+ self._platform_space_to_unified_space([0, 0, 0, end_depth["depth"]])[3],
+ "",
+ )
except KeyError:
# Manipulator not found in registered manipulators
@@ -377,7 +406,6 @@ def _get_manipulators(self) -> list:
:return: List of manipulator IDs
:rtype: list
"""
- pass
@abstractmethod
def _register_manipulator(self, manipulator_id: str) -> None:
@@ -387,7 +415,6 @@ def _register_manipulator(self, manipulator_id: str) -> None:
:type manipulator_id: str
:return: None
"""
- pass
@abstractmethod
def _unregister_manipulator(self, manipulator_id: str) -> None:
@@ -397,7 +424,6 @@ def _unregister_manipulator(self, manipulator_id: str) -> None:
:type manipulator_id: str
:return: None
"""
- pass
@abstractmethod
def _get_pos(self, manipulator_id: str) -> com.PositionalOutputData:
@@ -409,7 +435,6 @@ def _get_pos(self, manipulator_id: str) -> com.PositionalOutputData:
empty array on error) in mm, error message)
:rtype: :class:`ephys_link.common.PositionalOutputData`
"""
- pass
@abstractmethod
def _get_angles(self, manipulator_id: str) -> com.AngularOutputData:
@@ -421,7 +446,6 @@ def _get_angles(self, manipulator_id: str) -> com.AngularOutputData:
empty array on error) in degrees, error message)
:rtype: :class:`ephys_link.common.AngularOutputData`
"""
- pass
@abstractmethod
async def _goto_pos(
@@ -439,7 +463,6 @@ async def _goto_pos(
empty array on error) in mm, error message)
:rtype: :class:`ephys_link.common.PositionalOutputData`
"""
- pass
@abstractmethod
async def _drive_to_depth(
@@ -457,7 +480,6 @@ async def _drive_to_depth(
message)
:rtype: :class:`ephys_link.common.DriveToDepthOutputData`
"""
- pass
@abstractmethod
def _set_inside_brain(
@@ -472,7 +494,6 @@ def _set_inside_brain(
:return: Callback parameters (manipulator ID, inside, error message)
:rtype: :class:`ephys_link.common.StateOutputData`
"""
- pass
@abstractmethod
async def _calibrate(self, manipulator_id: str, sio: socketio.AsyncServer) -> str:
@@ -485,7 +506,6 @@ async def _calibrate(self, manipulator_id: str, sio: socketio.AsyncServer) -> st
:return: Callback parameters (manipulator ID, error message)
:rtype: str
"""
- pass
@abstractmethod
def _bypass_calibration(self, manipulator_id: str) -> str:
@@ -496,7 +516,6 @@ def _bypass_calibration(self, manipulator_id: str) -> str:
:return: Callback parameters (manipulator ID, error message)
:rtype: str
"""
- pass
@abstractmethod
def _set_can_write(
@@ -519,4 +538,27 @@ def _set_can_write(
:return: Callback parameters (manipulator ID, can_write, error message)
:rtype: :class:`ephys_link.common.StateOutputData`
"""
- pass
+
+ @abstractmethod
+ def _platform_space_to_unified_space(
+ self, platform_position: list[float]
+ ) -> list[float]:
+ """Convert position in platform space to position in unified manipulator space
+
+ :param platform_position: Position in platform space (x, y, z, w) in mm
+ :type platform_position: list[float]
+ :return: Position in unified manipulator space (x, y, z, w) in mm
+ :rtype: list[float]
+ """
+
+ @abstractmethod
+ def _unified_space_to_platform_space(
+ self, unified_position: list[float]
+ ) -> list[float]:
+ """Convert position in unified manipulator space to position in platform space
+
+ :param unified_position: Position in unified manipulator space (x, y, z, w) in mm
+ :type unified_position: list[float]
+ :return: Position in platform space (x, y, z, w) in mm
+ :rtype: list[float]
+ """
diff --git a/src/ephys_link/platforms/new_scale_handler.py b/src/ephys_link/platforms/new_scale_handler.py
index 7e28c7e..7f3d55c 100644
--- a/src/ephys_link/platforms/new_scale_handler.py
+++ b/src/ephys_link/platforms/new_scale_handler.py
@@ -24,7 +24,8 @@ def __init__(self) -> None:
"""Initialize New Scale handler"""
super().__init__()
- self.type = "new_scale"
+ self.num_axes = 3
+ self.dimensions = [15, 15, 15]
# Load New Scale API
# noinspection PyUnresolvedReferences
@@ -120,3 +121,35 @@ def _set_can_write(
f"[SUCCESS]\t Set can_write state for manipulator" f" {manipulator_id}\n"
)
return com.StateOutputData(can_write, "")
+
+ def _platform_space_to_unified_space(
+ self, platform_position: list[float]
+ ) -> list[float]:
+ # unified <- platform
+ # +x <- -x
+ # +y <- +z
+ # +z <- +y
+ # +d <- -d
+
+ return [
+ self.dimensions[0] - platform_position[0],
+ platform_position[2],
+ platform_position[1],
+ self.dimensions[3] - platform_position[3],
+ ]
+
+ def _unified_space_to_platform_space(
+ self, unified_position: list[float]
+ ) -> list[float]:
+ # platform <- unified
+ # +x <- -x
+ # +y <- +z
+ # +z <- +y
+ # +d <- -d
+
+ return [
+ self.dimensions[0] - unified_position[0],
+ unified_position[2],
+ unified_position[1],
+ self.dimensions[3] - unified_position[3],
+ ]
diff --git a/src/ephys_link/platforms/new_scale_pathfinder_handler.py b/src/ephys_link/platforms/new_scale_pathfinder_handler.py
index be620d1..c622869 100644
--- a/src/ephys_link/platforms/new_scale_pathfinder_handler.py
+++ b/src/ephys_link/platforms/new_scale_pathfinder_handler.py
@@ -71,7 +71,7 @@ def __init__(self, port: int = 8080) -> None:
"""
super().__init__()
- self.type = "new_scale_pathfinder"
+ self.num_axes = -1
self.port = port
@@ -219,3 +219,13 @@ def _set_can_write(
sio: socketio.AsyncServer,
) -> com.StateOutputData:
pass
+
+ def _unified_space_to_platform_space(
+ self, unified_position: list[float]
+ ) -> list[float]:
+ pass
+
+ def _platform_space_to_unified_space(
+ self, platform_position: list[float]
+ ) -> list[float]:
+ pass
diff --git a/src/ephys_link/platforms/sensapex_handler.py b/src/ephys_link/platforms/sensapex_handler.py
index b3df37c..5f33732 100644
--- a/src/ephys_link/platforms/sensapex_handler.py
+++ b/src/ephys_link/platforms/sensapex_handler.py
@@ -124,3 +124,35 @@ def _set_can_write(
f"[SUCCESS]\t Set can_write state for manipulator" f" {manipulator_id}\n"
)
return com.StateOutputData(can_write, "")
+
+ def _platform_space_to_unified_space(
+ self, platform_position: list[float]
+ ) -> list[float]:
+ # unified <- platform
+ # +x <- +y
+ # +y <- -z
+ # +z <- +x
+ # +d <- +d
+
+ return [
+ platform_position[1],
+ self.dimensions[2] - platform_position[2],
+ platform_position[0],
+ platform_position[3],
+ ]
+
+ def _unified_space_to_platform_space(
+ self, unified_position: list[float]
+ ) -> list[float]:
+ # platform <- unified
+ # +x <- +z
+ # +y <- +x
+ # +z <- -y
+ # +d <- +d
+
+ return [
+ unified_position[2],
+ unified_position[0],
+ self.dimensions[2] - unified_position[1],
+ unified_position[3],
+ ]
diff --git a/src/ephys_link/platforms/sensapex_manipulator.py b/src/ephys_link/platforms/sensapex_manipulator.py
index b6b86e2..015559a 100644
--- a/src/ephys_link/platforms/sensapex_manipulator.py
+++ b/src/ephys_link/platforms/sensapex_manipulator.py
@@ -69,8 +69,8 @@ async def goto_pos(
"""
# Check if able to write
if not self._can_write:
- print(f"[ERROR]\t\t Manipulator {self._id} movement " f"canceled")
- return com.PositionalOutputData([], "Manipulator " "movement canceled")
+ print(f"[ERROR]\t\t Manipulator {self._id} movement canceled")
+ return com.PositionalOutputData([], "Manipulator movement canceled")
# Stop current movement
if self._is_moving: