ephys-link 1.3.3__tar.gz → 2.0.0b1__tar.gz
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.
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/PKG-INFO +6 -4
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/ephys_link.spec +5 -0
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/pyproject.toml +15 -9
- ephys_link-2.0.0b1/scripts/move_tester.py +15 -0
- ephys_link-2.0.0b1/scripts/server_tester.py +23 -0
- ephys_link-2.0.0b1/src/ephys_link/__about__.py +1 -0
- ephys_link-2.0.0b1/src/ephys_link/__main__.py +43 -0
- ephys_link-2.0.0b1/src/ephys_link/back_end/__init__.py +0 -0
- ephys_link-2.0.0b1/src/ephys_link/back_end/platform_handler.py +298 -0
- ephys_link-2.0.0b1/src/ephys_link/back_end/server.py +200 -0
- ephys_link-2.0.0b1/src/ephys_link/bindings/__init__.py +0 -0
- ephys_link-2.0.0b1/src/ephys_link/bindings/fake_bindings.py +54 -0
- ephys_link-2.0.0b1/src/ephys_link/bindings/ump_4_bindings.py +127 -0
- ephys_link-2.0.0b1/src/ephys_link/front_end/__init__.py +0 -0
- ephys_link-2.0.0b1/src/ephys_link/front_end/cli.py +98 -0
- {ephys_link-1.3.3/src/ephys_link → ephys_link-2.0.0b1/src/ephys_link/front_end}/gui.py +93 -95
- ephys_link-2.0.0b1/src/ephys_link/util/__init__.py +0 -0
- ephys_link-2.0.0b1/src/ephys_link/util/base_bindings.py +133 -0
- ephys_link-2.0.0b1/src/ephys_link/util/common.py +121 -0
- ephys_link-2.0.0b1/src/ephys_link/util/console.py +112 -0
- ephys_link-1.3.3/scripts/move_tester.py +0 -21
- ephys_link-1.3.3/scripts/proxy_dev.py +0 -24
- ephys_link-1.3.3/src/ephys_link/__about__.py +0 -1
- ephys_link-1.3.3/src/ephys_link/__main__.py +0 -105
- ephys_link-1.3.3/src/ephys_link/common.py +0 -49
- ephys_link-1.3.3/src/ephys_link/emergency_stop.py +0 -67
- ephys_link-1.3.3/src/ephys_link/platform_handler.py +0 -465
- ephys_link-1.3.3/src/ephys_link/platform_manipulator.py +0 -35
- ephys_link-1.3.3/src/ephys_link/platforms/__init__.py +0 -5
- ephys_link-1.3.3/src/ephys_link/platforms/new_scale_handler.py +0 -141
- ephys_link-1.3.3/src/ephys_link/platforms/new_scale_manipulator.py +0 -312
- ephys_link-1.3.3/src/ephys_link/platforms/new_scale_pathfinder_handler.py +0 -235
- ephys_link-1.3.3/src/ephys_link/platforms/sensapex_handler.py +0 -151
- ephys_link-1.3.3/src/ephys_link/platforms/sensapex_manipulator.py +0 -227
- ephys_link-1.3.3/src/ephys_link/platforms/ump3_handler.py +0 -57
- ephys_link-1.3.3/src/ephys_link/platforms/ump3_manipulator.py +0 -147
- ephys_link-1.3.3/src/ephys_link/server.py +0 -508
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/.gitignore +0 -0
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/LICENSE +0 -0
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/README.md +0 -0
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/assets/icon.ico +0 -0
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/scripts/__init__.py +0 -0
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/src/ephys_link/__init__.py +0 -0
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/src/ephys_link/resources/CP210xManufacturing.dll +0 -0
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/src/ephys_link/resources/NstMotorCtrl.dll +0 -0
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/src/ephys_link/resources/SiUSBXp.dll +0 -0
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/src/ephys_link/resources/libum.dll +0 -0
- {ephys_link-1.3.3 → ephys_link-2.0.0b1}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: ephys-link
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0b1
|
|
4
4
|
Summary: A Python Socket.IO server that allows any Socket.IO-compliant application to communicate with manipulators used in electrophysiology experiments.
|
|
5
5
|
Project-URL: Documentation, https://virtualbrainlab.org/ephys_link/installation_and_use.html
|
|
6
6
|
Project-URL: Issues, https://github.com/VirtualBrainLab/ephys-link/issues
|
|
@@ -27,12 +27,14 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
27
27
|
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
28
28
|
Requires-Python: <3.13,>=3.10
|
|
29
29
|
Requires-Dist: aiohttp==3.9.5
|
|
30
|
+
Requires-Dist: colorama==0.4.6
|
|
30
31
|
Requires-Dist: platformdirs==4.2.2
|
|
31
32
|
Requires-Dist: pyserial==3.5
|
|
32
|
-
Requires-Dist: python-socketio[asyncio-client]==5.11.
|
|
33
|
+
Requires-Dist: python-socketio[asyncio-client]==5.11.3
|
|
33
34
|
Requires-Dist: pythonnet==3.0.3
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
Requires-Dist:
|
|
35
|
+
Requires-Dist: requests==2.32.3
|
|
36
|
+
Requires-Dist: sensapex==1.400.1
|
|
37
|
+
Requires-Dist: vbl-aquarium==0.0.19
|
|
36
38
|
Description-Content-Type: text/markdown
|
|
37
39
|
|
|
38
40
|
# Electrophysiology Manipulator Link
|
|
@@ -10,6 +10,7 @@ options = parser.parse_args()
|
|
|
10
10
|
|
|
11
11
|
FILE_NAME = f"EphysLink-v{version}"
|
|
12
12
|
|
|
13
|
+
# noinspection PyUnresolvedReferences
|
|
13
14
|
a = Analysis(
|
|
14
15
|
['src\\ephys_link\\__main__.py'],
|
|
15
16
|
pathex=[],
|
|
@@ -23,9 +24,11 @@ a = Analysis(
|
|
|
23
24
|
noarchive=False,
|
|
24
25
|
optimize=1,
|
|
25
26
|
)
|
|
27
|
+
# noinspection PyUnresolvedReferences
|
|
26
28
|
pyz = PYZ(a.pure)
|
|
27
29
|
|
|
28
30
|
if options.dir:
|
|
31
|
+
# noinspection PyUnresolvedReferences
|
|
29
32
|
exe = EXE(
|
|
30
33
|
pyz,
|
|
31
34
|
a.scripts,
|
|
@@ -45,8 +48,10 @@ if options.dir:
|
|
|
45
48
|
entitlements_file=None,
|
|
46
49
|
icon='assets\\icon.ico',
|
|
47
50
|
)
|
|
51
|
+
# noinspection PyUnresolvedReferences
|
|
48
52
|
coll = COLLECT(exe, a.binaries, a.datas, strip=False, upx=True, upx_exclude=[], name=FILE_NAME)
|
|
49
53
|
else:
|
|
54
|
+
# noinspection PyUnresolvedReferences
|
|
50
55
|
exe = EXE(
|
|
51
56
|
pyz,
|
|
52
57
|
a.scripts,
|
|
@@ -31,12 +31,14 @@ classifiers = [
|
|
|
31
31
|
]
|
|
32
32
|
dependencies = [
|
|
33
33
|
"aiohttp==3.9.5",
|
|
34
|
+
"colorama==0.4.6",
|
|
34
35
|
"platformdirs==4.2.2",
|
|
35
36
|
"pyserial==3.5",
|
|
36
|
-
"python-socketio[asyncio_client]==5.11.
|
|
37
|
+
"python-socketio[asyncio_client]==5.11.3",
|
|
37
38
|
"pythonnet==3.0.3",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
39
|
+
"requests==2.32.3",
|
|
40
|
+
"sensapex==1.400.1",
|
|
41
|
+
"vbl-aquarium==0.0.19"
|
|
40
42
|
]
|
|
41
43
|
|
|
42
44
|
[project.urls]
|
|
@@ -59,7 +61,6 @@ exclude = ["/.github", "/.idea"]
|
|
|
59
61
|
python = "3.12"
|
|
60
62
|
dependencies = [
|
|
61
63
|
"coverage[toml]>=6.5",
|
|
62
|
-
"mypy>=1.0.0",
|
|
63
64
|
"pytest",
|
|
64
65
|
]
|
|
65
66
|
[tool.hatch.envs.default.scripts]
|
|
@@ -73,7 +74,6 @@ cov = [
|
|
|
73
74
|
"test-cov",
|
|
74
75
|
"cov-report",
|
|
75
76
|
]
|
|
76
|
-
types = "mypy --strict --install-types --non-interactive {args:src/ephys_link tests}"
|
|
77
77
|
|
|
78
78
|
#[[tool.hatch.envs.all.matrix]]
|
|
79
79
|
#python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
|
@@ -88,6 +88,15 @@ build = "pyinstaller.exe ephys_link.spec -y -- -d && pyinstaller.exe ephys_link.
|
|
|
88
88
|
build_onefile = "pyinstaller.exe ephys_link.spec -y"
|
|
89
89
|
build_clean = "pyinstaller.exe ephys_link.spec -y --clean"
|
|
90
90
|
|
|
91
|
+
[tool.hatch.envs.types]
|
|
92
|
+
python = "3.12"
|
|
93
|
+
skip-install = true
|
|
94
|
+
dependencies = [
|
|
95
|
+
"mypy",
|
|
96
|
+
]
|
|
97
|
+
[tool.hatch.envs.types.scripts]
|
|
98
|
+
check = "mypy --strict --install-types --non-interactive --ignore-missing-imports {args:src/ephys_link tests}"
|
|
99
|
+
|
|
91
100
|
[tool.coverage.run]
|
|
92
101
|
source_pkgs = ["ephys_link", "tests"]
|
|
93
102
|
branch = true
|
|
@@ -105,7 +114,4 @@ exclude_lines = [
|
|
|
105
114
|
"no cov",
|
|
106
115
|
"if __name__ == .__main__.:",
|
|
107
116
|
"if TYPE_CHECKING:",
|
|
108
|
-
]
|
|
109
|
-
|
|
110
|
-
[tool.ruff.lint]
|
|
111
|
-
extend-ignore = ["T201", "PLW0603", "BLE001", "FBT001", "ARG002", "S310"]
|
|
117
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from asyncio import run
|
|
2
|
+
|
|
3
|
+
from vbl_aquarium.models.ephys_link import SetPositionRequest
|
|
4
|
+
from vbl_aquarium.models.unity import Vector4
|
|
5
|
+
|
|
6
|
+
from ephys_link.back_end.platform_handler import PlatformHandler
|
|
7
|
+
from ephys_link.util.console import Console
|
|
8
|
+
|
|
9
|
+
c = Console(enable_debug=True)
|
|
10
|
+
p = PlatformHandler("ump-4", c)
|
|
11
|
+
target = Vector4()
|
|
12
|
+
# target = Vector4(x=10, y=10, z=10, w=10)
|
|
13
|
+
|
|
14
|
+
print(run(p.set_position(SetPositionRequest(manipulator_id="6", position=target, speed=5))).to_json_string())
|
|
15
|
+
print("Done!")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from time import sleep
|
|
2
|
+
|
|
3
|
+
from socketio import SimpleClient
|
|
4
|
+
from vbl_aquarium.models.ephys_link import SetDepthRequest, SetInsideBrainRequest
|
|
5
|
+
from vbl_aquarium.models.unity import Vector4
|
|
6
|
+
|
|
7
|
+
with SimpleClient() as sio:
|
|
8
|
+
sio.connect("http://localhost:3000")
|
|
9
|
+
|
|
10
|
+
print(sio.call("set_inside_brain", SetInsideBrainRequest(manipulator_id="6", inside=True).to_json_string()))
|
|
11
|
+
|
|
12
|
+
target = Vector4()
|
|
13
|
+
# target = Vector4(x=10, y=10, z=10, w=10)
|
|
14
|
+
|
|
15
|
+
sio.emit(
|
|
16
|
+
"set_depth",
|
|
17
|
+
SetDepthRequest(manipulator_id="6", depth=10, speed=3).to_json_string(),
|
|
18
|
+
)
|
|
19
|
+
sleep(1)
|
|
20
|
+
print(sio.call("stop"))
|
|
21
|
+
# while True:
|
|
22
|
+
# print(sio.call("get_position", "6"))
|
|
23
|
+
sio.disconnect()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.0b1"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Ephys Link entry point.
|
|
2
|
+
|
|
3
|
+
Responsible for gathering launch options, instantiating the appropriate interface, and starting the application.
|
|
4
|
+
|
|
5
|
+
Usage: call main() to start.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from sys import argv
|
|
9
|
+
|
|
10
|
+
from ephys_link.back_end.platform_handler import PlatformHandler
|
|
11
|
+
from ephys_link.back_end.server import Server
|
|
12
|
+
from ephys_link.front_end.cli import CLI
|
|
13
|
+
from ephys_link.front_end.gui import GUI
|
|
14
|
+
from ephys_link.util.console import Console
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main() -> None:
|
|
18
|
+
"""Ephys Link entry point.
|
|
19
|
+
|
|
20
|
+
1. Get options via CLI or GUI.
|
|
21
|
+
2. Instantiate the Console and make it globally accessible.
|
|
22
|
+
3. Instantiate the Platform Handler with the appropriate platform bindings.
|
|
23
|
+
4. Instantiate the Emergency Stop service.
|
|
24
|
+
5. Start the server.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# 1. Get options via CLI or GUI (if no CLI options are provided).
|
|
28
|
+
options = CLI().parse_args() if len(argv) > 1 else GUI().get_options()
|
|
29
|
+
|
|
30
|
+
# 2. Instantiate the Console and make it globally accessible.
|
|
31
|
+
console = Console(enable_debug=options.debug)
|
|
32
|
+
|
|
33
|
+
# 3. Instantiate the Platform Handler with the appropriate platform bindings.
|
|
34
|
+
platform_handler = PlatformHandler(options.type, console)
|
|
35
|
+
|
|
36
|
+
# 4. Instantiate the Emergency Stop service.
|
|
37
|
+
|
|
38
|
+
# 5. Start the server.
|
|
39
|
+
Server(options, platform_handler, console).launch()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# ruff: noqa: BLE001
|
|
2
|
+
"""Manipulator platform handler.
|
|
3
|
+
|
|
4
|
+
Responsible for performing the various manipulator commands.
|
|
5
|
+
Instantiates the appropriate bindings based on the platform type and uses them to perform the commands.
|
|
6
|
+
|
|
7
|
+
Usage: Instantiate PlatformHandler with the platform type and call the desired command.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from uuid import uuid4
|
|
11
|
+
|
|
12
|
+
from vbl_aquarium.models.ephys_link import (
|
|
13
|
+
AngularResponse,
|
|
14
|
+
BooleanStateResponse,
|
|
15
|
+
GetManipulatorsResponse,
|
|
16
|
+
PositionalResponse,
|
|
17
|
+
SetDepthRequest,
|
|
18
|
+
SetDepthResponse,
|
|
19
|
+
SetInsideBrainRequest,
|
|
20
|
+
SetPositionRequest,
|
|
21
|
+
ShankCountResponse,
|
|
22
|
+
)
|
|
23
|
+
from vbl_aquarium.models.proxy import PinpointIdResponse
|
|
24
|
+
from vbl_aquarium.models.unity import Vector4
|
|
25
|
+
|
|
26
|
+
from ephys_link.__about__ import __version__
|
|
27
|
+
from ephys_link.bindings.fake_bindings import FakeBindings
|
|
28
|
+
from ephys_link.bindings.ump_4_bindings import Ump4Bindings
|
|
29
|
+
from ephys_link.util.base_bindings import BaseBindings
|
|
30
|
+
from ephys_link.util.common import vector4_to_array
|
|
31
|
+
from ephys_link.util.console import Console
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PlatformHandler:
|
|
35
|
+
"""Handler for platform commands."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, platform_type: str, console: Console) -> None:
|
|
38
|
+
"""Initialize platform handler.
|
|
39
|
+
|
|
40
|
+
:param platform_type: Platform type to initialize bindings from.
|
|
41
|
+
:type platform_type: str
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
# Store the platform type.
|
|
45
|
+
self._platform_type = platform_type
|
|
46
|
+
|
|
47
|
+
# Store the console.
|
|
48
|
+
self._console = console
|
|
49
|
+
|
|
50
|
+
# Define bindings based on platform type.
|
|
51
|
+
self._bindings = self._match_platform_type(platform_type)
|
|
52
|
+
|
|
53
|
+
# Record which IDs are inside the brain.
|
|
54
|
+
self._inside_brain: set[str] = set()
|
|
55
|
+
|
|
56
|
+
# Generate a Pinpoint ID for proxy usage.
|
|
57
|
+
self._pinpoint_id = str(uuid4())[:8]
|
|
58
|
+
|
|
59
|
+
def _match_platform_type(self, platform_type: str) -> BaseBindings:
|
|
60
|
+
"""Match the platform type to the appropriate bindings.
|
|
61
|
+
|
|
62
|
+
:param platform_type: Platform type.
|
|
63
|
+
:type platform_type: str
|
|
64
|
+
:returns: Bindings for the specified platform type.
|
|
65
|
+
:rtype: :class:`ephys_link.util.base_bindings.BaseBindings`
|
|
66
|
+
"""
|
|
67
|
+
match platform_type:
|
|
68
|
+
case "ump-4":
|
|
69
|
+
return Ump4Bindings()
|
|
70
|
+
case "fake":
|
|
71
|
+
return FakeBindings()
|
|
72
|
+
case _:
|
|
73
|
+
error_message = f'Platform type "{platform_type}" not recognized.'
|
|
74
|
+
self._console.labeled_error_print("PLATFORM", error_message)
|
|
75
|
+
raise ValueError(error_message)
|
|
76
|
+
|
|
77
|
+
# Ephys Link metadata.
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def get_version() -> str:
|
|
81
|
+
"""Get Ephys Link's version.
|
|
82
|
+
|
|
83
|
+
:returns: Ephys Link's version.
|
|
84
|
+
:rtype: str
|
|
85
|
+
"""
|
|
86
|
+
return __version__
|
|
87
|
+
|
|
88
|
+
def get_pinpoint_id(self) -> PinpointIdResponse:
|
|
89
|
+
"""Get the Pinpoint ID for proxy usage.
|
|
90
|
+
|
|
91
|
+
:returns: Pinpoint ID response.
|
|
92
|
+
:rtype: :class:`vbl_aquarium.models.ephys_link.PinpointIDResponse`
|
|
93
|
+
"""
|
|
94
|
+
return PinpointIdResponse(pinpoint_id=self._pinpoint_id, is_requester=False)
|
|
95
|
+
|
|
96
|
+
def get_platform_type(self) -> str:
|
|
97
|
+
"""Get the manipulator platform type connected to Ephys Link.
|
|
98
|
+
|
|
99
|
+
:returns: Platform type config identifier (see CLI options for examples).
|
|
100
|
+
:rtype: str
|
|
101
|
+
"""
|
|
102
|
+
return self._platform_type
|
|
103
|
+
|
|
104
|
+
# Manipulator commands.
|
|
105
|
+
|
|
106
|
+
async def get_manipulators(self) -> GetManipulatorsResponse:
|
|
107
|
+
"""Get a list of available manipulators on the current handler.
|
|
108
|
+
|
|
109
|
+
:returns: List of manipulator IDs, number of axes, dimensions of manipulators (mm), and an error message if any.
|
|
110
|
+
:rtype: :class:`vbl_aquarium.models.ephys_link.GetManipulatorsResponse`
|
|
111
|
+
"""
|
|
112
|
+
try:
|
|
113
|
+
manipulators = await self._bindings.get_manipulators()
|
|
114
|
+
num_axes = await self._bindings.get_num_axes()
|
|
115
|
+
dimensions = self._bindings.get_dimensions()
|
|
116
|
+
except Exception as e:
|
|
117
|
+
self._console.exception_error_print("Get Manipulators", e)
|
|
118
|
+
return GetManipulatorsResponse(error=self._console.pretty_exception(e))
|
|
119
|
+
else:
|
|
120
|
+
return GetManipulatorsResponse(
|
|
121
|
+
manipulators=manipulators,
|
|
122
|
+
num_axes=num_axes,
|
|
123
|
+
dimensions=dimensions,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
async def get_position(self, manipulator_id: str) -> PositionalResponse:
|
|
127
|
+
"""Get the current translation position of a manipulator in unified coordinates (mm).
|
|
128
|
+
|
|
129
|
+
:param manipulator_id: Manipulator ID.
|
|
130
|
+
:type manipulator_id: str
|
|
131
|
+
:returns: Current position of the manipulator and an error message if any.
|
|
132
|
+
:rtype: :class:`vbl_aquarium.models.ephys_link.PositionalResponse`
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
unified_position = self._bindings.platform_space_to_unified_space(
|
|
136
|
+
await self._bindings.get_position(manipulator_id)
|
|
137
|
+
)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
self._console.exception_error_print("Get Position", e)
|
|
140
|
+
return PositionalResponse(error=str(e))
|
|
141
|
+
else:
|
|
142
|
+
return PositionalResponse(position=unified_position)
|
|
143
|
+
|
|
144
|
+
async def get_angles(self, manipulator_id: str) -> AngularResponse:
|
|
145
|
+
"""Get the current rotation angles of a manipulator in Yaw, Pitch, Roll (degrees).
|
|
146
|
+
|
|
147
|
+
:param manipulator_id: Manipulator ID.
|
|
148
|
+
:type manipulator_id: str
|
|
149
|
+
:returns: Current angles of the manipulator and an error message if any.
|
|
150
|
+
:rtype: :class:`vbl_aquarium.models.ephys_link.AngularResponse`
|
|
151
|
+
"""
|
|
152
|
+
try:
|
|
153
|
+
angles = await self._bindings.get_angles(manipulator_id)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
self._console.exception_error_print("Get Angles", e)
|
|
156
|
+
return AngularResponse(error=self._console.pretty_exception(e))
|
|
157
|
+
else:
|
|
158
|
+
return AngularResponse(angles=angles)
|
|
159
|
+
|
|
160
|
+
async def get_shank_count(self, manipulator_id: str) -> ShankCountResponse:
|
|
161
|
+
"""Get the number of shanks on a manipulator.
|
|
162
|
+
|
|
163
|
+
:param manipulator_id: Manipulator ID.
|
|
164
|
+
:type manipulator_id: str
|
|
165
|
+
:returns: Number of shanks on the manipulator and an error message if any.
|
|
166
|
+
:rtype: :class:`vbl_aquarium.models.ephys_link.ShankCountResponse`
|
|
167
|
+
"""
|
|
168
|
+
try:
|
|
169
|
+
shank_count = await self._bindings.get_shank_count(manipulator_id)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
self._console.exception_error_print("Get Shank Count", e)
|
|
172
|
+
return ShankCountResponse(error=self._console.pretty_exception(e))
|
|
173
|
+
else:
|
|
174
|
+
return ShankCountResponse(shank_count=shank_count)
|
|
175
|
+
|
|
176
|
+
async def set_position(self, request: SetPositionRequest) -> PositionalResponse:
|
|
177
|
+
"""Move a manipulator to a specified translation position in unified coordinates (mm).
|
|
178
|
+
|
|
179
|
+
:param request: Request to move a manipulator to a specified position.
|
|
180
|
+
:type request: :class:`vbl_aquarium.models.ephys_link.GotoPositionRequest`
|
|
181
|
+
:returns: Final position of the manipulator and an error message if any.
|
|
182
|
+
:rtype: :class:`vbl_aquarium.models.ephys_link.Position`
|
|
183
|
+
"""
|
|
184
|
+
try:
|
|
185
|
+
# Disallow setting manipulator position while inside the brain.
|
|
186
|
+
if request.manipulator_id in self._inside_brain:
|
|
187
|
+
error_message = 'Can not move manipulator while inside the brain. Set the depth ("set_depth") instead.'
|
|
188
|
+
self._console.error_print(error_message)
|
|
189
|
+
return PositionalResponse(error=error_message)
|
|
190
|
+
|
|
191
|
+
# Move to the new position.
|
|
192
|
+
final_platform_position = await self._bindings.set_position(
|
|
193
|
+
manipulator_id=request.manipulator_id,
|
|
194
|
+
position=self._bindings.unified_space_to_platform_space(request.position),
|
|
195
|
+
speed=request.speed,
|
|
196
|
+
)
|
|
197
|
+
final_unified_position = self._bindings.platform_space_to_unified_space(final_platform_position)
|
|
198
|
+
|
|
199
|
+
# Return error if movement did not reach target within tolerance.
|
|
200
|
+
for index, axis in enumerate(vector4_to_array(final_unified_position - request.position)):
|
|
201
|
+
# End once index is greater than the number of axes.
|
|
202
|
+
if index >= await self._bindings.get_num_axes():
|
|
203
|
+
break
|
|
204
|
+
|
|
205
|
+
# Check if the axis is within the movement tolerance.
|
|
206
|
+
if abs(axis) > await self._bindings.get_movement_tolerance():
|
|
207
|
+
error_message = (
|
|
208
|
+
f"Manipulator {request.manipulator_id} did not reach target"
|
|
209
|
+
f" position on axis {list(Vector4.model_fields.keys())[index]}."
|
|
210
|
+
f"Requested: {request.position}, got: {final_unified_position}."
|
|
211
|
+
)
|
|
212
|
+
self._console.error_print(error_message)
|
|
213
|
+
return PositionalResponse(error=error_message)
|
|
214
|
+
except Exception as e:
|
|
215
|
+
self._console.exception_error_print("Set Position", e)
|
|
216
|
+
return PositionalResponse(error=self._console.pretty_exception(e))
|
|
217
|
+
else:
|
|
218
|
+
return PositionalResponse(position=final_unified_position)
|
|
219
|
+
|
|
220
|
+
async def set_depth(self, request: SetDepthRequest) -> SetDepthResponse:
|
|
221
|
+
"""Move a manipulator's depth translation stage to a specific value (mm).
|
|
222
|
+
|
|
223
|
+
:param request: Request to move a manipulator to a specified depth.
|
|
224
|
+
:type request: :class:`vbl_aquarium.models.ephys_link.DriveToDepthRequest`
|
|
225
|
+
:returns: Final depth of the manipulator and an error message if any.
|
|
226
|
+
:rtype: :class:`vbl_aquarium.models.ephys_link.DriveToDepthResponse`
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
# Create a position based on the new depth.
|
|
230
|
+
current_platform_position = await self._bindings.get_position(request.manipulator_id)
|
|
231
|
+
current_unified_position = self._bindings.platform_space_to_unified_space(current_platform_position)
|
|
232
|
+
target_unified_position = current_unified_position.model_copy(update={"w": request.depth})
|
|
233
|
+
target_platform_position = self._bindings.unified_space_to_platform_space(target_unified_position)
|
|
234
|
+
|
|
235
|
+
# Move to the new depth.
|
|
236
|
+
final_platform_position = await self._bindings.set_position(
|
|
237
|
+
manipulator_id=request.manipulator_id,
|
|
238
|
+
position=target_platform_position,
|
|
239
|
+
speed=request.speed,
|
|
240
|
+
)
|
|
241
|
+
final_unified_position = self._bindings.platform_space_to_unified_space(final_platform_position)
|
|
242
|
+
except Exception as e:
|
|
243
|
+
self._console.exception_error_print("Set Depth", e)
|
|
244
|
+
return SetDepthResponse(error=self._console.pretty_exception(e))
|
|
245
|
+
else:
|
|
246
|
+
return SetDepthResponse(depth=final_unified_position.w)
|
|
247
|
+
|
|
248
|
+
async def set_inside_brain(self, request: SetInsideBrainRequest) -> BooleanStateResponse:
|
|
249
|
+
"""Mark a manipulator as inside the brain or not.
|
|
250
|
+
|
|
251
|
+
This should restrict the manipulator's movement to just the depth axis.
|
|
252
|
+
|
|
253
|
+
:param request: Request to set a manipulator's inside brain state.
|
|
254
|
+
:type request: :class:`vbl_aquarium.models.ephys_link.InsideBrainRequest`
|
|
255
|
+
:returns: Inside brain state of the manipulator and an error message if any.
|
|
256
|
+
:rtype: :class:`vbl_aquarium.models.ephys_link.BooleanStateResponse`
|
|
257
|
+
"""
|
|
258
|
+
try:
|
|
259
|
+
if request.inside:
|
|
260
|
+
self._inside_brain.add(request.manipulator_id)
|
|
261
|
+
else:
|
|
262
|
+
self._inside_brain.discard(request.manipulator_id)
|
|
263
|
+
except Exception as e:
|
|
264
|
+
self._console.exception_error_print("Set Inside Brain", e)
|
|
265
|
+
return BooleanStateResponse(error=self._console.pretty_exception(e))
|
|
266
|
+
else:
|
|
267
|
+
return BooleanStateResponse(state=request.inside)
|
|
268
|
+
|
|
269
|
+
async def stop(self, manipulator_id: str) -> str:
|
|
270
|
+
"""Stop a manipulator.
|
|
271
|
+
|
|
272
|
+
:param manipulator_id: Manipulator ID.
|
|
273
|
+
:type manipulator_id: str
|
|
274
|
+
:returns: Error message if any.
|
|
275
|
+
:rtype: str
|
|
276
|
+
"""
|
|
277
|
+
try:
|
|
278
|
+
await self._bindings.stop(manipulator_id)
|
|
279
|
+
except Exception as e:
|
|
280
|
+
self._console.exception_error_print("Stop", e)
|
|
281
|
+
return self._console.pretty_exception(e)
|
|
282
|
+
else:
|
|
283
|
+
return ""
|
|
284
|
+
|
|
285
|
+
async def stop_all(self) -> str:
|
|
286
|
+
"""Stop all manipulators.
|
|
287
|
+
|
|
288
|
+
:returns: Error message if any.
|
|
289
|
+
:rtype: str
|
|
290
|
+
"""
|
|
291
|
+
try:
|
|
292
|
+
for manipulator_id in await self._bindings.get_manipulators():
|
|
293
|
+
await self._bindings.stop(manipulator_id)
|
|
294
|
+
except Exception as e:
|
|
295
|
+
self._console.exception_error_print("Stop", e)
|
|
296
|
+
return self._console.pretty_exception(e)
|
|
297
|
+
else:
|
|
298
|
+
return ""
|