np-services 0.1.69__py3-none-any.whl → 0.1.71__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.
- np_services/__init__.py +8 -8
- np_services/open_ephys.py +377 -378
- np_services/protocols.py +185 -185
- np_services/proxies.py +1489 -1489
- np_services/resources/mvr_connector.py +260 -260
- np_services/resources/zro.py +325 -325
- np_services/scripts/pretest.py +412 -389
- np_services/stim_computer_theme_changer.py +41 -41
- np_services/utils.py +167 -167
- {np_services-0.1.69.dist-info → np_services-0.1.71.dist-info}/METADATA +5 -5
- np_services-0.1.71.dist-info/RECORD +15 -0
- {np_services-0.1.69.dist-info → np_services-0.1.71.dist-info}/WHEEL +2 -1
- {np_services-0.1.69.dist-info → np_services-0.1.71.dist-info}/entry_points.txt +1 -1
- np_services-0.1.71.dist-info/top_level.txt +1 -0
- np_services/.mypy_cache/.gitignore +0 -2
- np_services/.mypy_cache/3.9/@plugins_snapshot.json +0 -1
- np_services/.mypy_cache/3.9/__future__.data.json +0 -1
- np_services/.mypy_cache/3.9/__future__.meta.json +0 -1
- np_services/.mypy_cache/3.9/_ast.data.json +0 -1
- np_services/.mypy_cache/3.9/_ast.meta.json +0 -1
- np_services/.mypy_cache/3.9/_codecs.data.json +0 -1
- np_services/.mypy_cache/3.9/_codecs.meta.json +0 -1
- np_services/.mypy_cache/3.9/_collections_abc.data.json +0 -1
- np_services/.mypy_cache/3.9/_collections_abc.meta.json +0 -1
- np_services/.mypy_cache/3.9/_ctypes.data.json +0 -1
- np_services/.mypy_cache/3.9/_ctypes.meta.json +0 -1
- np_services/.mypy_cache/3.9/_decimal.data.json +0 -1
- np_services/.mypy_cache/3.9/_decimal.meta.json +0 -1
- np_services/.mypy_cache/3.9/_random.data.json +0 -1
- np_services/.mypy_cache/3.9/_random.meta.json +0 -1
- np_services/.mypy_cache/3.9/_socket.data.json +0 -1
- np_services/.mypy_cache/3.9/_socket.meta.json +0 -1
- np_services/.mypy_cache/3.9/_thread.data.json +0 -1
- np_services/.mypy_cache/3.9/_thread.meta.json +0 -1
- np_services/.mypy_cache/3.9/_typeshed/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/_typeshed/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/_warnings.data.json +0 -1
- np_services/.mypy_cache/3.9/_warnings.meta.json +0 -1
- np_services/.mypy_cache/3.9/_weakref.data.json +0 -1
- np_services/.mypy_cache/3.9/_weakref.meta.json +0 -1
- np_services/.mypy_cache/3.9/_weakrefset.data.json +0 -1
- np_services/.mypy_cache/3.9/_weakrefset.meta.json +0 -1
- np_services/.mypy_cache/3.9/_winapi.data.json +0 -1
- np_services/.mypy_cache/3.9/_winapi.meta.json +0 -1
- np_services/.mypy_cache/3.9/abc.data.json +0 -1
- np_services/.mypy_cache/3.9/abc.meta.json +0 -1
- np_services/.mypy_cache/3.9/array.data.json +0 -1
- np_services/.mypy_cache/3.9/array.meta.json +0 -1
- np_services/.mypy_cache/3.9/atexit.data.json +0 -1
- np_services/.mypy_cache/3.9/atexit.meta.json +0 -1
- np_services/.mypy_cache/3.9/builtins.data.json +0 -1
- np_services/.mypy_cache/3.9/builtins.meta.json +0 -1
- np_services/.mypy_cache/3.9/codecs.data.json +0 -1
- np_services/.mypy_cache/3.9/codecs.meta.json +0 -1
- np_services/.mypy_cache/3.9/collections/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/collections/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/collections/abc.data.json +0 -1
- np_services/.mypy_cache/3.9/collections/abc.meta.json +0 -1
- np_services/.mypy_cache/3.9/contextlib.data.json +0 -1
- np_services/.mypy_cache/3.9/contextlib.meta.json +0 -1
- np_services/.mypy_cache/3.9/ctypes/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/ctypes/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/datetime.data.json +0 -1
- np_services/.mypy_cache/3.9/datetime.meta.json +0 -1
- np_services/.mypy_cache/3.9/decimal.data.json +0 -1
- np_services/.mypy_cache/3.9/decimal.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/email/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/charset.data.json +0 -1
- np_services/.mypy_cache/3.9/email/charset.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/contentmanager.data.json +0 -1
- np_services/.mypy_cache/3.9/email/contentmanager.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/errors.data.json +0 -1
- np_services/.mypy_cache/3.9/email/errors.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/header.data.json +0 -1
- np_services/.mypy_cache/3.9/email/header.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/message.data.json +0 -1
- np_services/.mypy_cache/3.9/email/message.meta.json +0 -1
- np_services/.mypy_cache/3.9/email/policy.data.json +0 -1
- np_services/.mypy_cache/3.9/email/policy.meta.json +0 -1
- np_services/.mypy_cache/3.9/enum.data.json +0 -1
- np_services/.mypy_cache/3.9/enum.meta.json +0 -1
- np_services/.mypy_cache/3.9/errno.data.json +0 -1
- np_services/.mypy_cache/3.9/errno.meta.json +0 -1
- np_services/.mypy_cache/3.9/fractions.data.json +0 -1
- np_services/.mypy_cache/3.9/fractions.meta.json +0 -1
- np_services/.mypy_cache/3.9/genericpath.data.json +0 -1
- np_services/.mypy_cache/3.9/genericpath.meta.json +0 -1
- np_services/.mypy_cache/3.9/importlib/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/importlib/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/importlib/abc.data.json +0 -1
- np_services/.mypy_cache/3.9/importlib/abc.meta.json +0 -1
- np_services/.mypy_cache/3.9/importlib/machinery.data.json +0 -1
- np_services/.mypy_cache/3.9/importlib/machinery.meta.json +0 -1
- np_services/.mypy_cache/3.9/importlib/metadata/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/importlib/metadata/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/io.data.json +0 -1
- np_services/.mypy_cache/3.9/io.meta.json +0 -1
- np_services/.mypy_cache/3.9/json/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/json/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/json/decoder.data.json +0 -1
- np_services/.mypy_cache/3.9/json/decoder.meta.json +0 -1
- np_services/.mypy_cache/3.9/json/encoder.data.json +0 -1
- np_services/.mypy_cache/3.9/json/encoder.meta.json +0 -1
- np_services/.mypy_cache/3.9/logging/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/logging/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/math.data.json +0 -1
- np_services/.mypy_cache/3.9/math.meta.json +0 -1
- np_services/.mypy_cache/3.9/mmap.data.json +0 -1
- np_services/.mypy_cache/3.9/mmap.meta.json +0 -1
- np_services/.mypy_cache/3.9/np_services/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/np_services/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/np_services/config.data.json +0 -1
- np_services/.mypy_cache/3.9/np_services/config.meta.json +0 -1
- np_services/.mypy_cache/3.9/np_services/protocols.data.json +0 -1
- np_services/.mypy_cache/3.9/np_services/protocols.meta.json +0 -1
- np_services/.mypy_cache/3.9/np_services/zro.data.json +0 -1
- np_services/.mypy_cache/3.9/np_services/zro.meta.json +0 -1
- np_services/.mypy_cache/3.9/ntpath.data.json +0 -1
- np_services/.mypy_cache/3.9/ntpath.meta.json +0 -1
- np_services/.mypy_cache/3.9/numbers.data.json +0 -1
- np_services/.mypy_cache/3.9/numbers.meta.json +0 -1
- np_services/.mypy_cache/3.9/os/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/os/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/os/path.data.json +0 -1
- np_services/.mypy_cache/3.9/os/path.meta.json +0 -1
- np_services/.mypy_cache/3.9/pathlib.data.json +0 -1
- np_services/.mypy_cache/3.9/pathlib.meta.json +0 -1
- np_services/.mypy_cache/3.9/pickle.data.json +0 -1
- np_services/.mypy_cache/3.9/pickle.meta.json +0 -1
- np_services/.mypy_cache/3.9/platform.data.json +0 -1
- np_services/.mypy_cache/3.9/platform.meta.json +0 -1
- np_services/.mypy_cache/3.9/posixpath.data.json +0 -1
- np_services/.mypy_cache/3.9/posixpath.meta.json +0 -1
- np_services/.mypy_cache/3.9/random.data.json +0 -1
- np_services/.mypy_cache/3.9/random.meta.json +0 -1
- np_services/.mypy_cache/3.9/re.data.json +0 -1
- np_services/.mypy_cache/3.9/re.meta.json +0 -1
- np_services/.mypy_cache/3.9/shutil.data.json +0 -1
- np_services/.mypy_cache/3.9/shutil.meta.json +0 -1
- np_services/.mypy_cache/3.9/socket.data.json +0 -1
- np_services/.mypy_cache/3.9/socket.meta.json +0 -1
- np_services/.mypy_cache/3.9/sre_compile.data.json +0 -1
- np_services/.mypy_cache/3.9/sre_compile.meta.json +0 -1
- np_services/.mypy_cache/3.9/sre_constants.data.json +0 -1
- np_services/.mypy_cache/3.9/sre_constants.meta.json +0 -1
- np_services/.mypy_cache/3.9/sre_parse.data.json +0 -1
- np_services/.mypy_cache/3.9/sre_parse.meta.json +0 -1
- np_services/.mypy_cache/3.9/string.data.json +0 -1
- np_services/.mypy_cache/3.9/string.meta.json +0 -1
- np_services/.mypy_cache/3.9/subprocess.data.json +0 -1
- np_services/.mypy_cache/3.9/subprocess.meta.json +0 -1
- np_services/.mypy_cache/3.9/sys.data.json +0 -1
- np_services/.mypy_cache/3.9/sys.meta.json +0 -1
- np_services/.mypy_cache/3.9/threading.data.json +0 -1
- np_services/.mypy_cache/3.9/threading.meta.json +0 -1
- np_services/.mypy_cache/3.9/time.data.json +0 -1
- np_services/.mypy_cache/3.9/time.meta.json +0 -1
- np_services/.mypy_cache/3.9/types.data.json +0 -1
- np_services/.mypy_cache/3.9/types.meta.json +0 -1
- np_services/.mypy_cache/3.9/typing.data.json +0 -1
- np_services/.mypy_cache/3.9/typing.meta.json +0 -1
- np_services/.mypy_cache/3.9/typing_extensions.data.json +0 -1
- np_services/.mypy_cache/3.9/typing_extensions.meta.json +0 -1
- np_services/.mypy_cache/3.9/warnings.data.json +0 -1
- np_services/.mypy_cache/3.9/warnings.meta.json +0 -1
- np_services/.mypy_cache/3.9/weakref.data.json +0 -1
- np_services/.mypy_cache/3.9/weakref.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/_typing.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/_typing.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/backend/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/backend/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/backend/select.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/backend/select.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/constants.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/constants.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/error.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/error.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/attrsettr.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/attrsettr.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/context.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/context.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/frame.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/frame.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/poll.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/poll.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/socket.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/socket.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/tracker.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/tracker.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/version.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/sugar/version.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/utils/__init__.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/utils/__init__.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/utils/interop.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/utils/interop.meta.json +0 -1
- np_services/.mypy_cache/3.9/zmq/utils/jsonapi.data.json +0 -1
- np_services/.mypy_cache/3.9/zmq/utils/jsonapi.meta.json +0 -1
- np_services/.mypy_cache/CACHEDIR.TAG +0 -3
- np_services/resources/black_desktop.ps1 +0 -66
- np_services/resources/grey_desktop.ps1 +0 -66
- np_services/resources/reset_desktop.ps1 +0 -66
- np_services-0.1.69.dist-info/RECORD +0 -206
np_services/scripts/pretest.py
CHANGED
|
@@ -1,390 +1,413 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import abc
|
|
4
|
-
import argparse
|
|
5
|
-
import copy
|
|
6
|
-
import contextlib
|
|
7
|
-
import dataclasses
|
|
8
|
-
import functools
|
|
9
|
-
import json
|
|
10
|
-
import os
|
|
11
|
-
import pathlib
|
|
12
|
-
import sys
|
|
13
|
-
import tempfile
|
|
14
|
-
import time
|
|
15
|
-
import logging
|
|
16
|
-
from typing import Iterable, Literal, Type
|
|
17
|
-
|
|
18
|
-
import upath
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
import np_session
|
|
22
|
-
import np_services
|
|
23
|
-
import np_config
|
|
24
|
-
import npc_sync
|
|
25
|
-
import npc_ephys
|
|
26
|
-
import npc_mvr
|
|
27
|
-
import npc_stim
|
|
28
|
-
|
|
29
|
-
logger = logging.getLogger()
|
|
30
|
-
|
|
31
|
-
DEFAULT_SERVICES: tuple[np_services.Testable, ...] = (np_services.MouseDirector, )
|
|
32
|
-
DEFAULT_RECORDERS: tuple[np_services.Startable, ...] = (np_services.Sync, np_services.VideoMVR, )
|
|
33
|
-
|
|
34
|
-
@dataclasses.dataclass
|
|
35
|
-
class PretestConfig:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
self._commit_hash =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
self._github_url =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
params['
|
|
236
|
-
|
|
237
|
-
if self.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
params['
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
with contextlib.suppress(np_services.resources.zro.ZroError):
|
|
260
|
-
self.stim.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
self.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
self.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
self.stim
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
self.
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
self.stim.
|
|
291
|
-
|
|
292
|
-
def configure_services(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import argparse
|
|
5
|
+
import copy
|
|
6
|
+
import contextlib
|
|
7
|
+
import dataclasses
|
|
8
|
+
import functools
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import pathlib
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
import time
|
|
15
|
+
import logging
|
|
16
|
+
from typing import Iterable, Literal, Type
|
|
17
|
+
|
|
18
|
+
import upath
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
import np_session
|
|
22
|
+
import np_services
|
|
23
|
+
import np_config
|
|
24
|
+
import npc_sync
|
|
25
|
+
import npc_ephys
|
|
26
|
+
import npc_mvr
|
|
27
|
+
import npc_stim
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger()
|
|
30
|
+
|
|
31
|
+
DEFAULT_SERVICES: tuple[np_services.Testable, ...] = (np_services.MouseDirector, )
|
|
32
|
+
DEFAULT_RECORDERS: tuple[np_services.Startable, ...] = (np_services.Sync, np_services.VideoMVR, )
|
|
33
|
+
|
|
34
|
+
@dataclasses.dataclass
|
|
35
|
+
class PretestConfig:
|
|
36
|
+
check_sync_barcodes: bool = False
|
|
37
|
+
check_ephys_barcodes: bool = False # this is error prone with short recordings, so is separated from checking sync alone
|
|
38
|
+
check_licks: bool = False
|
|
39
|
+
check_opto: bool = False
|
|
40
|
+
check_audio: bool = False
|
|
41
|
+
check_running: bool = False
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def check_barcodes(self) -> bool:
|
|
45
|
+
return self.check_sync_barcodes or self.check_ephys_barcodes
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PretestSession(abc.ABC):
|
|
49
|
+
|
|
50
|
+
def __init__(self, pretest_config: PretestConfig) -> None:
|
|
51
|
+
self.pretest_config = pretest_config
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def services(self) -> tuple[np_services.Testable | np_services.Startable, ...]:
|
|
55
|
+
return DEFAULT_SERVICES + self.recorders + (self.stim, )
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def recorders(self) -> tuple[np_services.Startable, ...]:
|
|
59
|
+
if self.pretest_config.check_ephys_barcodes or self.pretest_config.check_sync_barcodes:
|
|
60
|
+
return DEFAULT_RECORDERS + (np_services.OpenEphys, )
|
|
61
|
+
return DEFAULT_RECORDERS
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
@abc.abstractmethod
|
|
65
|
+
def stim(self) -> Type[np_services.Camstim]: ...
|
|
66
|
+
|
|
67
|
+
@abc.abstractmethod
|
|
68
|
+
def configure_services(self) -> None: ...
|
|
69
|
+
|
|
70
|
+
@abc.abstractmethod
|
|
71
|
+
def run_pretest_stim(self) -> None: ...
|
|
72
|
+
|
|
73
|
+
class DynamicRoutingPretest(PretestSession):
|
|
74
|
+
"""Modified version of class in np_workflows."""
|
|
75
|
+
|
|
76
|
+
use_github: bool = True
|
|
77
|
+
task_name: str = "" # unused in pretest
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def stim(self) -> Type[np_services.ScriptCamstim]:
|
|
81
|
+
return np_services.ScriptCamstim
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def rig(self) -> np_config.Rig:
|
|
85
|
+
return np_config.Rig()
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def commit_hash(self) -> str:
|
|
89
|
+
if hasattr(self, '_commit_hash'):
|
|
90
|
+
return self._commit_hash
|
|
91
|
+
self._commit_hash = self.rig.config['dynamicrouting_task_script']['commit_hash']
|
|
92
|
+
return self.commit_hash
|
|
93
|
+
|
|
94
|
+
@commit_hash.setter
|
|
95
|
+
def commit_hash(self, value: str):
|
|
96
|
+
self._commit_hash = value
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def github_url(self) -> str:
|
|
100
|
+
if hasattr(self, '_github_url'):
|
|
101
|
+
return self._github_url
|
|
102
|
+
self._github_url = self.rig.config['dynamicrouting_task_script']['url']
|
|
103
|
+
return self.github_url
|
|
104
|
+
|
|
105
|
+
@github_url.setter
|
|
106
|
+
def github_url(self, value: str):
|
|
107
|
+
self._github_url = value
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def base_url(self) -> upath.UPath:
|
|
111
|
+
return upath.UPath(self.github_url) / self.commit_hash
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def base_path(self) -> pathlib.Path:
|
|
115
|
+
return pathlib.Path('//allen/programs/mindscope/workgroups/dynamicrouting/DynamicRoutingTask/')
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def mouse(self) -> np_session.Mouse:
|
|
119
|
+
return np_session.Mouse(366122)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def hdf5_dir(self) -> pathlib.Path:
|
|
123
|
+
return self.base_path / 'Data' / str(self.mouse)
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def task_script_base(self) -> upath.UPath:
|
|
127
|
+
return self.base_url if self.use_github else upath.UPath(self.base_path)
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def task_params(self) -> dict[str, str | bool]:
|
|
131
|
+
"""For sending to runTask.py"""
|
|
132
|
+
return dict(
|
|
133
|
+
rigName = str(self.rig).replace('.',''),
|
|
134
|
+
subjectName = str(self.mouse),
|
|
135
|
+
taskScript = 'DynamicRouting1.py',
|
|
136
|
+
taskVersion = self.task_name,
|
|
137
|
+
saveSoundArray = True,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def spontaneous_params(self) -> dict[str, str]:
|
|
142
|
+
"""For sending to runTask.py"""
|
|
143
|
+
return dict(
|
|
144
|
+
rigName = str(self.rig).replace('.',''),
|
|
145
|
+
subjectName = str(self.mouse),
|
|
146
|
+
taskScript = 'TaskControl.py',
|
|
147
|
+
taskVersion = 'spontaneous',
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def spontaneous_rewards_params(self) -> dict[str, str]:
|
|
152
|
+
"""For sending to runTask.py"""
|
|
153
|
+
return dict(
|
|
154
|
+
rigName = str(self.rig).replace('.',''),
|
|
155
|
+
subjectName = str(self.mouse),
|
|
156
|
+
taskScript = 'TaskControl.py',
|
|
157
|
+
taskVersion = 'spontaneous rewards',
|
|
158
|
+
rewardSound = "device",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def get_latest_optogui_txt(self, opto_or_optotagging: Literal['opto', 'optotagging']) -> pathlib.Path:
|
|
162
|
+
dirname = dict(opto='optoParams', optotagging='optotagging')[opto_or_optotagging]
|
|
163
|
+
file_prefix = dirname
|
|
164
|
+
|
|
165
|
+
rig = str(self.rig).replace('.', '')
|
|
166
|
+
locs_root = self.base_path / 'OptoGui' / f'{dirname}'
|
|
167
|
+
# use any available locs file - as long as the light switches on the
|
|
168
|
+
# values don't matter
|
|
169
|
+
available_locs = sorted(tuple(locs_root.glob(f"{file_prefix}*")), reverse=True)
|
|
170
|
+
if not available_locs:
|
|
171
|
+
raise FileNotFoundError(f"No optotagging locs found - have you run OptoGui?")
|
|
172
|
+
return available_locs[0]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def optotagging_params(self) -> dict[str, str]:
|
|
177
|
+
"""For sending to runTask.py"""
|
|
178
|
+
return dict(
|
|
179
|
+
rigName = str(self.rig).replace('.',''),
|
|
180
|
+
subjectName = str(self.mouse),
|
|
181
|
+
taskScript = 'OptoTagging.py',
|
|
182
|
+
optoTaggingLocs = self.get_latest_optogui_txt('optotagging').as_posix(),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def opto_params(self) -> dict[str, str | bool]:
|
|
187
|
+
"""Opto params are handled by runTask.py and don't need to be passed from
|
|
188
|
+
here. Just check they exist on disk here.
|
|
189
|
+
"""
|
|
190
|
+
_ = self.get_latest_optogui_txt('opto') # raises FileNotFoundError if not found
|
|
191
|
+
return dict(
|
|
192
|
+
rigName = str(self.rig).replace('.',''),
|
|
193
|
+
subjectName = str(self.mouse),
|
|
194
|
+
taskScript = 'DynamicRouting1.py',
|
|
195
|
+
saveSoundArray = True,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def mapping_params(self) -> dict[str, str | bool]:
|
|
200
|
+
"""For sending to runTask.py"""
|
|
201
|
+
return dict(
|
|
202
|
+
rigName = str(self.rig).replace('.',''),
|
|
203
|
+
subjectName = str(self.mouse),
|
|
204
|
+
taskScript = 'RFMapping.py',
|
|
205
|
+
saveSoundArray = True,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def sound_test_params(self) -> dict[str, str]:
|
|
210
|
+
"""For sending to runTask.py"""
|
|
211
|
+
return dict(
|
|
212
|
+
rigName = str(self.rig).replace('.',''),
|
|
213
|
+
subjectName = 'sound',
|
|
214
|
+
taskScript = 'TaskControl.py',
|
|
215
|
+
taskVersion = 'sound test',
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def get_github_file_content(self, address: str) -> str:
|
|
219
|
+
import requests
|
|
220
|
+
response = requests.get(address)
|
|
221
|
+
if response.status_code not in (200, ):
|
|
222
|
+
response.raise_for_status()
|
|
223
|
+
return response.content.decode("utf-8")
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def camstim_script(self) -> upath.UPath:
|
|
227
|
+
return self.task_script_base / 'runTask.py'
|
|
228
|
+
|
|
229
|
+
def run_script(self, stim: Literal['sound_test', 'mapping', 'task', 'opto', 'optotagging', 'spontaneous', 'spontaneous_rewards']) -> None:
|
|
230
|
+
|
|
231
|
+
params = copy.deepcopy(getattr(self, f'{stim.replace(" ", "_")}_params'))
|
|
232
|
+
|
|
233
|
+
# add mouse and user info for MPE
|
|
234
|
+
params['mouse_id'] = str(self.mouse.id)
|
|
235
|
+
params['user_id'] = 'ben.hardcastle'
|
|
236
|
+
|
|
237
|
+
if self.task_script_base.as_posix() not in params['taskScript']:
|
|
238
|
+
params['taskScript'] = (self.task_script_base / params['taskScript']).as_posix()
|
|
239
|
+
|
|
240
|
+
params['maxTrials'] = 30
|
|
241
|
+
|
|
242
|
+
if self.use_github:
|
|
243
|
+
|
|
244
|
+
params['GHTaskScriptParams'] = {
|
|
245
|
+
'taskScript': params['taskScript'],
|
|
246
|
+
'taskControl': (self.task_script_base / 'TaskControl.py').as_posix(),
|
|
247
|
+
'taskUtils': (self.task_script_base / 'TaskUtils.py').as_posix(),
|
|
248
|
+
}
|
|
249
|
+
params['task_script_commit_hash'] = self.commit_hash
|
|
250
|
+
|
|
251
|
+
self.stim.script = self.camstim_script.read_text()
|
|
252
|
+
else:
|
|
253
|
+
self.stim.script = self.camstim_script.as_posix()
|
|
254
|
+
|
|
255
|
+
self.stim.params = params
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
self.stim.start()
|
|
259
|
+
with contextlib.suppress(np_services.resources.zro.ZroError):
|
|
260
|
+
while not self.stim.is_ready_to_start():
|
|
261
|
+
time.sleep(1)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
with contextlib.suppress(np_services.resources.zro.ZroError):
|
|
265
|
+
self.stim.finalize()
|
|
266
|
+
|
|
267
|
+
def run_pretest_stim(self) -> None:
|
|
268
|
+
if self.pretest_config.check_audio:
|
|
269
|
+
print("Starting audio test - check for sound...", end="", flush=True)
|
|
270
|
+
self.run_script('sound_test')
|
|
271
|
+
print(" done")
|
|
272
|
+
print("Starting stim - check for opto, spin wheel and tap lick spout...", end="", flush=True)
|
|
273
|
+
self.run_script('optotagging') # vis stim with opto (for checking vsyncs, running, )
|
|
274
|
+
print(" done")
|
|
275
|
+
|
|
276
|
+
def configure_services(self) -> None:
|
|
277
|
+
self.stim.script = '//allen/programs/mindscope/workgroups/dynamicrouting/DynamicRoutingTask/runTask.py'
|
|
278
|
+
self.stim.data_root = pathlib.Path('//allen/programs/mindscope/workgroups/dynamicrouting/DynamicRoutingTask/Data/366122')
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class LegacyNP0Pretest(PretestSession):
|
|
282
|
+
def __init__(self, pretest_config: PretestConfig) -> None:
|
|
283
|
+
self.pretest_config = pretest_config
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def stim(self) -> Type[np_services.SessionCamstim]:
|
|
287
|
+
return np_services.SessionCamstim
|
|
288
|
+
|
|
289
|
+
def run_pretest_stim(self) -> None:
|
|
290
|
+
self.stim.start()
|
|
291
|
+
|
|
292
|
+
def configure_services(self) -> None:
|
|
293
|
+
self.stim.lims_user_id = "ben.hardcastle"
|
|
294
|
+
self.stim.labtracks_mouse_id = 598796
|
|
295
|
+
self.stim.override_params = json.loads(pathlib.Path("//allen/programs/mindscope/workgroups/dynamicrouting/ben/np0_pretest/params.json").read_bytes())
|
|
296
|
+
|
|
297
|
+
def configure_services(session: PretestSession) -> None:
|
|
298
|
+
"""For each service, apply every key in self.config['service'] as an attribute."""
|
|
299
|
+
|
|
300
|
+
def apply_config(service) -> None:
|
|
301
|
+
if config := np_config.Rig().config["services"].get(service.__name__):
|
|
302
|
+
for key, value in config.items():
|
|
303
|
+
setattr(service, key, value)
|
|
304
|
+
logger.debug(
|
|
305
|
+
f"{service.__name__} | Configuring {service.__name__}.{key} = {getattr(service, key)}"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
for service in session.services:
|
|
309
|
+
for base in service.__class__.__bases__:
|
|
310
|
+
apply_config(base)
|
|
311
|
+
apply_config(service)
|
|
312
|
+
|
|
313
|
+
np_services.MouseDirector.user = 'ben.hardcastle'
|
|
314
|
+
np_services.MouseDirector.mouse = 366122
|
|
315
|
+
|
|
316
|
+
session.configure_services()
|
|
317
|
+
|
|
318
|
+
if session.pretest_config.check_barcodes:
|
|
319
|
+
np_services.OpenEphys.folder = '_test_'
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@functools.cache
|
|
323
|
+
def get_temp_dir() -> pathlib.Path:
|
|
324
|
+
return pathlib.Path(tempfile.mkdtemp())
|
|
325
|
+
|
|
326
|
+
def run_pretest(
|
|
327
|
+
config: PretestConfig = PretestConfig(),
|
|
328
|
+
) -> None:
|
|
329
|
+
print("Starting pretest")
|
|
330
|
+
session: PretestSession
|
|
331
|
+
if np_config.Rig().idx == 0:
|
|
332
|
+
session = LegacyNP0Pretest(config)
|
|
333
|
+
else:
|
|
334
|
+
session = DynamicRoutingPretest(config)
|
|
335
|
+
configure_services(session)
|
|
336
|
+
|
|
337
|
+
for service in session.services:
|
|
338
|
+
if isinstance(service, np_services.Initializable):
|
|
339
|
+
service.initialize()
|
|
340
|
+
|
|
341
|
+
stoppables = tuple(_ for _ in session.recorders if isinstance(_, np_services.Stoppable))
|
|
342
|
+
with np_services.stop_on_error(*stoppables):
|
|
343
|
+
for service in stoppables:
|
|
344
|
+
if isinstance(service, np_services.Startable):
|
|
345
|
+
service.start()
|
|
346
|
+
t0 = time.time()
|
|
347
|
+
session.run_pretest_stim()
|
|
348
|
+
t1 = time.time()
|
|
349
|
+
if not config.check_barcodes:
|
|
350
|
+
min_wait_time = 0
|
|
351
|
+
elif config.check_sync_barcodes and not config.check_ephys_barcodes:
|
|
352
|
+
min_wait_time = 35 # long enough to capture 1 sets of barcodes on sync
|
|
353
|
+
else:
|
|
354
|
+
min_wait_time = 70 # long enough to capture 2 sets of barcodes on sync/openephys (cannot scale time with 1 set)
|
|
355
|
+
time.sleep(max(0, min_wait_time - (t1 - t0)))
|
|
356
|
+
for service in reversed(stoppables):
|
|
357
|
+
if isinstance(service, np_services.Stoppable):
|
|
358
|
+
service.stop()
|
|
359
|
+
|
|
360
|
+
for service in session.services:
|
|
361
|
+
if isinstance(service, np_services.Finalizable):
|
|
362
|
+
service.finalize()
|
|
363
|
+
|
|
364
|
+
np_services.VideoMVR.sync_path = np_services.OpenEphys.sync_path = session.stim.sync_path = np_services.Sync.data_files[0]
|
|
365
|
+
|
|
366
|
+
# validate
|
|
367
|
+
for service in session.services:
|
|
368
|
+
if isinstance(service, np_services.Validatable):
|
|
369
|
+
if service is not np_services.OpenEphys:
|
|
370
|
+
service.validate()
|
|
371
|
+
elif config.check_ephys_barcodes:
|
|
372
|
+
# try validating ephys without sync (currently error prone with short pretest-like recordings)
|
|
373
|
+
npc_ephys.validate_ephys(
|
|
374
|
+
root_paths=service.data_files,
|
|
375
|
+
sync_path_or_dataset=False,
|
|
376
|
+
ignore_small_folders=False,
|
|
377
|
+
)
|
|
378
|
+
else:
|
|
379
|
+
# barcodes on sync will be validated, open ephys will be assumed to work correctly
|
|
380
|
+
continue
|
|
381
|
+
assert np_services.Sync.data_files is not None, "No sync file found"
|
|
382
|
+
assert session.stim.data_files is not None, "No stim file found"
|
|
383
|
+
|
|
384
|
+
if any((config.check_licks, config.check_opto, config.check_audio)):
|
|
385
|
+
npc_sync.SyncDataset(np_services.Sync.data_files[0]).validate(
|
|
386
|
+
licks=config.check_licks, opto=config.check_opto, audio=config.check_audio,
|
|
387
|
+
)
|
|
388
|
+
if config.check_running:
|
|
389
|
+
speed, timestamps = npc_stim.get_running_speed_from_stim_files(*session.stim.data_files, sync=np_services.Sync.data_files[0])
|
|
390
|
+
if not speed.size or not timestamps.size:
|
|
391
|
+
raise AssertionError("No running data found")
|
|
392
|
+
|
|
393
|
+
def parse_args() -> PretestConfig:
|
|
394
|
+
parser = argparse.ArgumentParser(description="Run pretest")
|
|
395
|
+
parser.add_argument("--check_ephys_barcodes", action="store_true", help="Check barcodes from Arduino are being received on open ephys and time-alignment is possible (currently error prone with short pretest-like recordings)", default=False)
|
|
396
|
+
parser.add_argument("--check_sync_barcodes", action="store_true", help="Check barcodes from Arduino are being received on sync", default=False)
|
|
397
|
+
parser.add_argument("--check_licks", action="store_true", help="Check lick sensor line on sync", default=False)
|
|
398
|
+
parser.add_argument("--check_opto", action="store_true", help="Check opto-running line on sync", default=False)
|
|
399
|
+
parser.add_argument("--check_audio", action="store_true", help="Check audio-running line on sync", default=False)
|
|
400
|
+
parser.add_argument("--check_running", action="store_true", help="Check running-wheel encoder data in stim files", default=False)
|
|
401
|
+
return PretestConfig(**vars(parser.parse_args()))
|
|
402
|
+
|
|
403
|
+
def main() -> None:
|
|
404
|
+
logging.basicConfig(
|
|
405
|
+
level="INFO",
|
|
406
|
+
format="%(name)s | %(levelname)s | %(message)s",
|
|
407
|
+
datefmt="%H:%M:%S",
|
|
408
|
+
stream=sys.stdout,
|
|
409
|
+
)
|
|
410
|
+
run_pretest(parse_args())
|
|
411
|
+
|
|
412
|
+
if __name__ == '__main__':
|
|
390
413
|
main()
|