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,7 @@
1
+ import np_workflows.shared.npxc as npxc
2
+ import np_workflows.shared.widgets as widgets
3
+ import np_workflows.shared.base_experiments as base_experiments
4
+
5
+ import np_workflows.experiments as experiments
6
+
7
+ from np_workflows.shared import *
@@ -0,0 +1 @@
1
+ from np_workflows.experiments import *
@@ -0,0 +1,2 @@
1
+ from np_workflows.experiments.dynamic_routing.main import *
2
+ from np_workflows.experiments.dynamic_routing.widgets import *
@@ -0,0 +1,117 @@
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
+ def new_experiment(
47
+ mouse: int | str | np_session.Mouse,
48
+ user: str | np_session.User,
49
+ workflow: base_experiments.DynamicRoutingExperiment.Workflow,
50
+ ) -> DRTask:
51
+ """Create a new experiment for the given mouse and user."""
52
+ experiment: DRTask
53
+ if any(tag in workflow.name for tag in ('EPHYS', 'PRETEST')):
54
+ experiment = Ephys(mouse, user)
55
+ elif 'HAB' in workflow.name:
56
+ experiment = Hab(mouse, user)
57
+ elif 'OPTO' in workflow.name:
58
+ experiment = Opto(mouse, user)
59
+ elif 'TRAINING' in workflow.name:
60
+ experiment = Training(mouse, user)
61
+ else:
62
+ raise ValueError(f"Unknown {workflow = }. Create an experiment with e.g.\n\n\texperiment = Ephys(mouse, user)\nexperiment.session.npexp_path.mkdir()")
63
+ experiment.workflow = workflow
64
+ experiment.log(f"{experiment} created")
65
+ experiment.session.npexp_path.mkdir(parents=True, exist_ok=True)
66
+ return experiment
67
+
68
+ class DRTask(base_experiments.DynamicRoutingExperiment):
69
+ """Provides project-specific methods and attributes, mainly related to camstim scripts."""
70
+
71
+ default_session_subclass = np_session.DRPilotSession
72
+
73
+ workflow: base_experiments.DynamicRoutingExperiment.Workflow
74
+ """Enum for workflow type, e.g. PRETEST, HAB_AUD, HAB_VIS, EPHYS_ etc."""
75
+
76
+ class Hab(DRTask):
77
+ def __init__(self, *args, **kwargs):
78
+ self.services = (
79
+ MouseDirector,
80
+ Sync,
81
+ VideoMVR,
82
+ ScriptCamstim,
83
+ )
84
+ super().__init__(*args, **kwargs)
85
+
86
+ class Opto(DRTask):
87
+ def __init__(self, *args, **kwargs):
88
+ self.services = (
89
+ MouseDirector,
90
+ Sync,
91
+ VideoMVR,
92
+ ScriptCamstim,
93
+ )
94
+ super().__init__(*args, **kwargs)
95
+
96
+
97
+ class Ephys(DRTask):
98
+ def __init__(self, *args, **kwargs):
99
+ self.services = (
100
+ MouseDirector,
101
+ Sync,
102
+ VideoMVR,
103
+ NewScaleCoordinateRecorder,
104
+ ScriptCamstim,
105
+ OpenEphys,
106
+ )
107
+ super().__init__(*args, **kwargs)
108
+
109
+ class Training(DRTask):
110
+ def __init__(self, *args, **kwargs):
111
+ self.services = (
112
+ MouseDirector,
113
+ Sync,
114
+ VideoMVR,
115
+ ScriptCamstim,
116
+ )
117
+ super().__init__(*args, **kwargs)
@@ -0,0 +1,82 @@
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 np_workflows.experiments.dynamic_routing.main import Ephys, Hab, DRTask
17
+ from np_workflows.shared.base_experiments import DynamicRoutingExperiment
18
+
19
+
20
+ # for widget, before creating a experiment --------------------------------------------- #
21
+
22
+
23
+ class SelectedWorkflow:
24
+ def __init__(self, workflow: str | DynamicRoutingExperiment.Workflow, mouse: str | int | np_session.Mouse):
25
+ if isinstance(workflow, str):
26
+ workflow = DynamicRoutingExperiment.Workflow[workflow] # uses enum name (not value)
27
+ self.workflow = workflow
28
+ self.mouse = str(mouse)
29
+
30
+ def __repr__(self) -> str:
31
+ return f"{self.__class__.__name__}({self.workflow}, {self.mouse})"
32
+
33
+
34
+ def workflow_select_widget(
35
+ mouse: str | int | np_session.Mouse,
36
+ ) -> SelectedWorkflow:
37
+ """Select a session type to run (hab, pretest, ephys).
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
+ # set default
45
+ selection = SelectedWorkflow(DynamicRoutingExperiment.Workflow.PRETEST, mouse)
46
+
47
+ workflow_dropdown = ipw.Select(
48
+ options=tuple(_.name for _ in DynamicRoutingExperiment.Workflow),
49
+ description="Workflow",
50
+ )
51
+ workflow_descriptions = ipw.Select(
52
+ options=tuple(_.value for _ in DynamicRoutingExperiment.Workflow),
53
+ disabled=True,
54
+ value=None,
55
+ )
56
+ console = ipw.Output()
57
+ with console:
58
+ if last_workflow := np_session.Mouse(selection.mouse).state.get('last_workflow'):
59
+ print(f"{mouse} last workflow: {last_workflow}\t({np_session.Mouse(selection.mouse).state.get('last_session')})")
60
+ print(f"Selected: {selection.workflow.name}")
61
+
62
+ def update(change):
63
+ if change["name"] != "value":
64
+ return
65
+ if (options := getattr(change["owner"], "options", None)) and change[
66
+ "new"
67
+ ] not in options:
68
+ return
69
+ if change["new"] == change["old"]:
70
+ return
71
+ selection.__init__(str(workflow_dropdown.value), mouse.id if isinstance(mouse, np_session.Mouse) else str(mouse))
72
+ with console:
73
+ print(f"Selected: {selection.workflow}")
74
+ workflow_dropdown.observe(update, names='value')
75
+
76
+ IPython.display.display(ipw.VBox([ipw.HBox([workflow_dropdown, workflow_descriptions]), console]))
77
+
78
+ return selection
79
+
80
+
81
+ def photodoc_widget(session: np_session.Session, reminder: str) -> None:
82
+ print(f'Take an image in Vimba Viewer and save as:\n{session.npexp_path.name}_{reminder}.png')
@@ -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 np_workflows.experiments.openscope_P3.main_P3_pilot import P3Session
17
+
18
+ global_state = {}
19
+ """Global variable for persisting widget states."""
20
+
21
+ # for widget, before creating a experiment --------------------------------------------- #
22
+
23
+ class SelectedSession:
24
+ def __init__(self, session: str | P3Session, mouse: str | int | np_session.Mouse):
25
+ if isinstance(session, str):
26
+ session = P3Session(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 P3_workflow_widget(
35
+ mouse: str | int | np_session.Mouse,
36
+ ) -> SelectedSession:
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 = SelectedSession(P3Session.PRETEST, mouse)
46
+
47
+ session_dropdown = ipw.Select(
48
+ options=tuple(_.value for _ in P3Session),
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_P3_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,2 @@
1
+ from .main_P3_pilot import new_experiment, Hab, Ephys, validate_selected_workflow
2
+ from .P3_workflow_widget import P3_workflow_widget
@@ -0,0 +1,217 @@
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, SessionCamstim,
31
+ SessionCamstim,
32
+ OpenEphys,
33
+ Sync,
34
+ VideoMVR,
35
+ NewScaleCoordinateRecorder,
36
+ MouseDirector,
37
+ )
38
+
39
+ logger = np_logging.getLogger(__name__)
40
+
41
+
42
+ class P3Session(enum.Enum):
43
+ """Enum for the different sessions available, each with different param sets."""
44
+
45
+ PRETEST = "pretest"
46
+ HAB = "hab"
47
+ EPHYS = "ephys"
48
+
49
+
50
+ class P3Mixin:
51
+ """Provides project-specific methods and attributes, mainly related to camstim scripts."""
52
+
53
+ workflow: P3Session
54
+ """Enum for particular workflow/session, e.g. PRETEST, HAB_60, HAB_90,
55
+ EPHYS."""
56
+
57
+ session: np_session.PipelineSession
58
+ mouse: np_session.Mouse
59
+ user: np_session.User
60
+ platform_json: np_session.PlatformJson
61
+
62
+ @property
63
+ def recorders(self) -> tuple[Service, ...]:
64
+ """Services to be started before stimuli run, and stopped after. Session-dependent."""
65
+ match self.workflow:
66
+ case P3Session.PRETEST | P3Session.EPHYS:
67
+ return (Sync, VideoMVR, OpenEphys)
68
+ case P3Session.HAB:
69
+ return (Sync, VideoMVR)
70
+
71
+ @property
72
+ def stims(self) -> tuple[Service, ...]:
73
+ return (SessionCamstim, )
74
+
75
+ def initialize_and_test_services(self) -> None:
76
+ """Configure, initialize (ie. reset), then test all services."""
77
+
78
+ MouseDirector.user = self.user.id
79
+ MouseDirector.mouse = self.mouse.id
80
+
81
+ OpenEphys.folder = self.session.folder
82
+
83
+ NewScaleCoordinateRecorder.log_root = self.session.npexp_path
84
+ NewScaleCoordinateRecorder.log_name = self.platform_json.path.name
85
+
86
+ SessionCamstim.labtracks_mouse_id = self.mouse.id
87
+ SessionCamstim.lims_user_id = self.user.id
88
+
89
+ self.configure_services()
90
+
91
+ super().initialize_and_test_services()
92
+
93
+ def update_state(self) -> None:
94
+ "Store useful but non-essential info."
95
+ self.mouse.state['last_session'] = self.session.id
96
+ self.mouse.state['last_P3_session'] = str(self.workflow)
97
+ if self.mouse == 366122:
98
+ return
99
+ match self.workflow:
100
+ case P3Session.PRETEST:
101
+ return
102
+ case P3Session.HAB:
103
+ self.session.project.state['latest_hab'] = self.session.id
104
+ case P3Session.EPHYS:
105
+ self.session.project.state['latest_ephys'] = self.session.id
106
+ self.session.project.state['sessions'] = self.session.project.state.get('sessions', []) + [self.session.id]
107
+
108
+ def run_stim(self) -> None:
109
+
110
+ self.update_state()
111
+
112
+ if not SessionCamstim.is_ready_to_start():
113
+ raise RuntimeError("SessionCamstim is not ready to start.")
114
+
115
+ np_logging.web(f'P3_{self.workflow.name.lower()}').info(f"Started session {self.mouse.mtrain.stage['name']}")
116
+ SessionCamstim.start()
117
+
118
+ with contextlib.suppress(Exception):
119
+ while not SessionCamstim.is_ready_to_start():
120
+ time.sleep(2.5)
121
+
122
+ if isinstance(SessionCamstim, Finalizable):
123
+ SessionCamstim.finalize()
124
+
125
+ with contextlib.suppress(Exception):
126
+ np_logging.web(f'P3_{self.workflow.name.lower()}').info(f"Finished session {self.mouse.mtrain.stage['name']}")
127
+
128
+
129
+ def copy_data_files(self) -> None:
130
+ super().copy_data_files()
131
+
132
+ # When all processing completes, camstim Agent class passes data and uuid to
133
+ # /camstim/lims BehaviorSession class, and write_behavior_data() writes a
134
+ # final .pkl with default name YYYYMMDDSSSS_mouseID_foragingID.pkl
135
+ # - if we have a foraging ID, we can search for that
136
+ if None == (stim_pkl := next(self.session.npexp_path.glob(f'{self.session.date:%y%m%d}*_{self.session.mouse}_*.pkl'), None)):
137
+ logger.warning('Did not find stim file on npexp matching the format `YYYYMMDDSSSS_mouseID_foragingID.pkl`')
138
+ return
139
+ assert stim_pkl
140
+ if not self.session.platform_json.foraging_id:
141
+ self.session.platform_json.foraging_id = stim_pkl.stem.split('_')[-1]
142
+ new_stem = f'{self.session.folder}.stim'
143
+ logger.debug(f'Renaming stim file copied to npexp: {stim_pkl} -> {new_stem}')
144
+ stim_pkl = stim_pkl.rename(stim_pkl.with_stem(new_stem))
145
+
146
+ # remove other stim pkl, which is nearly identical, if it was also copied
147
+ for pkl in self.session.npexp_path.glob('*.pkl'):
148
+ if (
149
+ self.session.folder not in pkl.stem
150
+ and
151
+ abs(pkl.stat().st_size - stim_pkl.stat().st_size) < 1e6
152
+ ):
153
+ logger.debug(f'Deleting extra stim pkl copied to npexp: {pkl.stem}')
154
+ pkl.unlink()
155
+
156
+
157
+ def validate_selected_workflow(session: P3Session, mouse: np_session.Mouse) -> None:
158
+ for workflow in ('hab', 'ephys'):
159
+ if (
160
+ workflow in session.value.lower()
161
+ and workflow not in mouse.mtrain.stage['name'].lower()
162
+ ) or (
163
+ session.value.lower() == 'ephys' and 'hab' in mouse.mtrain.stage['name'].lower()
164
+ ):
165
+ raise ValueError(f"Workflow selected ({session.value}) does not match MTrain stage ({mouse.mtrain.stage['name']}): please check cells above.")
166
+
167
+
168
+ class Hab(P3Mixin, np_workflows.PipelineHab):
169
+ def __init__(self, *args, **kwargs):
170
+ self.services = (
171
+ MouseDirector,
172
+ Sync,
173
+ VideoMVR,
174
+ self.imager,
175
+ NewScaleCoordinateRecorder,
176
+ SessionCamstim,
177
+ )
178
+ super().__init__(*args, **kwargs)
179
+
180
+
181
+ class Ephys(P3Mixin, np_workflows.PipelineEphys):
182
+ def __init__(self, *args, **kwargs):
183
+ self.services = (
184
+ MouseDirector,
185
+ Sync,
186
+ VideoMVR,
187
+ self.imager,
188
+ NewScaleCoordinateRecorder,
189
+ SessionCamstim,
190
+ OpenEphys,
191
+ )
192
+ super().__init__(*args, **kwargs)
193
+
194
+
195
+ # --------------------------------------------------------------------------------------
196
+
197
+
198
+ def new_experiment(
199
+ mouse: int | str | np_session.Mouse,
200
+ user: str | np_session.User,
201
+ workflow: P3Session,
202
+ ) -> Ephys | Hab:
203
+ """Create a new experiment for the given mouse and user."""
204
+ match workflow:
205
+ case P3Session.PRETEST | P3Session.EPHYS:
206
+ experiment = Ephys(mouse, user)
207
+ case P3Session.HAB:
208
+ experiment = Hab(mouse, user)
209
+ case _:
210
+ raise ValueError(f"Invalid workflow type: {workflow}")
211
+ experiment.workflow = workflow
212
+
213
+ with contextlib.suppress(Exception):
214
+ np_logging.web(f'P3_{experiment.workflow.name.lower()}').info(f"{experiment} created")
215
+
216
+ return experiment
217
+
@@ -0,0 +1,2 @@
1
+ from .main_barcode_pilot import new_experiment, Hab, Ephys, validate_selected_workflow
2
+ from .barcode_workflow_widget import barcode_workflow_widget
@@ -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 np_workflows.experiments.openscope_barcode.main_barcode_pilot import BarcodeSession
17
+
18
+ global_state = {}
19
+ """Global variable for persisting widget states."""
20
+
21
+ # for widget, before creating a experiment --------------------------------------------- #
22
+
23
+ class SelectedSession:
24
+ def __init__(self, session: str | BarcodeSession, mouse: str | int | np_session.Mouse):
25
+ if isinstance(session, str):
26
+ session = BarcodeSession(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 barcode_workflow_widget(
35
+ mouse: str | int | np_session.Mouse,
36
+ ) -> SelectedSession:
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 = SelectedSession(BarcodeSession.PRETEST, mouse)
46
+
47
+ session_dropdown = ipw.Select(
48
+ options=tuple(_.value for _ in BarcodeSession),
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_barcode_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