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