ephys-link 2.1.0b0__py3-none-any.whl → 2.1.1__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.
- ephys_link/__about__.py +1 -1
- ephys_link/__main__.py +9 -6
- ephys_link/back_end/platform_handler.py +25 -84
- ephys_link/back_end/server.py +42 -37
- ephys_link/bindings/ump_binding.py +13 -19
- ephys_link/front_end/cli.py +2 -2
- ephys_link/{utils → front_end}/console.py +1 -1
- ephys_link/front_end/gui.py +10 -2
- ephys_link/utils/constants.py +83 -1
- ephys_link/utils/startup.py +46 -9
- {ephys_link-2.1.0b0.dist-info → ephys_link-2.1.1.dist-info}/METADATA +8 -7
- ephys_link-2.1.1.dist-info/RECORD +24 -0
- ephys_link/resources/libum.dll +0 -0
- ephys_link-2.1.0b0.dist-info/RECORD +0 -25
- {ephys_link-2.1.0b0.dist-info → ephys_link-2.1.1.dist-info}/WHEEL +0 -0
- {ephys_link-2.1.0b0.dist-info → ephys_link-2.1.1.dist-info}/entry_points.txt +0 -0
- {ephys_link-2.1.0b0.dist-info → ephys_link-2.1.1.dist-info}/licenses/LICENSE +0 -0
ephys_link/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.1.
|
|
1
|
+
__version__ = "2.1.1"
|
ephys_link/__main__.py
CHANGED
|
@@ -16,9 +16,9 @@ from keyboard import add_hotkey
|
|
|
16
16
|
from ephys_link.back_end.platform_handler import PlatformHandler
|
|
17
17
|
from ephys_link.back_end.server import Server
|
|
18
18
|
from ephys_link.front_end.cli import CLI
|
|
19
|
+
from ephys_link.front_end.console import Console
|
|
19
20
|
from ephys_link.front_end.gui import GUI
|
|
20
|
-
from ephys_link.utils.
|
|
21
|
-
from ephys_link.utils.startup import check_for_updates, preamble
|
|
21
|
+
from ephys_link.utils.startup import check_for_updates, get_binding_instance, preamble
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def main() -> None:
|
|
@@ -37,13 +37,16 @@ def main() -> None:
|
|
|
37
37
|
if not options.ignore_updates:
|
|
38
38
|
check_for_updates(console)
|
|
39
39
|
|
|
40
|
-
# 4. Instantiate the
|
|
41
|
-
|
|
40
|
+
# 4. Instantiate the requested platform binding.
|
|
41
|
+
binding = get_binding_instance(options, console)
|
|
42
42
|
|
|
43
|
-
# 5.
|
|
43
|
+
# 5. Instantiate the Platform Handler with the appropriate platform bindings.
|
|
44
|
+
platform_handler = PlatformHandler(binding, console)
|
|
45
|
+
|
|
46
|
+
# 6. Add hotkeys for emergency stop.
|
|
44
47
|
_ = add_hotkey("ctrl+alt+shift+q", lambda: run(platform_handler.emergency_stop()))
|
|
45
48
|
|
|
46
|
-
#
|
|
49
|
+
# 7. Start the server.
|
|
47
50
|
Server(options, platform_handler, console).launch()
|
|
48
51
|
|
|
49
52
|
|
|
@@ -8,12 +8,10 @@ Usage:
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from typing import final
|
|
11
|
-
from uuid import uuid4
|
|
12
11
|
|
|
13
12
|
from vbl_aquarium.models.ephys_link import (
|
|
14
13
|
AngularResponse,
|
|
15
14
|
BooleanStateResponse,
|
|
16
|
-
EphysLinkOptions,
|
|
17
15
|
GetManipulatorsResponse,
|
|
18
16
|
PlatformInfo,
|
|
19
17
|
PositionalResponse,
|
|
@@ -23,81 +21,38 @@ from vbl_aquarium.models.ephys_link import (
|
|
|
23
21
|
SetPositionRequest,
|
|
24
22
|
ShankCountResponse,
|
|
25
23
|
)
|
|
26
|
-
from vbl_aquarium.models.unity import Vector4
|
|
27
24
|
|
|
28
|
-
from ephys_link.
|
|
25
|
+
from ephys_link.front_end.console import Console
|
|
29
26
|
from ephys_link.utils.base_binding import BaseBinding
|
|
30
|
-
from ephys_link.utils.
|
|
27
|
+
from ephys_link.utils.constants import (
|
|
28
|
+
EMERGENCY_STOP_MESSAGE,
|
|
29
|
+
NO_SET_POSITION_WHILE_INSIDE_BRAIN_ERROR,
|
|
30
|
+
did_not_reach_target_depth_error,
|
|
31
|
+
did_not_reach_target_position_error,
|
|
32
|
+
)
|
|
31
33
|
from ephys_link.utils.converters import vector4_to_array
|
|
32
|
-
from ephys_link.utils.startup import get_bindings
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
@final
|
|
36
37
|
class PlatformHandler:
|
|
37
38
|
"""Handler for platform commands."""
|
|
38
39
|
|
|
39
|
-
def __init__(self,
|
|
40
|
+
def __init__(self, binding: BaseBinding, console: Console) -> None:
|
|
40
41
|
"""Initialize platform handler.
|
|
41
42
|
|
|
42
43
|
Args:
|
|
43
|
-
|
|
44
|
+
binding: Binding instance for the platform.
|
|
44
45
|
console: Console instance.
|
|
45
46
|
"""
|
|
46
|
-
# Store the CLI options.
|
|
47
|
-
self._options = options
|
|
48
|
-
|
|
49
47
|
# Store the console.
|
|
50
48
|
self._console = console
|
|
51
49
|
|
|
52
50
|
# Define bindings based on platform type.
|
|
53
|
-
self._bindings =
|
|
51
|
+
self._bindings = binding
|
|
54
52
|
|
|
55
53
|
# Record which IDs are inside the brain.
|
|
56
54
|
self._inside_brain: set[str] = set()
|
|
57
55
|
|
|
58
|
-
# Generate a Pinpoint ID for proxy usage.
|
|
59
|
-
self._pinpoint_id = str(uuid4())[:8]
|
|
60
|
-
|
|
61
|
-
def _get_binding_instance(self, options: EphysLinkOptions) -> BaseBinding:
|
|
62
|
-
"""Match the platform type to the appropriate bindings.
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
options: CLI options.
|
|
66
|
-
|
|
67
|
-
Raises:
|
|
68
|
-
ValueError: If the platform type is not recognized.
|
|
69
|
-
|
|
70
|
-
Returns:
|
|
71
|
-
Bindings for the specified platform type.
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
# What the user supplied.
|
|
75
|
-
selected_type = options.type
|
|
76
|
-
|
|
77
|
-
for binding_type in get_bindings():
|
|
78
|
-
binding_cli_name = binding_type.get_cli_name()
|
|
79
|
-
|
|
80
|
-
# Notify deprecation of "ump-4" and "ump-3" CLI options and fix.
|
|
81
|
-
if selected_type in ("ump-4", "ump-3"):
|
|
82
|
-
self._console.error_print(
|
|
83
|
-
"DEPRECATION",
|
|
84
|
-
f"CLI option '{selected_type}' is deprecated and will be removed in v3.0.0. Use 'ump' instead.",
|
|
85
|
-
)
|
|
86
|
-
selected_type = "ump"
|
|
87
|
-
|
|
88
|
-
if binding_cli_name == selected_type:
|
|
89
|
-
# Pass in HTTP port for Pathfinder MPM.
|
|
90
|
-
if binding_cli_name == "pathfinder-mpm":
|
|
91
|
-
return MPMBinding(options.mpm_port)
|
|
92
|
-
|
|
93
|
-
# Otherwise just return the binding.
|
|
94
|
-
return binding_type()
|
|
95
|
-
|
|
96
|
-
# Raise an error if the platform type is not recognized.
|
|
97
|
-
error_message = f'Platform type "{options.type}" not recognized.'
|
|
98
|
-
self._console.critical_print(error_message)
|
|
99
|
-
raise ValueError(error_message)
|
|
100
|
-
|
|
101
56
|
# Platform metadata.
|
|
102
57
|
|
|
103
58
|
def get_display_name(self) -> str:
|
|
@@ -152,7 +107,7 @@ class PlatformHandler:
|
|
|
152
107
|
)
|
|
153
108
|
except Exception as e: # noqa: BLE001
|
|
154
109
|
self._console.exception_error_print("Get Position", e)
|
|
155
|
-
return PositionalResponse(error=
|
|
110
|
+
return PositionalResponse(error=self._console.pretty_exception(e))
|
|
156
111
|
else:
|
|
157
112
|
return PositionalResponse(position=unified_position)
|
|
158
113
|
|
|
@@ -202,9 +157,8 @@ class PlatformHandler:
|
|
|
202
157
|
try:
|
|
203
158
|
# Disallow setting manipulator position while inside the brain.
|
|
204
159
|
if request.manipulator_id in self._inside_brain:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return PositionalResponse(error=error_message)
|
|
160
|
+
self._console.error_print("Set Position", NO_SET_POSITION_WHILE_INSIDE_BRAIN_ERROR)
|
|
161
|
+
return PositionalResponse(error=NO_SET_POSITION_WHILE_INSIDE_BRAIN_ERROR)
|
|
208
162
|
|
|
209
163
|
# Move to the new position.
|
|
210
164
|
final_platform_position = await self._bindings.set_position(
|
|
@@ -222,11 +176,7 @@ class PlatformHandler:
|
|
|
222
176
|
|
|
223
177
|
# Check if the axis is within the movement tolerance.
|
|
224
178
|
if abs(axis) > self._bindings.get_movement_tolerance():
|
|
225
|
-
error_message = (
|
|
226
|
-
f"Manipulator {request.manipulator_id} did not reach target"
|
|
227
|
-
f" position on axis {list(Vector4.model_fields.keys())[index]}."
|
|
228
|
-
f" Requested: {request.position}, got: {final_unified_position}."
|
|
229
|
-
)
|
|
179
|
+
error_message = did_not_reach_target_position_error(request, index, final_unified_position)
|
|
230
180
|
self._console.error_print("Set Position", error_message)
|
|
231
181
|
return PositionalResponse(error=error_message)
|
|
232
182
|
except Exception as e: # noqa: BLE001
|
|
@@ -246,26 +196,22 @@ class PlatformHandler:
|
|
|
246
196
|
"""
|
|
247
197
|
try:
|
|
248
198
|
# Move to the new depth.
|
|
249
|
-
|
|
199
|
+
final_depth = await self._bindings.set_depth(
|
|
250
200
|
manipulator_id=request.manipulator_id,
|
|
251
|
-
depth=
|
|
201
|
+
depth=request.depth,
|
|
252
202
|
speed=request.speed,
|
|
253
203
|
)
|
|
254
|
-
final_unified_depth = self._bindings.platform_space_to_unified_space(Vector4(w=final_platform_depth)).w
|
|
255
204
|
|
|
256
205
|
# Return error if movement did not reach target within tolerance.
|
|
257
|
-
if abs(
|
|
258
|
-
error_message = (
|
|
259
|
-
f"Manipulator {request.manipulator_id} did not reach target depth."
|
|
260
|
-
f" Requested: {request.depth}, got: {final_unified_depth}."
|
|
261
|
-
)
|
|
206
|
+
if abs(final_depth - request.depth) > self._bindings.get_movement_tolerance():
|
|
207
|
+
error_message = did_not_reach_target_depth_error(request, final_depth)
|
|
262
208
|
self._console.error_print("Set Depth", error_message)
|
|
263
209
|
return SetDepthResponse(error=error_message)
|
|
264
210
|
except Exception as e: # noqa: BLE001
|
|
265
211
|
self._console.exception_error_print("Set Depth", e)
|
|
266
212
|
return SetDepthResponse(error=self._console.pretty_exception(e))
|
|
267
213
|
else:
|
|
268
|
-
return SetDepthResponse(depth=
|
|
214
|
+
return SetDepthResponse(depth=final_depth)
|
|
269
215
|
|
|
270
216
|
async def set_inside_brain(self, request: SetInsideBrainRequest) -> BooleanStateResponse:
|
|
271
217
|
"""Mark a manipulator as inside the brain or not.
|
|
@@ -278,16 +224,11 @@ class PlatformHandler:
|
|
|
278
224
|
Returns:
|
|
279
225
|
Inside brain state of the manipulator and an error message if any.
|
|
280
226
|
"""
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
self._inside_brain.add(request.manipulator_id)
|
|
284
|
-
else:
|
|
285
|
-
self._inside_brain.discard(request.manipulator_id)
|
|
286
|
-
except Exception as e: # noqa: BLE001
|
|
287
|
-
self._console.exception_error_print("Set Inside Brain", e)
|
|
288
|
-
return BooleanStateResponse(error=self._console.pretty_exception(e))
|
|
227
|
+
if request.inside:
|
|
228
|
+
self._inside_brain.add(request.manipulator_id)
|
|
289
229
|
else:
|
|
290
|
-
|
|
230
|
+
self._inside_brain.discard(request.manipulator_id)
|
|
231
|
+
return BooleanStateResponse(state=request.inside)
|
|
291
232
|
|
|
292
233
|
async def stop(self, manipulator_id: str) -> str:
|
|
293
234
|
"""Stop a manipulator.
|
|
@@ -316,12 +257,12 @@ class PlatformHandler:
|
|
|
316
257
|
for manipulator_id in await self._bindings.get_manipulators():
|
|
317
258
|
await self._bindings.stop(manipulator_id)
|
|
318
259
|
except Exception as e: # noqa: BLE001
|
|
319
|
-
self._console.exception_error_print("Stop", e)
|
|
260
|
+
self._console.exception_error_print("Stop All", e)
|
|
320
261
|
return self._console.pretty_exception(e)
|
|
321
262
|
else:
|
|
322
263
|
return ""
|
|
323
264
|
|
|
324
265
|
async def emergency_stop(self) -> None:
|
|
325
266
|
"""Stops all manipulators with a message."""
|
|
326
|
-
self._console.critical_print(
|
|
267
|
+
self._console.critical_print(EMERGENCY_STOP_MESSAGE)
|
|
327
268
|
_ = await self.stop_all()
|
ephys_link/back_end/server.py
CHANGED
|
@@ -32,8 +32,16 @@ from vbl_aquarium.utils.vbl_base_model import VBLBaseModel
|
|
|
32
32
|
|
|
33
33
|
from ephys_link.__about__ import __version__
|
|
34
34
|
from ephys_link.back_end.platform_handler import PlatformHandler
|
|
35
|
-
from ephys_link.
|
|
36
|
-
from ephys_link.utils.constants import
|
|
35
|
+
from ephys_link.front_end.console import Console
|
|
36
|
+
from ephys_link.utils.constants import (
|
|
37
|
+
MALFORMED_REQUEST_ERROR,
|
|
38
|
+
PORT,
|
|
39
|
+
PROXY_CLIENT_NOT_INITIALIZED_ERROR,
|
|
40
|
+
SERVER_NOT_INITIALIZED_ERROR,
|
|
41
|
+
UNKNOWN_EVENT_ERROR,
|
|
42
|
+
cannot_connect_as_client_is_already_connected_error,
|
|
43
|
+
client_disconnected_without_being_connected_error,
|
|
44
|
+
)
|
|
37
45
|
|
|
38
46
|
# Server message generic types.
|
|
39
47
|
INPUT_TYPE = TypeVar("INPUT_TYPE", bound=VBLBaseModel)
|
|
@@ -61,9 +69,8 @@ class Server:
|
|
|
61
69
|
if not self._options.use_proxy:
|
|
62
70
|
# Exit if _sio is not a Server.
|
|
63
71
|
if not isinstance(self._sio, AsyncServer):
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
raise TypeError(error)
|
|
72
|
+
self._console.critical_print(SERVER_NOT_INITIALIZED_ERROR)
|
|
73
|
+
raise TypeError(SERVER_NOT_INITIALIZED_ERROR)
|
|
67
74
|
|
|
68
75
|
self._app = Application()
|
|
69
76
|
self._sio.attach(self._app) # pyright: ignore [reportUnknownMemberType]
|
|
@@ -101,9 +108,8 @@ class Server:
|
|
|
101
108
|
async def connect_proxy() -> None:
|
|
102
109
|
# Exit if _sio is not a proxy client.
|
|
103
110
|
if not isinstance(self._sio, AsyncClient):
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
raise TypeError(error)
|
|
111
|
+
self._console.critical_print(PROXY_CLIENT_NOT_INITIALIZED_ERROR)
|
|
112
|
+
raise TypeError(PROXY_CLIENT_NOT_INITIALIZED_ERROR)
|
|
107
113
|
|
|
108
114
|
# noinspection HttpUrlsUsage
|
|
109
115
|
await self._sio.connect(f"http://{self._options.proxy_address}:{PORT}") # pyright: ignore [reportUnknownMemberType]
|
|
@@ -125,13 +131,13 @@ class Server:
|
|
|
125
131
|
Response for a malformed request.
|
|
126
132
|
"""
|
|
127
133
|
self._console.error_print("MALFORMED REQUEST", f"{request}: {data}")
|
|
128
|
-
return dumps(
|
|
134
|
+
return dumps(MALFORMED_REQUEST_ERROR)
|
|
129
135
|
|
|
130
136
|
async def _run_if_data_available(
|
|
131
137
|
self,
|
|
132
138
|
function: Callable[[str], Coroutine[Any, Any, VBLBaseModel]], # pyright: ignore [reportExplicitAny]
|
|
133
139
|
event: str,
|
|
134
|
-
data:
|
|
140
|
+
data: Any, # pyright: ignore [reportAny, reportExplicitAny]
|
|
135
141
|
) -> str:
|
|
136
142
|
"""Run a function if data is available.
|
|
137
143
|
|
|
@@ -143,17 +149,16 @@ class Server:
|
|
|
143
149
|
Returns:
|
|
144
150
|
Response data from function.
|
|
145
151
|
"""
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return self._malformed_request_response(event, request_data)
|
|
152
|
+
if data:
|
|
153
|
+
return str((await function(str(data))).to_json_string()) # pyright: ignore[reportAny]
|
|
154
|
+
return self._malformed_request_response(event, data) # pyright: ignore[reportAny]
|
|
150
155
|
|
|
151
156
|
async def _run_if_data_parses(
|
|
152
157
|
self,
|
|
153
158
|
function: Callable[[INPUT_TYPE], Coroutine[Any, Any, OUTPUT_TYPE]], # pyright: ignore [reportExplicitAny]
|
|
154
159
|
data_type: type[INPUT_TYPE],
|
|
155
160
|
event: str,
|
|
156
|
-
data:
|
|
161
|
+
data: Any, # pyright: ignore [reportAny, reportExplicitAny]
|
|
157
162
|
) -> str:
|
|
158
163
|
"""Run a function if data parses.
|
|
159
164
|
|
|
@@ -166,18 +171,17 @@ class Server:
|
|
|
166
171
|
Returns:
|
|
167
172
|
Response data from function.
|
|
168
173
|
"""
|
|
169
|
-
|
|
170
|
-
if request_data:
|
|
174
|
+
if data:
|
|
171
175
|
try:
|
|
172
|
-
parsed_data = data_type(**loads(str(
|
|
176
|
+
parsed_data = data_type(**loads(str(data))) # pyright: ignore[reportAny]
|
|
173
177
|
except JSONDecodeError:
|
|
174
|
-
return self._malformed_request_response(event,
|
|
178
|
+
return self._malformed_request_response(event, data) # pyright: ignore[reportAny]
|
|
175
179
|
except ValidationError as e:
|
|
176
180
|
self._console.exception_error_print(event, e)
|
|
177
|
-
return self._malformed_request_response(event,
|
|
181
|
+
return self._malformed_request_response(event, data) # pyright: ignore[reportAny]
|
|
178
182
|
else:
|
|
179
183
|
return str((await function(parsed_data)).to_json_string())
|
|
180
|
-
return self._malformed_request_response(event,
|
|
184
|
+
return self._malformed_request_response(event, data) # pyright: ignore[reportAny]
|
|
181
185
|
|
|
182
186
|
# Event Handlers.
|
|
183
187
|
|
|
@@ -199,7 +203,7 @@ class Server:
|
|
|
199
203
|
return True
|
|
200
204
|
|
|
201
205
|
self._console.error_print(
|
|
202
|
-
"CONNECTION REFUSED",
|
|
206
|
+
"CONNECTION REFUSED", cannot_connect_as_client_is_already_connected_error(sid, self._client_sid)
|
|
203
207
|
)
|
|
204
208
|
return False
|
|
205
209
|
|
|
@@ -209,22 +213,24 @@ class Server:
|
|
|
209
213
|
Args:
|
|
210
214
|
sid: Socket session ID.
|
|
211
215
|
"""
|
|
212
|
-
self._console.info_print("
|
|
216
|
+
self._console.info_print("DISCONNECTION REQUEST", sid)
|
|
213
217
|
|
|
214
218
|
# Reset client SID if it matches.
|
|
215
219
|
if self._client_sid == sid:
|
|
216
220
|
self._client_sid = ""
|
|
221
|
+
self._console.info_print("DISCONNECTED", sid)
|
|
217
222
|
else:
|
|
218
|
-
self._console.error_print("DISCONNECTION",
|
|
223
|
+
self._console.error_print("DISCONNECTION", client_disconnected_without_being_connected_error(sid))
|
|
219
224
|
|
|
220
|
-
async def platform_event_handler(self, event: str,
|
|
225
|
+
async def platform_event_handler(self, event: str, _: str, data: Any) -> str: # pyright: ignore [reportAny, reportExplicitAny]
|
|
221
226
|
"""Handle events from the server.
|
|
222
227
|
|
|
223
228
|
Matches incoming events based on the Socket.IO API.
|
|
224
229
|
|
|
225
230
|
Args:
|
|
226
231
|
event: Event name.
|
|
227
|
-
|
|
232
|
+
_: Socket session ID (unused).
|
|
233
|
+
data: Event data.
|
|
228
234
|
|
|
229
235
|
Returns:
|
|
230
236
|
Response data.
|
|
@@ -247,28 +253,27 @@ class Server:
|
|
|
247
253
|
case "get_manipulators":
|
|
248
254
|
return str((await self._platform_handler.get_manipulators()).to_json_string())
|
|
249
255
|
case "get_position":
|
|
250
|
-
return await self._run_if_data_available(self._platform_handler.get_position, event,
|
|
256
|
+
return await self._run_if_data_available(self._platform_handler.get_position, event, data)
|
|
251
257
|
case "get_angles":
|
|
252
|
-
return await self._run_if_data_available(self._platform_handler.get_angles, event,
|
|
258
|
+
return await self._run_if_data_available(self._platform_handler.get_angles, event, data)
|
|
253
259
|
case "get_shank_count":
|
|
254
|
-
return await self._run_if_data_available(self._platform_handler.get_shank_count, event,
|
|
260
|
+
return await self._run_if_data_available(self._platform_handler.get_shank_count, event, data)
|
|
255
261
|
case "set_position":
|
|
256
262
|
return await self._run_if_data_parses(
|
|
257
|
-
self._platform_handler.set_position, SetPositionRequest, event,
|
|
263
|
+
self._platform_handler.set_position, SetPositionRequest, event, data
|
|
258
264
|
)
|
|
259
265
|
case "set_depth":
|
|
260
|
-
return await self._run_if_data_parses(self._platform_handler.set_depth, SetDepthRequest, event,
|
|
266
|
+
return await self._run_if_data_parses(self._platform_handler.set_depth, SetDepthRequest, event, data)
|
|
261
267
|
case "set_inside_brain":
|
|
262
268
|
return await self._run_if_data_parses(
|
|
263
|
-
self._platform_handler.set_inside_brain, SetInsideBrainRequest, event,
|
|
269
|
+
self._platform_handler.set_inside_brain, SetInsideBrainRequest, event, data
|
|
264
270
|
)
|
|
265
271
|
case "stop":
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
return self._malformed_request_response(event, request_data)
|
|
272
|
+
if data:
|
|
273
|
+
return await self._platform_handler.stop(str(data)) # pyright: ignore[reportAny]
|
|
274
|
+
return self._malformed_request_response(event, data) # pyright: ignore[reportAny]
|
|
270
275
|
case "stop_all":
|
|
271
276
|
return await self._platform_handler.stop_all()
|
|
272
277
|
case _:
|
|
273
278
|
self._console.error_print("EVENT", f"Unknown event: {event}.")
|
|
274
|
-
return dumps(
|
|
279
|
+
return dumps(UNKNOWN_EVENT_ERROR)
|
|
@@ -10,7 +10,6 @@ from sensapex import UMP, SensapexDevice # pyright: ignore [reportMissingTypeSt
|
|
|
10
10
|
from vbl_aquarium.models.unity import Vector4
|
|
11
11
|
|
|
12
12
|
from ephys_link.utils.base_binding import BaseBinding
|
|
13
|
-
from ephys_link.utils.constants import RESOURCES_DIRECTORY
|
|
14
13
|
from ephys_link.utils.converters import (
|
|
15
14
|
list_to_vector4,
|
|
16
15
|
scalar_mm_to_um,
|
|
@@ -31,23 +30,11 @@ class UmpBinding(BaseBinding):
|
|
|
31
30
|
"""Initialize uMp bindings."""
|
|
32
31
|
|
|
33
32
|
# Establish connection to Sensapex API (exit if connection fails).
|
|
34
|
-
UMP.set_library_path(RESOURCES_DIRECTORY)
|
|
35
33
|
self._ump: UMP = UMP.get_ump() # pyright: ignore [reportUnknownMemberType]
|
|
36
34
|
|
|
37
|
-
#
|
|
38
|
-
device_ids
|
|
39
|
-
if len(device_ids) == 0
|
|
40
|
-
msg = "No manipulators connected."
|
|
41
|
-
raise RuntimeError(msg)
|
|
42
|
-
|
|
43
|
-
# Currently only supports using uMp-4 XOR uMp-3. Exit if both are connected.
|
|
44
|
-
|
|
45
|
-
# Use the first device as the reference for the number of axes.
|
|
46
|
-
self.num_axes: int = self._get_device(device_ids[0]).n_axes()
|
|
47
|
-
|
|
48
|
-
if any(self._get_device(device_id).n_axes() != self.num_axes for device_id in device_ids): # pyright: ignore [reportUnknownArgumentType, reportUnknownMemberType]
|
|
49
|
-
msg = "uMp-4 and uMp-3 cannot be used at the same time."
|
|
50
|
-
raise RuntimeError(msg)
|
|
35
|
+
# Compute axis count, assumed as the first device. 0 if no devices are connected.
|
|
36
|
+
device_ids = list(map(str, self._ump.list_devices()))
|
|
37
|
+
self.axis_count: int = 0 if len(device_ids) == 0 else self._get_device(device_ids[0]).n_axes()
|
|
51
38
|
|
|
52
39
|
@staticmethod
|
|
53
40
|
@override
|
|
@@ -61,11 +48,18 @@ class UmpBinding(BaseBinding):
|
|
|
61
48
|
|
|
62
49
|
@override
|
|
63
50
|
async def get_manipulators(self) -> list[str]:
|
|
64
|
-
|
|
51
|
+
device_ids = list(map(str, self._ump.list_devices()))
|
|
52
|
+
|
|
53
|
+
# Currently only supports using uMp-4 XOR uMp-3. Throw error if both are connected.
|
|
54
|
+
if any(self._get_device(device_id).n_axes() != self.axis_count for device_id in device_ids): # pyright: ignore [reportUnknownArgumentType]
|
|
55
|
+
msg = "uMp-4 and uMp-3 cannot be used at the same time."
|
|
56
|
+
raise RuntimeError(msg)
|
|
57
|
+
|
|
58
|
+
return device_ids
|
|
65
59
|
|
|
66
60
|
@override
|
|
67
61
|
async def get_axes_count(self) -> int:
|
|
68
|
-
return self.
|
|
62
|
+
return self.axis_count
|
|
69
63
|
|
|
70
64
|
@override
|
|
71
65
|
def get_dimensions(self) -> Vector4:
|
|
@@ -236,4 +230,4 @@ class UmpBinding(BaseBinding):
|
|
|
236
230
|
Returns:
|
|
237
231
|
True if the device is uMp-3, False otherwise.
|
|
238
232
|
"""
|
|
239
|
-
return self.
|
|
233
|
+
return self.axis_count == self.UMP_3_NUM_AXES
|
ephys_link/front_end/cli.py
CHANGED
|
@@ -46,8 +46,8 @@ class CLI:
|
|
|
46
46
|
"--type",
|
|
47
47
|
type=str,
|
|
48
48
|
dest="type",
|
|
49
|
-
default="ump
|
|
50
|
-
help='Manipulator type (i.e. "ump
|
|
49
|
+
default="ump",
|
|
50
|
+
help='Manipulator type (i.e. "ump", "pathfinder-mpm", "fake"). Default: "ump".',
|
|
51
51
|
)
|
|
52
52
|
_ = self._parser.add_argument(
|
|
53
53
|
"-d",
|
ephys_link/front_end/gui.py
CHANGED
|
@@ -20,7 +20,7 @@ from platformdirs import user_config_dir
|
|
|
20
20
|
from vbl_aquarium.models.ephys_link import EphysLinkOptions
|
|
21
21
|
|
|
22
22
|
from ephys_link.__about__ import __version__ as version
|
|
23
|
-
from ephys_link.utils.startup import
|
|
23
|
+
from ephys_link.utils.startup import get_bindings
|
|
24
24
|
|
|
25
25
|
# Define options path.
|
|
26
26
|
OPTIONS_DIR = join(user_config_dir(), "VBL", "Ephys Link")
|
|
@@ -156,7 +156,7 @@ class GUI:
|
|
|
156
156
|
platform_type_settings = ttk.LabelFrame(mainframe, text="Platform Type", padding=3)
|
|
157
157
|
platform_type_settings.grid(column=0, row=1, sticky="news")
|
|
158
158
|
|
|
159
|
-
for index, (display_name, cli_name) in enumerate(
|
|
159
|
+
for index, (display_name, cli_name) in enumerate(self._get_binding_display_to_cli_name().items()):
|
|
160
160
|
ttk.Radiobutton(
|
|
161
161
|
platform_type_settings,
|
|
162
162
|
text=display_name,
|
|
@@ -202,3 +202,11 @@ class GUI:
|
|
|
202
202
|
"""
|
|
203
203
|
self._submit = True
|
|
204
204
|
self._root.destroy()
|
|
205
|
+
|
|
206
|
+
def _get_binding_display_to_cli_name(self) -> dict[str, str]:
|
|
207
|
+
"""Get mapping of display to CLI option names of the available platform bindings.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Dictionary of platform binding display name to CLI option name.
|
|
211
|
+
"""
|
|
212
|
+
return {binding_type.get_display_name(): binding_type.get_cli_name() for binding_type in get_bindings()}
|
ephys_link/utils/constants.py
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from os.path import abspath, dirname, join
|
|
4
4
|
|
|
5
|
+
from vbl_aquarium.models.ephys_link import SetDepthRequest, SetPositionRequest
|
|
6
|
+
from vbl_aquarium.models.unity import Vector4
|
|
7
|
+
|
|
5
8
|
# Ephys Link ASCII.
|
|
6
9
|
ASCII = r"""
|
|
7
10
|
______ _ _ _ _
|
|
@@ -16,8 +19,87 @@ ASCII = r"""
|
|
|
16
19
|
|
|
17
20
|
# Absolute path to the resource folder.
|
|
18
21
|
PACKAGE_DIRECTORY = dirname(dirname(abspath(__file__)))
|
|
19
|
-
RESOURCES_DIRECTORY = join(PACKAGE_DIRECTORY, "resources")
|
|
20
22
|
BINDINGS_DIRECTORY = join(PACKAGE_DIRECTORY, "bindings")
|
|
21
23
|
|
|
22
24
|
# Ephys Link Port
|
|
23
25
|
PORT = 3000
|
|
26
|
+
|
|
27
|
+
# Error messages
|
|
28
|
+
|
|
29
|
+
NO_SET_POSITION_WHILE_INSIDE_BRAIN_ERROR = (
|
|
30
|
+
'Cannot move manipulator while inside the brain. Set the depth ("set_depth") instead.'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def did_not_reach_target_position_error(
|
|
35
|
+
request: SetPositionRequest, axis_index: int, final_unified_position: Vector4
|
|
36
|
+
) -> str:
|
|
37
|
+
"""Generate an error message for when the manipulator did not reach the target position.
|
|
38
|
+
Args:
|
|
39
|
+
request: The object containing the requested position.
|
|
40
|
+
axis_index: The index of the axis that did not reach the target position.
|
|
41
|
+
final_unified_position: The final position of the manipulator.
|
|
42
|
+
Returns:
|
|
43
|
+
str: The error message.
|
|
44
|
+
"""
|
|
45
|
+
return f"Manipulator {request.manipulator_id} did not reach target position on axis {list(Vector4.model_fields.keys())[axis_index]}. Requested: {request.position}, got: {final_unified_position}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def did_not_reach_target_depth_error(request: SetDepthRequest, final_unified_depth: float) -> str:
|
|
49
|
+
"""Generate an error message for when the manipulator did not reach the target position.
|
|
50
|
+
Args:
|
|
51
|
+
request: The object containing the requested depth.
|
|
52
|
+
final_unified_depth: The final depth of the manipulator.
|
|
53
|
+
Returns:
|
|
54
|
+
str: The error message.
|
|
55
|
+
"""
|
|
56
|
+
return f"Manipulator {request.manipulator_id} did not reach target depth. Requested: {request.depth}, got: {final_unified_depth}"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
EMERGENCY_STOP_MESSAGE = "Emergency Stopping All Manipulators..."
|
|
60
|
+
|
|
61
|
+
SERVER_NOT_INITIALIZED_ERROR = "Server not initialized."
|
|
62
|
+
PROXY_CLIENT_NOT_INITIALIZED_ERROR = "Proxy client not initialized."
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def cannot_connect_as_client_is_already_connected_error(new_client_sid: str, current_client_sid: str) -> str:
|
|
66
|
+
"""Generate an error message for when the client is already connected.
|
|
67
|
+
Args:
|
|
68
|
+
new_client_sid: The SID of the new client.
|
|
69
|
+
current_client_sid: The SID of the current client.
|
|
70
|
+
Returns:
|
|
71
|
+
str: The error message.
|
|
72
|
+
"""
|
|
73
|
+
return f"Cannot connect {new_client_sid} as {current_client_sid} is already connected."
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def client_disconnected_without_being_connected_error(client_sid: str) -> str:
|
|
77
|
+
"""Generate an error message for when the client is disconnected without being connected.
|
|
78
|
+
Args:
|
|
79
|
+
client_sid: The SID of the client.
|
|
80
|
+
Returns:
|
|
81
|
+
str: The error message.
|
|
82
|
+
"""
|
|
83
|
+
return f"Client {client_sid} disconnected without being connected."
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
MALFORMED_REQUEST_ERROR = {"error": "Malformed request."}
|
|
87
|
+
UNKNOWN_EVENT_ERROR = {"error": "Unknown event."}
|
|
88
|
+
|
|
89
|
+
UNABLE_TO_CHECK_FOR_UPDATES_ERROR = (
|
|
90
|
+
"Unable to check for updates. Ignore updates or use the -i flag to disable checks.\n"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def ump_4_3_deprecation_error(cli_name: str):
|
|
95
|
+
return f"CLI option '{cli_name}' is deprecated and will be removed in v3.0.0. Use 'ump' instead."
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def unrecognized_platform_type_error(cli_name: str) -> str:
|
|
99
|
+
"""Generate an error message for when the platform type is not recognized.
|
|
100
|
+
Args:
|
|
101
|
+
cli_name: The platform type that is not recognized.
|
|
102
|
+
Returns:
|
|
103
|
+
str: The error message.
|
|
104
|
+
"""
|
|
105
|
+
return f'Platform type "{cli_name}" not recognized.'
|
ephys_link/utils/startup.py
CHANGED
|
@@ -6,11 +6,19 @@ from pkgutil import iter_modules
|
|
|
6
6
|
|
|
7
7
|
from packaging.version import parse
|
|
8
8
|
from requests import ConnectionError, ConnectTimeout, get
|
|
9
|
+
from vbl_aquarium.models.ephys_link import EphysLinkOptions
|
|
9
10
|
|
|
10
11
|
from ephys_link.__about__ import __version__
|
|
12
|
+
from ephys_link.bindings.mpm_binding import MPMBinding
|
|
13
|
+
from ephys_link.front_end.console import Console
|
|
11
14
|
from ephys_link.utils.base_binding import BaseBinding
|
|
12
|
-
from ephys_link.utils.
|
|
13
|
-
|
|
15
|
+
from ephys_link.utils.constants import (
|
|
16
|
+
ASCII,
|
|
17
|
+
BINDINGS_DIRECTORY,
|
|
18
|
+
UNABLE_TO_CHECK_FOR_UPDATES_ERROR,
|
|
19
|
+
ump_4_3_deprecation_error,
|
|
20
|
+
unrecognized_platform_type_error,
|
|
21
|
+
)
|
|
14
22
|
|
|
15
23
|
|
|
16
24
|
def preamble() -> None:
|
|
@@ -37,9 +45,7 @@ def check_for_updates(console: Console) -> None:
|
|
|
37
45
|
console.critical_print(f"Update available: {latest_version} (current: {__version__})")
|
|
38
46
|
console.critical_print("Download at: https://github.com/VirtualBrainLab/ephys-link/releases/latest")
|
|
39
47
|
except (ConnectionError, ConnectTimeout):
|
|
40
|
-
console.error_print(
|
|
41
|
-
"UPDATE", "Unable to check for updates. Ignore updates or use the the -i flag to disable checks.\n"
|
|
42
|
-
)
|
|
48
|
+
console.error_print("UPDATE", UNABLE_TO_CHECK_FOR_UPDATES_ERROR)
|
|
43
49
|
|
|
44
50
|
|
|
45
51
|
def get_bindings() -> list[type[BaseBinding]]:
|
|
@@ -56,10 +62,41 @@ def get_bindings() -> list[type[BaseBinding]]:
|
|
|
56
62
|
]
|
|
57
63
|
|
|
58
64
|
|
|
59
|
-
def
|
|
60
|
-
"""Get
|
|
65
|
+
def get_binding_instance(options: EphysLinkOptions, console: Console) -> BaseBinding:
|
|
66
|
+
"""Get an instance of the requested binding class.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
options: Ephys Link options.
|
|
70
|
+
console: Console instance for printing messages.
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
ValueError: If the platform type is not recognized.
|
|
61
74
|
|
|
62
75
|
Returns:
|
|
63
|
-
|
|
76
|
+
Instance of a platform binding class.
|
|
64
77
|
"""
|
|
65
|
-
|
|
78
|
+
selected_type = options.type
|
|
79
|
+
|
|
80
|
+
for binding_type in get_bindings():
|
|
81
|
+
binding_cli_name = binding_type.get_cli_name()
|
|
82
|
+
|
|
83
|
+
# Notify deprecation of "ump-4" and "ump-3" CLI options and fix.
|
|
84
|
+
if selected_type in ("ump-4", "ump-3"):
|
|
85
|
+
console.error_print(
|
|
86
|
+
"DEPRECATION",
|
|
87
|
+
ump_4_3_deprecation_error(selected_type),
|
|
88
|
+
)
|
|
89
|
+
selected_type = "ump"
|
|
90
|
+
|
|
91
|
+
if binding_cli_name == selected_type:
|
|
92
|
+
# Pass in HTTP port for Pathfinder MPM.
|
|
93
|
+
if binding_cli_name == "pathfinder-mpm":
|
|
94
|
+
return MPMBinding(options.mpm_port)
|
|
95
|
+
|
|
96
|
+
# Otherwise just return the binding.
|
|
97
|
+
return binding_type()
|
|
98
|
+
|
|
99
|
+
# Raise an error if the platform type is not recognized.
|
|
100
|
+
error_message = unrecognized_platform_type_error(selected_type)
|
|
101
|
+
console.critical_print(error_message)
|
|
102
|
+
raise ValueError(error_message)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ephys-link
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.1
|
|
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
|
|
@@ -22,16 +22,16 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
|
22
22
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
23
23
|
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
24
24
|
Requires-Python: >=3.13
|
|
25
|
-
Requires-Dist: aiohttp==3.
|
|
25
|
+
Requires-Dist: aiohttp==3.12.15
|
|
26
26
|
Requires-Dist: colorama==0.4.6
|
|
27
27
|
Requires-Dist: keyboard==0.13.5
|
|
28
|
-
Requires-Dist: packaging==
|
|
29
|
-
Requires-Dist: platformdirs==4.
|
|
28
|
+
Requires-Dist: packaging==25.0
|
|
29
|
+
Requires-Dist: platformdirs==4.4.0
|
|
30
30
|
Requires-Dist: pyserial==3.5
|
|
31
31
|
Requires-Dist: python-socketio[asyncio-client]==5.13.0
|
|
32
|
-
Requires-Dist: requests==2.32.
|
|
33
|
-
Requires-Dist: rich==14.
|
|
34
|
-
Requires-Dist: sensapex==1.400.
|
|
32
|
+
Requires-Dist: requests==2.32.5
|
|
33
|
+
Requires-Dist: rich==14.1.0
|
|
34
|
+
Requires-Dist: sensapex==1.400.4
|
|
35
35
|
Requires-Dist: vbl-aquarium==1.0.0
|
|
36
36
|
Description-Content-Type: text/markdown
|
|
37
37
|
|
|
@@ -43,6 +43,7 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
[](https://github.com/astral-sh/ruff)
|
|
44
44
|
[](https://pydantic.dev)
|
|
45
45
|
[](https://microsoft.github.io/pyright/)
|
|
46
|
+
[](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/test.yml)
|
|
46
47
|
|
|
47
48
|
<!-- [](https://github.com/VirtualBrainLab/ephys-link/actions/workflows/build.yml) -->
|
|
48
49
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
ephys_link/__about__.py,sha256=zPJIgPGcoSNiD0qme18OnYJYE3A9VVytlhO-V5DaAW0,22
|
|
2
|
+
ephys_link/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
ephys_link/__main__.py,sha256=54zxQ-fyxC2-LGsTUdtlvib36ZZQwNyOa6IuffYLhhs,1582
|
|
4
|
+
ephys_link/back_end/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
ephys_link/back_end/platform_handler.py,sha256=6Lt_jaIcO9jTmWYOoWYkuhwSlCTT1Qfd53ru31X0fmc,10130
|
|
6
|
+
ephys_link/back_end/server.py,sha256=uZOK9UQq4gp7yx6_4dZrfRTjsio2WXT8TwjO4E5kECA,10712
|
|
7
|
+
ephys_link/bindings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
ephys_link/bindings/fake_binding.py,sha256=PI7zYv-SjWsGFEs_FVu5Z5l9gykIqG3C7pQISdbwdoY,2357
|
|
9
|
+
ephys_link/bindings/mpm_binding.py,sha256=vn7IKqdiZ6_MX91zomqDXX08ONHwVVgWncRuJTxJpOM,10872
|
|
10
|
+
ephys_link/bindings/ump_binding.py,sha256=wbJe6Ro4E-TPejBBDkNsKUluF2Gr0rZBuyp3LJ2TipM,7865
|
|
11
|
+
ephys_link/front_end/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
ephys_link/front_end/cli.py,sha256=jWUEWhIHvWF8ZN6xPIbfoQblDWxMLsjwhtFB_jfOO_s,3093
|
|
13
|
+
ephys_link/front_end/console.py,sha256=zq67dn7T4xKMg3jjbNra_1s0N9ItvsTRXVIhClWbjP8,3912
|
|
14
|
+
ephys_link/front_end/gui.py,sha256=x5mNekxe1yhPKcCFNnDL3AarhNmUoBJooykFS_6pWt4,7227
|
|
15
|
+
ephys_link/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
ephys_link/utils/base_binding.py,sha256=rq0FtvkjAN287nX8rZwR-j6YPzV3Pkp8PqJfZlhIzqo,5413
|
|
17
|
+
ephys_link/utils/constants.py,sha256=afD-FFrfCD3cZc500jzWw4I-dGibLPluxteX8BxlgK4,3883
|
|
18
|
+
ephys_link/utils/converters.py,sha256=ZdVmIX-LHCwM__F0SpjN_mfNGGetr1U97xvHd0hf8T0,2038
|
|
19
|
+
ephys_link/utils/startup.py,sha256=Yx9LSedaLmTpzkb1E0NOqxaDMhRXLShkI2PJj80_95U,3620
|
|
20
|
+
ephys_link-2.1.1.dist-info/METADATA,sha256=soPPtLMp-oQw7xCKX1hwMo7Slu5qmBxv4Ryk7gfcs7Y,4775
|
|
21
|
+
ephys_link-2.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
22
|
+
ephys_link-2.1.1.dist-info/entry_points.txt,sha256=o8wV3AdnJ9o47vg9ymKxPNVq9pMdPq8UZHE_iyAJx-k,124
|
|
23
|
+
ephys_link-2.1.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
24
|
+
ephys_link-2.1.1.dist-info/RECORD,,
|
ephys_link/resources/libum.dll
DELETED
|
Binary file
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
ephys_link/__about__.py,sha256=72sTjjXH8lKUBOrH9ADJVFplt-JL7YxNRjHxZ7aL25E,24
|
|
2
|
-
ephys_link/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
ephys_link/__main__.py,sha256=sbFdC6KJjTfXDgRraU_fmGRPcF4I1Ur9PRDiD86dkRI,1449
|
|
4
|
-
ephys_link/back_end/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
ephys_link/back_end/platform_handler.py,sha256=gdiO6d0L-DWWLEJOL6eP6685tOC6otffmhfIBtPjhq0,12604
|
|
6
|
-
ephys_link/back_end/server.py,sha256=mb3K3pXSO-gHaSj1CGJ0v3CSOW5YCi-p0EOKoySRzKQ,10322
|
|
7
|
-
ephys_link/bindings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
ephys_link/bindings/fake_binding.py,sha256=PI7zYv-SjWsGFEs_FVu5Z5l9gykIqG3C7pQISdbwdoY,2357
|
|
9
|
-
ephys_link/bindings/mpm_binding.py,sha256=vn7IKqdiZ6_MX91zomqDXX08ONHwVVgWncRuJTxJpOM,10872
|
|
10
|
-
ephys_link/bindings/ump_binding.py,sha256=-RwzUggGYsznhV6yiXb96y6WD-e3E7-tcDk6NfesiWU,8080
|
|
11
|
-
ephys_link/front_end/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
ephys_link/front_end/cli.py,sha256=isIJs_sZbz7VbNvLgi-HyDlE-TyKD12auDhMTxAkWQU,3099
|
|
13
|
-
ephys_link/front_end/gui.py,sha256=MDcrTS_Xz9bopAgamh4HknqRC10W8E6eOS3Kss_2ZKQ,6864
|
|
14
|
-
ephys_link/resources/libum.dll,sha256=YaD4dwiSNohx-XxHjx2eQWPOBEVvUIXARvx37e_yqNw,316316
|
|
15
|
-
ephys_link/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
ephys_link/utils/base_binding.py,sha256=rq0FtvkjAN287nX8rZwR-j6YPzV3Pkp8PqJfZlhIzqo,5413
|
|
17
|
-
ephys_link/utils/console.py,sha256=52SYvXv_7Fx8QDL3RMFQoggQ1n5W93Yu5aU7uuJQgfg,3904
|
|
18
|
-
ephys_link/utils/constants.py,sha256=1aML7zBNTM5onVSf6NDrYIR33VJy-dIHd1lFORVBGbM,725
|
|
19
|
-
ephys_link/utils/converters.py,sha256=ZdVmIX-LHCwM__F0SpjN_mfNGGetr1U97xvHd0hf8T0,2038
|
|
20
|
-
ephys_link/utils/startup.py,sha256=jZVed78tuWjUuZqWVgii_zumDr87T-ikEtOFa6KTE_E,2500
|
|
21
|
-
ephys_link-2.1.0b0.dist-info/METADATA,sha256=i3-gX7xuUweGrkbvtoQQ01nNf2jsrd-hYtvLL_zF4tE,4609
|
|
22
|
-
ephys_link-2.1.0b0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
-
ephys_link-2.1.0b0.dist-info/entry_points.txt,sha256=o8wV3AdnJ9o47vg9ymKxPNVq9pMdPq8UZHE_iyAJx-k,124
|
|
24
|
-
ephys_link-2.1.0b0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
25
|
-
ephys_link-2.1.0b0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|