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