np-workflows 1.6.89__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_workflows/__init__.py +7 -0
- np_workflows/assets/images/logo_np_hab.png +0 -0
- np_workflows/assets/images/logo_np_vis.png +0 -0
- np_workflows/experiments/__init__.py +1 -0
- np_workflows/experiments/dynamic_routing/__init__.py +2 -0
- np_workflows/experiments/dynamic_routing/main.py +117 -0
- np_workflows/experiments/dynamic_routing/widgets.py +82 -0
- np_workflows/experiments/openscope_P3/P3_workflow_widget.py +83 -0
- np_workflows/experiments/openscope_P3/__init__.py +2 -0
- np_workflows/experiments/openscope_P3/main_P3_pilot.py +217 -0
- np_workflows/experiments/openscope_barcode/__init__.py +2 -0
- np_workflows/experiments/openscope_barcode/barcode_workflow_widget.py +83 -0
- np_workflows/experiments/openscope_barcode/camstim_scripts/barcode_mapping_script.py +138 -0
- np_workflows/experiments/openscope_barcode/camstim_scripts/barcode_opto_script.py +219 -0
- np_workflows/experiments/openscope_barcode/main_barcode_pilot.py +217 -0
- np_workflows/experiments/openscope_loop/__init__.py +2 -0
- np_workflows/experiments/openscope_loop/camstim_scripts/barcode_mapping_script.py +138 -0
- np_workflows/experiments/openscope_loop/camstim_scripts/barcode_opto_script.py +219 -0
- np_workflows/experiments/openscope_loop/loop_workflow_widget.py +83 -0
- np_workflows/experiments/openscope_loop/main_loop_pilot.py +217 -0
- np_workflows/experiments/openscope_psycode/__init__.py +2 -0
- np_workflows/experiments/openscope_psycode/main_psycode_pilot.py +217 -0
- np_workflows/experiments/openscope_psycode/psycode_workflow_widget.py +83 -0
- np_workflows/experiments/openscope_v2/__init__.py +2 -0
- np_workflows/experiments/openscope_v2/main_v2_pilot.py +217 -0
- np_workflows/experiments/openscope_v2/v2_workflow_widget.py +83 -0
- np_workflows/experiments/openscope_vippo/__init__.py +2 -0
- np_workflows/experiments/openscope_vippo/main_vippo_pilot.py +217 -0
- np_workflows/experiments/openscope_vippo/vippo_workflow_widget.py +83 -0
- np_workflows/experiments/task_trained_network/__init__.py +2 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/make_tt_stims.py +23 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/oct22_tt_stim_script.py +69 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_00.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_01.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_02.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_03.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_04.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_05.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_06.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_07.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_08.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_09.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_10.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_11.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_12.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_13.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_14.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_15.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_16.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_17.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_18.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/flash_250ms.stim +20 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/gabor_20_deg_250ms.stim +30 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/old_stim.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/shuffle_reversed.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/shuffle_reversed_1st.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/stims/shuffle_reversed_2nd.stim +5 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/ttn_main_script.py +130 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/ttn_mapping_script.py +138 -0
- np_workflows/experiments/task_trained_network/camstim_scripts/ttn_opto_script.py +219 -0
- np_workflows/experiments/task_trained_network/main_ttn_pilot.py +263 -0
- np_workflows/experiments/task_trained_network/ttn_session_widget.py +83 -0
- np_workflows/experiments/task_trained_network/ttn_stim_config.py +213 -0
- np_workflows/experiments/templeton/__init__.py +2 -0
- np_workflows/experiments/templeton/main.py +105 -0
- np_workflows/experiments/templeton/widgets.py +82 -0
- np_workflows/shared/__init__.py +3 -0
- np_workflows/shared/base_experiments.py +826 -0
- np_workflows/shared/camstim_scripts/flash_250ms.stim +20 -0
- np_workflows/shared/camstim_scripts/gabor_20_deg_250ms.stim +30 -0
- np_workflows/shared/npxc.py +187 -0
- np_workflows/shared/widgets.py +705 -0
- np_workflows-1.6.89.dist-info/METADATA +85 -0
- np_workflows-1.6.89.dist-info/RECORD +76 -0
- np_workflows-1.6.89.dist-info/WHEEL +4 -0
- np_workflows-1.6.89.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import contextlib
|
|
3
|
+
import copy
|
|
4
|
+
import dataclasses
|
|
5
|
+
import datetime
|
|
6
|
+
import enum
|
|
7
|
+
import functools
|
|
8
|
+
import pathlib
|
|
9
|
+
import platform
|
|
10
|
+
import shutil
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
import zlib
|
|
14
|
+
from typing import ClassVar, Literal, NamedTuple, NoReturn, Optional, TypedDict
|
|
15
|
+
|
|
16
|
+
import IPython
|
|
17
|
+
import IPython.display
|
|
18
|
+
import ipywidgets as ipw
|
|
19
|
+
import np_config
|
|
20
|
+
import np_logging
|
|
21
|
+
import np_services
|
|
22
|
+
import np_session
|
|
23
|
+
import np_workflows
|
|
24
|
+
import PIL.Image
|
|
25
|
+
import pydantic
|
|
26
|
+
from pyparsing import Any
|
|
27
|
+
from np_services import (
|
|
28
|
+
Service,
|
|
29
|
+
Finalizable,
|
|
30
|
+
ScriptCamstim,
|
|
31
|
+
OpenEphys,
|
|
32
|
+
Sync,
|
|
33
|
+
VideoMVR,
|
|
34
|
+
NewScaleCoordinateRecorder,
|
|
35
|
+
MouseDirector,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from .ttn_stim_config import (
|
|
39
|
+
TTNSession,
|
|
40
|
+
camstim_defaults,
|
|
41
|
+
per_session_main_stim_params,
|
|
42
|
+
per_session_mapping_params,
|
|
43
|
+
per_session_opto_params,
|
|
44
|
+
default_ttn_params as DEFAULT_STIM_PARAMS,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
logger = np_logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TTNMixin:
|
|
51
|
+
"""Provides TTN-specific methods and attributes, mainly related to camstim scripts."""
|
|
52
|
+
|
|
53
|
+
ttn_session: TTNSession
|
|
54
|
+
"""Enum for session type, e.g. PRETEST, HAB_60, HAB_90, EPHYS."""
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def script_root_on_stim(self) -> pathlib.Path:
|
|
58
|
+
"Path to local copy on Stim, from Stim."
|
|
59
|
+
return pathlib.Path('C:/ProgramData/StimulusFiles/dev')
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def script_root_on_local(self) -> pathlib.Path:
|
|
63
|
+
"Path to version controlled scripts on local machine."
|
|
64
|
+
return (pathlib.Path(__file__).parent / 'camstim_scripts')
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def script_names(self) -> dict[Literal["main", "mapping", "opto"], str]:
|
|
68
|
+
return {
|
|
69
|
+
label: f"ttn_{label}_script.py" for label in ("main", "mapping", "opto")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def stim_root_on_stim(self) -> pathlib.Path:
|
|
74
|
+
"Path to dev folder on Stim computer, as seen from local machine."
|
|
75
|
+
return np_config.local_to_unc(self.rig.stim, self.script_root_on_stim)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def stim_root_on_local(self) -> pathlib.Path:
|
|
79
|
+
"Path to version controlled stim files on local machine."
|
|
80
|
+
return self.script_root_on_local / 'stims'
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def recorders(self) -> tuple[Service, ...]:
|
|
84
|
+
"""Services to be started before stimuli run, and stopped after. Session-dependent."""
|
|
85
|
+
match self.ttn_session:
|
|
86
|
+
case TTNSession.PRETEST | TTNSession.EPHYS:
|
|
87
|
+
return (Sync, VideoMVR, OpenEphys)
|
|
88
|
+
case TTNSession.HAB_60 | TTNSession.HAB_90 | TTNSession.HAB_120:
|
|
89
|
+
return (Sync, VideoMVR)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def stims(self) -> tuple[Service, ...]:
|
|
93
|
+
return (ScriptCamstim,)
|
|
94
|
+
|
|
95
|
+
def initialize_and_test_services(self) -> None:
|
|
96
|
+
"""Configure, initialize (ie. reset), then test all services."""
|
|
97
|
+
|
|
98
|
+
MouseDirector.user = self.user.id
|
|
99
|
+
MouseDirector.mouse = self.mouse.id
|
|
100
|
+
|
|
101
|
+
OpenEphys.folder = self.session.folder
|
|
102
|
+
|
|
103
|
+
NewScaleCoordinateRecorder.log_root = self.session.npexp_path
|
|
104
|
+
NewScaleCoordinateRecorder.log_name = self.platform_json.path.name
|
|
105
|
+
|
|
106
|
+
self.configure_services()
|
|
107
|
+
|
|
108
|
+
super().initialize_and_test_services()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def update_state(self) -> None:
|
|
112
|
+
"Store useful but non-essential info."
|
|
113
|
+
self.mouse.state['last_session'] = self.session.id
|
|
114
|
+
self.mouse.state['last_ttn_session'] = str(self.ttn_session)
|
|
115
|
+
if self.mouse == 366122:
|
|
116
|
+
return
|
|
117
|
+
match self.ttn_session:
|
|
118
|
+
case TTNSession.PRETEST:
|
|
119
|
+
return
|
|
120
|
+
case TTNSession.HAB_60 | TTNSession.HAB_90 | TTNSession.HAB_120:
|
|
121
|
+
self.session.project.state['latest_hab'] = self.session.id
|
|
122
|
+
case TTNSession.EPHYS:
|
|
123
|
+
self.session.project.state['latest_ephys'] = self.session.id
|
|
124
|
+
self.session.project.state['sessions'] = self.session.project.state.get('sessions', []) + [self.session.id]
|
|
125
|
+
|
|
126
|
+
def run_stim_scripts(self) -> None:
|
|
127
|
+
self.validate_or_copy_stim_files()
|
|
128
|
+
self.update_state()
|
|
129
|
+
|
|
130
|
+
for stim in ('mapping', 'main', 'opto'):
|
|
131
|
+
|
|
132
|
+
if not (params := self.params[stim]):
|
|
133
|
+
logger.info("%s script skipped this session: %r", stim, self.ttn_session)
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
ScriptCamstim.params = params
|
|
137
|
+
ScriptCamstim.script = self.scripts[stim]
|
|
138
|
+
|
|
139
|
+
logger.debug("Starting %s script", stim)
|
|
140
|
+
|
|
141
|
+
ScriptCamstim.start()
|
|
142
|
+
|
|
143
|
+
with contextlib.suppress(Exception):
|
|
144
|
+
np_logging.web(f'ttn_{self.ttn_session.name.lower()}').info(f"{stim.capitalize()} stim started")
|
|
145
|
+
|
|
146
|
+
with contextlib.suppress(Exception):
|
|
147
|
+
while not ScriptCamstim.is_ready_to_start():
|
|
148
|
+
time.sleep(2.5)
|
|
149
|
+
|
|
150
|
+
if isinstance(ScriptCamstim, Finalizable):
|
|
151
|
+
ScriptCamstim.finalize()
|
|
152
|
+
|
|
153
|
+
with contextlib.suppress(Exception):
|
|
154
|
+
np_logging.web(f'ttn_{self.ttn_session.name.lower()}').info(f"{stim.capitalize()} stim finished")
|
|
155
|
+
|
|
156
|
+
def validate_or_copy_stim_files(self):
|
|
157
|
+
for vc_copy in self.stim_root_on_local.iterdir():
|
|
158
|
+
stim_copy = self.stim_root_on_stim / vc_copy.name
|
|
159
|
+
validate_or_overwrite(validate=stim_copy, src=vc_copy)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def params(self) -> dict[Literal["main", "mapping", "opto", "system"], dict[str, Any]]:
|
|
163
|
+
params = copy.deepcopy(DEFAULT_STIM_PARAMS)
|
|
164
|
+
params["mouse_id"] = str(self.mouse)
|
|
165
|
+
params["user_id"] = str(self.user)
|
|
166
|
+
if system := self.system_camstim_params:
|
|
167
|
+
params["system"] = system
|
|
168
|
+
params["main"] = per_session_main_stim_params(self.ttn_session)
|
|
169
|
+
params["mapping"] = per_session_mapping_params(self.ttn_session)
|
|
170
|
+
params["opto"] = per_session_opto_params(self.ttn_session, self.mouse)
|
|
171
|
+
return params
|
|
172
|
+
|
|
173
|
+
@functools.cached_property
|
|
174
|
+
def scripts(self) -> dict[Literal["main", "mapping", "opto"], str]:
|
|
175
|
+
"""Local path on Stim computer to each script.
|
|
176
|
+
|
|
177
|
+
Verifies Stim copy matches v.c., or overwrites on Stim.
|
|
178
|
+
"""
|
|
179
|
+
for label in ("main", "mapping", "opto"):
|
|
180
|
+
script = self.script_names[label]
|
|
181
|
+
vc_copy = self.script_root_on_local / script
|
|
182
|
+
stim_copy = np_config.local_to_unc(
|
|
183
|
+
self.rig.stim, self.script_root_on_stim / script,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
validate_or_overwrite(validate=stim_copy, src=vc_copy)
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
label: str(self.script_root_on_stim / script)
|
|
190
|
+
for label, script in self.script_names.items()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@functools.cached_property
|
|
194
|
+
def system_camstim_params(self) -> dict[str, Any]:
|
|
195
|
+
"System config on Stim computer, if accessible."
|
|
196
|
+
return camstim_defaults()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class Hab(TTNMixin, np_workflows.PipelineHab):
|
|
200
|
+
def __init__(self, *args, **kwargs):
|
|
201
|
+
self.services = (
|
|
202
|
+
MouseDirector,
|
|
203
|
+
Sync,
|
|
204
|
+
VideoMVR,
|
|
205
|
+
self.imager,
|
|
206
|
+
NewScaleCoordinateRecorder,
|
|
207
|
+
ScriptCamstim,
|
|
208
|
+
)
|
|
209
|
+
super().__init__(*args, **kwargs)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class Ephys(TTNMixin, np_workflows.PipelineEphys):
|
|
213
|
+
def __init__(self, *args, **kwargs):
|
|
214
|
+
self.services = (
|
|
215
|
+
MouseDirector,
|
|
216
|
+
Sync,
|
|
217
|
+
VideoMVR,
|
|
218
|
+
self.imager,
|
|
219
|
+
NewScaleCoordinateRecorder,
|
|
220
|
+
ScriptCamstim,
|
|
221
|
+
OpenEphys,
|
|
222
|
+
)
|
|
223
|
+
super().__init__(*args, **kwargs)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# --------------------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def new_experiment(
|
|
230
|
+
mouse: int | str | np_session.Mouse,
|
|
231
|
+
user: str | np_session.User,
|
|
232
|
+
session: TTNSession,
|
|
233
|
+
) -> Ephys | Hab:
|
|
234
|
+
"""Create a new experiment for the given mouse and user."""
|
|
235
|
+
match session:
|
|
236
|
+
case TTNSession.PRETEST | TTNSession.EPHYS:
|
|
237
|
+
experiment = Ephys(mouse, user)
|
|
238
|
+
case TTNSession.HAB_60 | TTNSession.HAB_90 | TTNSession.HAB_120:
|
|
239
|
+
experiment = Hab(mouse, user)
|
|
240
|
+
case _:
|
|
241
|
+
raise ValueError(f"Invalid session type: {session}")
|
|
242
|
+
experiment.ttn_session = session
|
|
243
|
+
|
|
244
|
+
with contextlib.suppress(Exception):
|
|
245
|
+
np_logging.web(f'ttn_{experiment.ttn_session.name.lower()}').info(f"{experiment} created")
|
|
246
|
+
|
|
247
|
+
return experiment
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# --------------------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
def validate_or_overwrite(validate: str | pathlib.Path, src: str | pathlib.Path):
|
|
253
|
+
"Checksum validate against `src`, (over)write `validate` as `src` if different."
|
|
254
|
+
validate, src = pathlib.Path(validate), pathlib.Path(src)
|
|
255
|
+
def copy():
|
|
256
|
+
logger.debug("Copying %s to %s", src, validate)
|
|
257
|
+
shutil.copy2(src, validate)
|
|
258
|
+
while (
|
|
259
|
+
validate.exists() == False
|
|
260
|
+
or (v := zlib.crc32(validate.read_bytes())) != (c := zlib.crc32(pathlib.Path(src).read_bytes()))
|
|
261
|
+
):
|
|
262
|
+
copy()
|
|
263
|
+
logger.debug("Validated %s CRC32: %08X", validate, (v & 0xFFFFFFFF) )
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import contextlib
|
|
3
|
+
import copy
|
|
4
|
+
import enum
|
|
5
|
+
import functools
|
|
6
|
+
from typing import ClassVar, Literal, NamedTuple, NoReturn, Optional, TypedDict
|
|
7
|
+
|
|
8
|
+
import IPython.display
|
|
9
|
+
import ipywidgets as ipw
|
|
10
|
+
import np_config
|
|
11
|
+
import np_logging
|
|
12
|
+
import np_session
|
|
13
|
+
import np_workflows
|
|
14
|
+
from pyparsing import Any
|
|
15
|
+
|
|
16
|
+
from .ttn_stim_config import TTNSession
|
|
17
|
+
|
|
18
|
+
global_state = {}
|
|
19
|
+
"""Global variable for persisting widget states."""
|
|
20
|
+
|
|
21
|
+
# for widget, before creating a experiment --------------------------------------------- #
|
|
22
|
+
|
|
23
|
+
class TTNSelectedSession:
|
|
24
|
+
def __init__(self, session: str | TTNSession, mouse: str | int | np_session.Mouse):
|
|
25
|
+
if isinstance(session, str):
|
|
26
|
+
session = TTNSession(session)
|
|
27
|
+
self.session = session
|
|
28
|
+
self.mouse = str(mouse)
|
|
29
|
+
|
|
30
|
+
def __repr__(self) -> str:
|
|
31
|
+
return f"{self.__class__.__name__}({self.session}, {self.mouse})"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def stim_session_select_widget(
|
|
35
|
+
mouse: str | int | np_session.Mouse,
|
|
36
|
+
) -> TTNSelectedSession:
|
|
37
|
+
"""Select a stimulus session (hab, pretest, ephys) to run.
|
|
38
|
+
|
|
39
|
+
An object with mutable attributes is returned, so the selected session can be
|
|
40
|
+
updated along with the GUI selection. (Preference would be to return an enum
|
|
41
|
+
directly, and change it's value, but that doesn't seem possible.)
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
selection = TTNSelectedSession(TTNSession.PRETEST, mouse)
|
|
46
|
+
|
|
47
|
+
session_dropdown = ipw.Select(
|
|
48
|
+
options=tuple(_.value for _ in TTNSession),
|
|
49
|
+
description="Session",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def update_selection():
|
|
53
|
+
selection.__init__(str(session_dropdown.value), str(mouse))
|
|
54
|
+
|
|
55
|
+
if (previously_selected_value := global_state.get('selected_session')):
|
|
56
|
+
session_dropdown.value = previously_selected_value
|
|
57
|
+
update_selection()
|
|
58
|
+
|
|
59
|
+
console = ipw.Output()
|
|
60
|
+
with console:
|
|
61
|
+
if last_session := np_session.Mouse(selection.mouse).state.get('last_ttn_session'):
|
|
62
|
+
print(f"{mouse} last session: {last_session}")
|
|
63
|
+
print(f"Selected: {selection.session}")
|
|
64
|
+
|
|
65
|
+
def update(change):
|
|
66
|
+
if change["name"] != "value":
|
|
67
|
+
return
|
|
68
|
+
if (options := getattr(change["owner"], "options", None)) and change[
|
|
69
|
+
"new"
|
|
70
|
+
] not in options:
|
|
71
|
+
return
|
|
72
|
+
if change["new"] == change["old"]:
|
|
73
|
+
return
|
|
74
|
+
update_selection()
|
|
75
|
+
with console:
|
|
76
|
+
print(f"Selected: {selection.session}")
|
|
77
|
+
global_state['selected_session'] = selection.session.value
|
|
78
|
+
|
|
79
|
+
session_dropdown.observe(update, names='value')
|
|
80
|
+
|
|
81
|
+
IPython.display.display(ipw.VBox([session_dropdown, console]))
|
|
82
|
+
|
|
83
|
+
return selection
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import contextlib
|
|
3
|
+
import copy
|
|
4
|
+
import enum
|
|
5
|
+
import functools
|
|
6
|
+
from typing import ClassVar, Literal, NamedTuple, NoReturn, Optional, TypedDict
|
|
7
|
+
|
|
8
|
+
import IPython.display
|
|
9
|
+
import ipywidgets as ipw
|
|
10
|
+
import np_config
|
|
11
|
+
import np_logging
|
|
12
|
+
import np_session
|
|
13
|
+
import np_workflows
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
logger = np_logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TTNSession(enum.Enum):
|
|
20
|
+
"""Enum for the different TTN sessions available, each with different param sets."""
|
|
21
|
+
|
|
22
|
+
PRETEST = "pretest"
|
|
23
|
+
HAB_60 = "hab 60"
|
|
24
|
+
HAB_90 = "hab 90"
|
|
25
|
+
HAB_120 = "hab 120"
|
|
26
|
+
EPHYS = "ephys"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# setup parameters ---------------------------------------------------------------------
|
|
30
|
+
default_ttn_params = {}
|
|
31
|
+
|
|
32
|
+
def camstim_defaults() -> dict:
|
|
33
|
+
"""Try to load defaults from camstim config file on the Stim computer.
|
|
34
|
+
|
|
35
|
+
May encounter permission error if not running as svc_neuropix.
|
|
36
|
+
"""
|
|
37
|
+
with contextlib.suppress(OSError):
|
|
38
|
+
parser = configparser.RawConfigParser()
|
|
39
|
+
parser.read(
|
|
40
|
+
(np_config.Rig().paths["Camstim"].parent / "config" / "stim.cfg").as_posix()
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
camstim_default_config = {}
|
|
44
|
+
for section in parser.sections():
|
|
45
|
+
camstim_default_config[section] = {}
|
|
46
|
+
for k, v in parser[section].items():
|
|
47
|
+
try:
|
|
48
|
+
value = eval(
|
|
49
|
+
v
|
|
50
|
+
) # this removes comments in config and converts values to expected datatype
|
|
51
|
+
except:
|
|
52
|
+
continue
|
|
53
|
+
else:
|
|
54
|
+
camstim_default_config[section][k] = value
|
|
55
|
+
return camstim_default_config
|
|
56
|
+
logger.warning("Could not load camstim defaults from config file on Stim computer.")
|
|
57
|
+
return {}
|
|
58
|
+
|
|
59
|
+
## no longer added to default_ttn_params:
|
|
60
|
+
# default_ttn_params.update(**camstim_defaults())
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# main stimulus defaults ---------------------------------------------------------------
|
|
64
|
+
default_ttn_params["main"] = {}
|
|
65
|
+
|
|
66
|
+
default_ttn_params["main"]["sweepstim"] = {
|
|
67
|
+
"trigger_delay_sec": 5.0, #! does it matter?
|
|
68
|
+
'sync_sqr_loc': (870, 525), # for Window.warp=Warp.Disabled
|
|
69
|
+
'sync_sqr_loc_warp': (540, 329), # for Window.warp=Warp.Spherical
|
|
70
|
+
}
|
|
71
|
+
# default_ttn_params["main"]["movie_path"] = "C:/ProgramData/StimulusFiles/dev/"
|
|
72
|
+
default_ttn_params["main"][
|
|
73
|
+
"monitor"
|
|
74
|
+
] = 'Gamma1.Luminance50'
|
|
75
|
+
|
|
76
|
+
# other parameters that vary depending on session type (pretest, hab, ephys):
|
|
77
|
+
def per_session_main_stim_params(session: TTNSession) -> dict[str, Any]:
|
|
78
|
+
def build_session_stim_params(
|
|
79
|
+
key: str, old: int, reversed: int, annotated: int
|
|
80
|
+
) -> dict[str, dict[str, int]]:
|
|
81
|
+
return {key: dict(old=old, reversed=reversed, annotated=annotated)}
|
|
82
|
+
|
|
83
|
+
def session_stim_repeats(session: TTNSession) -> dict[str, dict[str, int]]:
|
|
84
|
+
repeats = functools.partial(build_session_stim_params, "stim_repeats")
|
|
85
|
+
match session:
|
|
86
|
+
case TTNSession.PRETEST:
|
|
87
|
+
return repeats(1, 1, 1)
|
|
88
|
+
case TTNSession.HAB_60:
|
|
89
|
+
return repeats(15, 5, 1)
|
|
90
|
+
case TTNSession.HAB_90:
|
|
91
|
+
return repeats(20, 7, 2)
|
|
92
|
+
case TTNSession.HAB_120 | TTNSession.EPHYS:
|
|
93
|
+
return repeats(25, 8, 2)
|
|
94
|
+
case _:
|
|
95
|
+
raise ValueError(f"Stim repeats not implemented for {session}")
|
|
96
|
+
|
|
97
|
+
def session_stim_lengths(session: TTNSession) -> dict[str, dict[str, int]]:
|
|
98
|
+
lengths_sec = functools.partial(build_session_stim_params, "stim_lengths_sec")
|
|
99
|
+
match session:
|
|
100
|
+
case TTNSession.PRETEST:
|
|
101
|
+
return lengths_sec(1, 1, 1)
|
|
102
|
+
case _:
|
|
103
|
+
return lengths_sec(40, 40, 60)
|
|
104
|
+
|
|
105
|
+
def main_blank_screen(session: TTNSession) -> dict[str, float | int]:
|
|
106
|
+
match session:
|
|
107
|
+
case TTNSession.PRETEST:
|
|
108
|
+
return {"pre_blank_screen_sec": .5, "post_blank_screen_sec": .5}
|
|
109
|
+
case _:
|
|
110
|
+
return {"pre_blank_screen_sec": 2, "post_blank_screen_sec": 2}
|
|
111
|
+
|
|
112
|
+
params = copy.deepcopy(default_ttn_params["main"])
|
|
113
|
+
params.update(session_stim_repeats(session))
|
|
114
|
+
params.update(session_stim_lengths(session))
|
|
115
|
+
params.update(main_blank_screen(session))
|
|
116
|
+
return params
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# optotagging defaults -----------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
default_ttn_params["opto"] = {}
|
|
122
|
+
|
|
123
|
+
# all parameters depend on session type (pretest, hab, ephys):
|
|
124
|
+
|
|
125
|
+
def per_session_opto_params(
|
|
126
|
+
session: TTNSession, mouse: str | int | np_session.Mouse
|
|
127
|
+
) -> dict[str, dict[str, str | list[float] | Literal["pretest", "experiment"]]]:
|
|
128
|
+
"All params for opto depending on session (e.g. will be empty for habs)."
|
|
129
|
+
|
|
130
|
+
def opto_mouse_id(mouse_id: str | int | np_session.Mouse) -> dict[str, str]:
|
|
131
|
+
return {"mouseID": str(mouse_id)}
|
|
132
|
+
|
|
133
|
+
def opto_levels(session: TTNSession) -> dict[str, list[float]]:
|
|
134
|
+
default_opto_levels: list[float] = camstim_defaults()["Optogenetics"][
|
|
135
|
+
"level_list"
|
|
136
|
+
]
|
|
137
|
+
match session:
|
|
138
|
+
case TTNSession.PRETEST | TTNSession.EPHYS:
|
|
139
|
+
return {"level_list": sorted(default_opto_levels)[-2:]}
|
|
140
|
+
case _:
|
|
141
|
+
raise ValueError(f"Opto levels not implemented for {session}")
|
|
142
|
+
|
|
143
|
+
def opto_operation_mode(
|
|
144
|
+
session: TTNSession,
|
|
145
|
+
) -> dict[str, Literal["pretest", "experiment"]]:
|
|
146
|
+
match session:
|
|
147
|
+
case TTNSession.PRETEST:
|
|
148
|
+
return {"operation_mode": "pretest"}
|
|
149
|
+
case TTNSession.EPHYS:
|
|
150
|
+
return {"operation_mode": "experiment"}
|
|
151
|
+
case _:
|
|
152
|
+
raise ValueError(f"Opto levels not implemented for {session}")
|
|
153
|
+
|
|
154
|
+
match session:
|
|
155
|
+
case TTNSession.PRETEST | TTNSession.EPHYS:
|
|
156
|
+
params = copy.deepcopy(default_ttn_params["opto"])
|
|
157
|
+
params.update(opto_mouse_id(mouse))
|
|
158
|
+
params.update(opto_levels(session))
|
|
159
|
+
params.update(opto_operation_mode(session))
|
|
160
|
+
return params
|
|
161
|
+
case TTNSession.HAB_60 | TTNSession.HAB_90 | TTNSession.HAB_120:
|
|
162
|
+
return {}
|
|
163
|
+
case _:
|
|
164
|
+
raise ValueError(f"Opto params not implemented for {session}")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# mapping defaults ---------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
default_ttn_params["mapping"] = {}
|
|
170
|
+
|
|
171
|
+
default_ttn_params["mapping"]["monitor"] = "Gamma1.Luminance50"
|
|
172
|
+
default_ttn_params["mapping"]["gabor_path"] = "gabor_20_deg_250ms.stim"
|
|
173
|
+
default_ttn_params["mapping"]["flash_path"] = "flash_250ms.stim" # relpath in StimulusFiles dir (for _v2 objects)
|
|
174
|
+
default_ttn_params["mapping"]["default_gabor_duration_seconds"] = 1200
|
|
175
|
+
default_ttn_params["mapping"]["default_flash_duration_seconds"] = 300 # may be overriden by 'max_total_duration_minutes'
|
|
176
|
+
|
|
177
|
+
default_ttn_params["mapping"]["sweepstim"] = {
|
|
178
|
+
'sync_sqr_loc_warp': (540, 329), # for Window.warp=Warp.Spherical
|
|
179
|
+
}
|
|
180
|
+
# trigger_delay_sec not specified
|
|
181
|
+
# all stim parameters depend on session type (pretest, hab, ephys):
|
|
182
|
+
def per_session_mapping_params(session: TTNSession) -> dict[str, dict[str, int]]:
|
|
183
|
+
"`'mapping'` key in params dict should be updated with the returned dict (which will be empty for habs)."
|
|
184
|
+
|
|
185
|
+
def mapping_duration(session: TTNSession) -> dict[str, float]:
|
|
186
|
+
# 0 = full length = gabor_duration + flash_duration = maximum possible
|
|
187
|
+
match session:
|
|
188
|
+
case TTNSession.PRETEST:
|
|
189
|
+
return {"max_total_duration_minutes": 0.1}
|
|
190
|
+
case TTNSession.EPHYS | TTNSession.HAB_120:
|
|
191
|
+
return {"max_total_duration_minutes": 10}
|
|
192
|
+
case _:
|
|
193
|
+
raise ValueError(f"Mapping params not implemented for {session}")
|
|
194
|
+
|
|
195
|
+
def mapping_blank_screen(session: TTNSession) -> dict[str, float | int]:
|
|
196
|
+
match session:
|
|
197
|
+
case TTNSession.PRETEST:
|
|
198
|
+
return {"pre_blank_screen_sec": .5, "post_blank_screen_sec": .5}
|
|
199
|
+
case TTNSession.EPHYS | TTNSession.HAB_120:
|
|
200
|
+
return {"pre_blank_screen_sec": 2, "post_blank_screen_sec": 2}
|
|
201
|
+
case _:
|
|
202
|
+
raise ValueError(f"Mapping params not implemented for {session}")
|
|
203
|
+
|
|
204
|
+
match session:
|
|
205
|
+
case TTNSession.PRETEST | TTNSession.EPHYS | TTNSession.HAB_120:
|
|
206
|
+
params = copy.deepcopy(default_ttn_params["mapping"])
|
|
207
|
+
params.update(mapping_duration(session))
|
|
208
|
+
params.update(mapping_blank_screen(session))
|
|
209
|
+
return params
|
|
210
|
+
case TTNSession.HAB_60 | TTNSession.HAB_90:
|
|
211
|
+
return {}
|
|
212
|
+
case _:
|
|
213
|
+
raise ValueError(f"Mapping params not implemented for {session}")
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import configparser
|
|
4
|
+
import contextlib
|
|
5
|
+
import copy
|
|
6
|
+
import dataclasses
|
|
7
|
+
import datetime
|
|
8
|
+
import enum
|
|
9
|
+
import functools
|
|
10
|
+
import pathlib
|
|
11
|
+
import platform
|
|
12
|
+
import shutil
|
|
13
|
+
import threading
|
|
14
|
+
import time
|
|
15
|
+
import zlib
|
|
16
|
+
from typing import ClassVar, Literal, NamedTuple, NoReturn, Optional, Type, TypedDict
|
|
17
|
+
|
|
18
|
+
import IPython
|
|
19
|
+
import IPython.display
|
|
20
|
+
import ipywidgets as ipw
|
|
21
|
+
import np_config
|
|
22
|
+
import np_logging
|
|
23
|
+
import np_services
|
|
24
|
+
import np_session
|
|
25
|
+
import np_workflows
|
|
26
|
+
import fabric
|
|
27
|
+
import PIL.Image
|
|
28
|
+
import pydantic
|
|
29
|
+
from pyparsing import Any
|
|
30
|
+
from np_services import (
|
|
31
|
+
Service,
|
|
32
|
+
Finalizable,
|
|
33
|
+
ScriptCamstim,
|
|
34
|
+
OpenEphys,
|
|
35
|
+
Sync,
|
|
36
|
+
ImageMVR,
|
|
37
|
+
VideoMVR,
|
|
38
|
+
NewScaleCoordinateRecorder,
|
|
39
|
+
MouseDirector,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
from np_workflows.shared import base_experiments
|
|
43
|
+
|
|
44
|
+
logger = np_logging.getLogger(__name__)
|
|
45
|
+
|
|
46
|
+
class TempletonPilot(base_experiments.DynamicRoutingExperiment):
|
|
47
|
+
"""Provides project-specific methods and attributes, mainly related to camstim scripts."""
|
|
48
|
+
|
|
49
|
+
default_session_subclass = np_session.TempletonPilotSession
|
|
50
|
+
|
|
51
|
+
workflow: base_experiments.DynamicRoutingExperiment.Workflow
|
|
52
|
+
"""Enum for workflow type, e.g. PRETEST, HAB_AUD, HAB_VIS, EPHYS_ etc."""
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def task_name(self) -> str:
|
|
56
|
+
task_name = super().task_name
|
|
57
|
+
return f'templeton {task_name}' if 'templeton' not in task_name else task_name
|
|
58
|
+
|
|
59
|
+
@task_name.setter
|
|
60
|
+
def task_name(self, value: str):
|
|
61
|
+
super().task_name = value
|
|
62
|
+
|
|
63
|
+
def new_experiment(
|
|
64
|
+
mouse: int | str | np_session.Mouse,
|
|
65
|
+
user: str | np_session.User,
|
|
66
|
+
workflow: base_experiments.DynamicRoutingExperiment.Workflow,
|
|
67
|
+
) -> TempletonPilot:
|
|
68
|
+
"""Create a new experiment for the given mouse and user."""
|
|
69
|
+
experiment: Ephys | Hab
|
|
70
|
+
if any(tag in workflow.name for tag in ('EPHYS', 'PRETEST')):
|
|
71
|
+
experiment = Ephys(mouse, user)
|
|
72
|
+
elif 'HAB' in workflow.name:
|
|
73
|
+
experiment = Hab(mouse, user)
|
|
74
|
+
else:
|
|
75
|
+
raise ValueError(f"Unknown {workflow = }. Create an experiment with e.g.\n\n\texperiment = Ephys(mouse, user)\nexperiment.session.npexp_path.mkdir()")
|
|
76
|
+
experiment.workflow = workflow
|
|
77
|
+
experiment.log(f"{experiment} created")
|
|
78
|
+
experiment.session.npexp_path.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
return experiment
|
|
80
|
+
|
|
81
|
+
class Hab(TempletonPilot):
|
|
82
|
+
def __init__(self, *args, **kwargs):
|
|
83
|
+
self.services = (
|
|
84
|
+
MouseDirector,
|
|
85
|
+
Sync,
|
|
86
|
+
VideoMVR,
|
|
87
|
+
self.imager,
|
|
88
|
+
ScriptCamstim,
|
|
89
|
+
NewScaleCoordinateRecorder,
|
|
90
|
+
)
|
|
91
|
+
super().__init__(*args, **kwargs)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Ephys(TempletonPilot):
|
|
95
|
+
def __init__(self, *args, **kwargs):
|
|
96
|
+
self.services = (
|
|
97
|
+
MouseDirector,
|
|
98
|
+
Sync,
|
|
99
|
+
VideoMVR,
|
|
100
|
+
self.imager,
|
|
101
|
+
ScriptCamstim,
|
|
102
|
+
OpenEphys,
|
|
103
|
+
NewScaleCoordinateRecorder,
|
|
104
|
+
)
|
|
105
|
+
super().__init__(*args, **kwargs)
|