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.
Files changed (76) hide show
  1. np_workflows/__init__.py +7 -0
  2. np_workflows/assets/images/logo_np_hab.png +0 -0
  3. np_workflows/assets/images/logo_np_vis.png +0 -0
  4. np_workflows/experiments/__init__.py +1 -0
  5. np_workflows/experiments/dynamic_routing/__init__.py +2 -0
  6. np_workflows/experiments/dynamic_routing/main.py +117 -0
  7. np_workflows/experiments/dynamic_routing/widgets.py +82 -0
  8. np_workflows/experiments/openscope_P3/P3_workflow_widget.py +83 -0
  9. np_workflows/experiments/openscope_P3/__init__.py +2 -0
  10. np_workflows/experiments/openscope_P3/main_P3_pilot.py +217 -0
  11. np_workflows/experiments/openscope_barcode/__init__.py +2 -0
  12. np_workflows/experiments/openscope_barcode/barcode_workflow_widget.py +83 -0
  13. np_workflows/experiments/openscope_barcode/camstim_scripts/barcode_mapping_script.py +138 -0
  14. np_workflows/experiments/openscope_barcode/camstim_scripts/barcode_opto_script.py +219 -0
  15. np_workflows/experiments/openscope_barcode/main_barcode_pilot.py +217 -0
  16. np_workflows/experiments/openscope_loop/__init__.py +2 -0
  17. np_workflows/experiments/openscope_loop/camstim_scripts/barcode_mapping_script.py +138 -0
  18. np_workflows/experiments/openscope_loop/camstim_scripts/barcode_opto_script.py +219 -0
  19. np_workflows/experiments/openscope_loop/loop_workflow_widget.py +83 -0
  20. np_workflows/experiments/openscope_loop/main_loop_pilot.py +217 -0
  21. np_workflows/experiments/openscope_psycode/__init__.py +2 -0
  22. np_workflows/experiments/openscope_psycode/main_psycode_pilot.py +217 -0
  23. np_workflows/experiments/openscope_psycode/psycode_workflow_widget.py +83 -0
  24. np_workflows/experiments/openscope_v2/__init__.py +2 -0
  25. np_workflows/experiments/openscope_v2/main_v2_pilot.py +217 -0
  26. np_workflows/experiments/openscope_v2/v2_workflow_widget.py +83 -0
  27. np_workflows/experiments/openscope_vippo/__init__.py +2 -0
  28. np_workflows/experiments/openscope_vippo/main_vippo_pilot.py +217 -0
  29. np_workflows/experiments/openscope_vippo/vippo_workflow_widget.py +83 -0
  30. np_workflows/experiments/task_trained_network/__init__.py +2 -0
  31. np_workflows/experiments/task_trained_network/camstim_scripts/make_tt_stims.py +23 -0
  32. np_workflows/experiments/task_trained_network/camstim_scripts/oct22_tt_stim_script.py +69 -0
  33. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_00.stim +5 -0
  34. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_01.stim +5 -0
  35. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_02.stim +5 -0
  36. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_03.stim +5 -0
  37. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_04.stim +5 -0
  38. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_05.stim +5 -0
  39. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_06.stim +5 -0
  40. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_07.stim +5 -0
  41. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_08.stim +5 -0
  42. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_09.stim +5 -0
  43. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_10.stim +5 -0
  44. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_11.stim +5 -0
  45. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_12.stim +5 -0
  46. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_13.stim +5 -0
  47. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_14.stim +5 -0
  48. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_15.stim +5 -0
  49. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_16.stim +5 -0
  50. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_17.stim +5 -0
  51. np_workflows/experiments/task_trained_network/camstim_scripts/stims/densely_annotated_18.stim +5 -0
  52. np_workflows/experiments/task_trained_network/camstim_scripts/stims/flash_250ms.stim +20 -0
  53. np_workflows/experiments/task_trained_network/camstim_scripts/stims/gabor_20_deg_250ms.stim +30 -0
  54. np_workflows/experiments/task_trained_network/camstim_scripts/stims/old_stim.stim +5 -0
  55. np_workflows/experiments/task_trained_network/camstim_scripts/stims/shuffle_reversed.stim +5 -0
  56. np_workflows/experiments/task_trained_network/camstim_scripts/stims/shuffle_reversed_1st.stim +5 -0
  57. np_workflows/experiments/task_trained_network/camstim_scripts/stims/shuffle_reversed_2nd.stim +5 -0
  58. np_workflows/experiments/task_trained_network/camstim_scripts/ttn_main_script.py +130 -0
  59. np_workflows/experiments/task_trained_network/camstim_scripts/ttn_mapping_script.py +138 -0
  60. np_workflows/experiments/task_trained_network/camstim_scripts/ttn_opto_script.py +219 -0
  61. np_workflows/experiments/task_trained_network/main_ttn_pilot.py +263 -0
  62. np_workflows/experiments/task_trained_network/ttn_session_widget.py +83 -0
  63. np_workflows/experiments/task_trained_network/ttn_stim_config.py +213 -0
  64. np_workflows/experiments/templeton/__init__.py +2 -0
  65. np_workflows/experiments/templeton/main.py +105 -0
  66. np_workflows/experiments/templeton/widgets.py +82 -0
  67. np_workflows/shared/__init__.py +3 -0
  68. np_workflows/shared/base_experiments.py +826 -0
  69. np_workflows/shared/camstim_scripts/flash_250ms.stim +20 -0
  70. np_workflows/shared/camstim_scripts/gabor_20_deg_250ms.stim +30 -0
  71. np_workflows/shared/npxc.py +187 -0
  72. np_workflows/shared/widgets.py +705 -0
  73. np_workflows-1.6.89.dist-info/METADATA +85 -0
  74. np_workflows-1.6.89.dist-info/RECORD +76 -0
  75. np_workflows-1.6.89.dist-info/WHEEL +4 -0
  76. 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,2 @@
1
+ from np_workflows.experiments.templeton.main import *
2
+ from np_workflows.experiments.templeton.widgets import *
@@ -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)