Skip to content

Commit

Permalink
362 new scale mpm bindings (#374)
Browse files Browse the repository at this point in the history
* Read bindings, draft write, not real export yet

* Put TODO

* Version bump

* Move manipulator and handle system getting stuck

* WIP set depth

* Better movement code

* Fix some linting errors, update aquarium

* Switch to local coordinate system

* Merged depth binding and position

* Update python module launch instructions

* Version bump

* MPM echoing like other platforms. Need to limit HTTP debug

* Restrict debug log level to just ephys link

* Switch step mode

* Correct movement direction

* fixed position update and target coordinates for depth

* Progress with duplicated coordinate.

* Revert "Progress with duplicated coordinate."

This reverts commit 3eed59b.

* Dual axis movement does not work

* MPM movement working

* Apply poll limit to position

* Auto format code

---------

Co-authored-by: kjy5 <[email protected]>
  • Loading branch information
kjy5 and kjy5 authored Jul 31, 2024
1 parent f739c4b commit d845519
Show file tree
Hide file tree
Showing 13 changed files with 390 additions and 81 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,12 @@ window instead of `localhost`.
pip install ephys-link
```

Import the modules you need and launch the server.
Import main and run (this will launch the setup GUI).

```python
from ephys_link.server import Server
from ephys_link.__main__ import main

server = Server()
server.launch("sensapex", args.proxy_address, 8081)
main()
```

## Install for Development
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ exclude = ["/.github", "/.idea"]
[tool.hatch.envs.default]
python = "3.12"
dependencies = [
"mypy",
"coverage[toml]>=6.5",
"pytest",
]
Expand Down
11 changes: 6 additions & 5 deletions scripts/move_tester.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from asyncio import run

from vbl_aquarium.models.ephys_link import SetPositionRequest
from vbl_aquarium.models.ephys_link import EphysLinkOptions, SetDepthRequest
from vbl_aquarium.models.unity import Vector4

from ephys_link.back_end.platform_handler import PlatformHandler
from ephys_link.util.console import Console

c = Console(enable_debug=True)
p = PlatformHandler("ump-4", c)
target = Vector4()
# target = Vector4(x=10, y=10, z=10, w=10)
p = PlatformHandler(EphysLinkOptions(type="pathfinder-mpm"), c)
# target = Vector4()
target = Vector4(x=7.5, y=7.5, z=7.5, w=7.5)

print(run(p.set_position(SetPositionRequest(manipulator_id="6", position=target, speed=5))).to_json_string())
# print(run(p.set_position(SetPositionRequest(manipulator_id="A", position=target, speed=5))).to_json_string())
print(run(p.set_depth(SetDepthRequest(manipulator_id="A", depth=7.5, speed=0.15))).to_json_string())
print("Done!")
2 changes: 1 addition & 1 deletion src/ephys_link/__about__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2.0.0b3"
__version__ = "2.0.0b5"
2 changes: 1 addition & 1 deletion src/ephys_link/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def main() -> None:
console = Console(enable_debug=options.debug)

# 3. Instantiate the Platform Handler with the appropriate platform bindings.
platform_handler = PlatformHandler(options.type, console)
platform_handler = PlatformHandler(options, console)

# 4. Instantiate the Emergency Stop service.

Expand Down
61 changes: 34 additions & 27 deletions src/ephys_link/back_end/platform_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from vbl_aquarium.models.ephys_link import (
AngularResponse,
BooleanStateResponse,
EphysLinkOptions,
GetManipulatorsResponse,
PositionalResponse,
SetDepthRequest,
Expand All @@ -25,6 +26,7 @@

from ephys_link.__about__ import __version__
from ephys_link.bindings.fake_bindings import FakeBindings
from ephys_link.bindings.mpm_bindings import MPMBinding
from ephys_link.bindings.ump_4_bindings import Ump4Bindings
from ephys_link.util.base_bindings import BaseBindings
from ephys_link.util.common import vector4_to_array
Expand All @@ -34,43 +36,45 @@
class PlatformHandler:
"""Handler for platform commands."""

def __init__(self, platform_type: str, console: Console) -> None:
def __init__(self, options: EphysLinkOptions, console: Console) -> None:
"""Initialize platform handler.
:param platform_type: Platform type to initialize bindings from.
:type platform_type: str
:param options: CLI options.
:type options: EphysLinkOptions
"""

# Store the platform type.
self._platform_type = platform_type
# Store the CLI options.
self._options = options

# Store the console.
self._console = console

# Define bindings based on platform type.
self._bindings = self._match_platform_type(platform_type)
self._bindings = self._match_platform_type(options)

# Record which IDs are inside the brain.
self._inside_brain: set[str] = set()

# Generate a Pinpoint ID for proxy usage.
self._pinpoint_id = str(uuid4())[:8]

def _match_platform_type(self, platform_type: str) -> BaseBindings:
def _match_platform_type(self, options: EphysLinkOptions) -> BaseBindings:
"""Match the platform type to the appropriate bindings.
:param platform_type: Platform type.
:type platform_type: str
:param options: CLI options.
:type options: EphysLinkOptions
:returns: Bindings for the specified platform type.
:rtype: :class:`ephys_link.util.base_bindings.BaseBindings`
"""
match platform_type:
match options.type:
case "ump-4":
return Ump4Bindings()
case "pathfinder-mpm":
return MPMBinding(options.mpm_port)
case "fake":
return FakeBindings()
case _:
error_message = f'Platform type "{platform_type}" not recognized.'
error_message = f'Platform type "{options.type}" not recognized.'
self._console.critical_print(error_message)
raise ValueError(error_message)

Expand Down Expand Up @@ -99,7 +103,7 @@ def get_platform_type(self) -> str:
:returns: Platform type config identifier (see CLI options for examples).
:rtype: str
"""
return self._platform_type
return str(self._options.type)

# Manipulator commands.

Expand All @@ -111,7 +115,7 @@ async def get_manipulators(self) -> GetManipulatorsResponse:
"""
try:
manipulators = await self._bindings.get_manipulators()
num_axes = await self._bindings.get_num_axes()
num_axes = await self._bindings.get_axes_count()
dimensions = self._bindings.get_dimensions()
except Exception as e:
self._console.exception_error_print("Get Manipulators", e)
Expand Down Expand Up @@ -198,16 +202,16 @@ async def set_position(self, request: SetPositionRequest) -> PositionalResponse:

# Return error if movement did not reach target within tolerance.
for index, axis in enumerate(vector4_to_array(final_unified_position - request.position)):
# End once index is greater than the number of axes.
if index >= await self._bindings.get_num_axes():
# End once index is the number of axes.
if index == await self._bindings.get_axes_count():
break

# Check if the axis is within the movement tolerance.
if abs(axis) > await self._bindings.get_movement_tolerance():
if abs(axis) > self._bindings.get_movement_tolerance():
error_message = (
f"Manipulator {request.manipulator_id} did not reach target"
f" position on axis {list(Vector4.model_fields.keys())[index]}."
f"Requested: {request.position}, got: {final_unified_position}."
f" Requested: {request.position}, got: {final_unified_position}."
)
self._console.error_print("Set Position", error_message)
return PositionalResponse(error=error_message)
Expand All @@ -226,24 +230,27 @@ async def set_depth(self, request: SetDepthRequest) -> SetDepthResponse:
:rtype: :class:`vbl_aquarium.models.ephys_link.DriveToDepthResponse`
"""
try:
# Create a position based on the new depth.
current_platform_position = await self._bindings.get_position(request.manipulator_id)
current_unified_position = self._bindings.platform_space_to_unified_space(current_platform_position)
target_unified_position = current_unified_position.model_copy(update={"w": request.depth})
target_platform_position = self._bindings.unified_space_to_platform_space(target_unified_position)

# Move to the new depth.
final_platform_position = await self._bindings.set_position(
final_platform_depth = await self._bindings.set_depth(
manipulator_id=request.manipulator_id,
position=target_platform_position,
depth=self._bindings.unified_space_to_platform_space(Vector4(w=request.depth)).w,
speed=request.speed,
)
final_unified_position = self._bindings.platform_space_to_unified_space(final_platform_position)
final_unified_depth = self._bindings.platform_space_to_unified_space(Vector4(w=final_platform_depth)).w

# Return error if movement did not reach target within tolerance.
if abs(final_unified_depth - request.depth) > self._bindings.get_movement_tolerance():
error_message = (
f"Manipulator {request.manipulator_id} did not reach target depth."
f" Requested: {request.depth}, got: {final_unified_depth}."
)
self._console.error_print("Set Depth", error_message)
return SetDepthResponse(error=error_message)
except Exception as e:
self._console.exception_error_print("Set Depth", e)
return SetDepthResponse(error=self._console.pretty_exception(e))
else:
return SetDepthResponse(depth=final_unified_position.w)
return SetDepthResponse(depth=final_unified_depth)

async def set_inside_brain(self, request: SetInsideBrainRequest) -> BooleanStateResponse:
"""Mark a manipulator as inside the brain or not.
Expand Down
2 changes: 1 addition & 1 deletion src/ephys_link/back_end/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
SetInsideBrainRequest,
SetPositionRequest,
)
from vbl_aquarium.models.generic import VBLBaseModel
from vbl_aquarium.utils.vbl_base_model import VBLBaseModel

from ephys_link.back_end.platform_handler import PlatformHandler
from ephys_link.util.common import PORT, check_for_updates, server_preamble
Expand Down
11 changes: 8 additions & 3 deletions src/ephys_link/bindings/fake_bindings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from vbl_aquarium.models.unity import Vector3, Vector4

from ephys_link.util.base_bindings import BaseBindings
from ephys_link.util.common import array_to_vector4


class FakeBindings(BaseBindings):
Expand All @@ -22,11 +23,11 @@ def __init__(self) -> None:
async def get_manipulators(self) -> list[str]:
return list(map(str, range(8)))

async def get_num_axes(self) -> int:
async def get_axes_count(self) -> int:
return 4

def get_dimensions(self) -> Vector4:
return Vector4(x=20, y=20, z=20, w=20)
return array_to_vector4([20] * 4)

async def get_position(self, manipulator_id: str) -> Vector4:
return self._positions[int(manipulator_id)]
Expand All @@ -37,13 +38,17 @@ async def get_angles(self, manipulator_id: str) -> Vector3:
async def get_shank_count(self, _: str) -> int:
return 1

async def get_movement_tolerance(self) -> float:
def get_movement_tolerance(self) -> float:
return 0.001

async def set_position(self, manipulator_id: str, position: Vector4, _: float) -> Vector4:
self._positions[int(manipulator_id)] = position
return position

async def set_depth(self, manipulator_id: str, depth: float, _: float) -> float:
self._positions[int(manipulator_id)].w = depth
return depth

async def stop(self, _: str) -> None:
pass

Expand Down
Loading

0 comments on commit d845519

Please sign in to comment.