sl-shared-assets 1.0.0rc1__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.
Potentially problematic release.
This version of sl-shared-assets might be problematic. Click here for more details.
- sl_shared_assets/__init__.py +96 -0
- sl_shared_assets/__init__.pyi +87 -0
- sl_shared_assets/cli.py +72 -0
- sl_shared_assets/cli.pyi +17 -0
- sl_shared_assets/data_classes.py +1435 -0
- sl_shared_assets/data_classes.pyi +646 -0
- sl_shared_assets/packaging_tools.py +133 -0
- sl_shared_assets/packaging_tools.pyi +52 -0
- sl_shared_assets/py.typed +0 -0
- sl_shared_assets/server.py +293 -0
- sl_shared_assets/server.pyi +112 -0
- sl_shared_assets/suite2p.py +449 -0
- sl_shared_assets/suite2p.pyi +188 -0
- sl_shared_assets/transfer_tools.py +119 -0
- sl_shared_assets/transfer_tools.pyi +53 -0
- sl_shared_assets-1.0.0rc1.dist-info/METADATA +849 -0
- sl_shared_assets-1.0.0rc1.dist-info/RECORD +20 -0
- sl_shared_assets-1.0.0rc1.dist-info/WHEEL +4 -0
- sl_shared_assets-1.0.0rc1.dist-info/entry_points.txt +3 -0
- sl_shared_assets-1.0.0rc1.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,1435 @@
|
|
|
1
|
+
"""This module provides classes used to store various data used by the sl-experiment and the sl-forgery libraries.
|
|
2
|
+
This includes classes used to store the data generated during acquisition and preprocessing and classes used to manage
|
|
3
|
+
the runtime of other libraries (configuration data classes)."""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import copy
|
|
7
|
+
import shutil as sh
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import warnings
|
|
10
|
+
from dataclasses import field, dataclass
|
|
11
|
+
|
|
12
|
+
import appdirs
|
|
13
|
+
from ataraxis_base_utilities import LogLevel, console, ensure_directory_exists
|
|
14
|
+
from ataraxis_data_structures import YamlConfig
|
|
15
|
+
from ataraxis_time.time_helpers import get_timestamp
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def replace_root_path(path: Path) -> None:
|
|
19
|
+
"""Replaces the path to the local root directory used to store all Sun lab projects with the provided path.
|
|
20
|
+
|
|
21
|
+
When ProjectConfiguration class is instantiated for the first time on a new machine, it asks the user to provide
|
|
22
|
+
the path to the local directory where to save all Sun lab projects. This path is then stored inside the default
|
|
23
|
+
user data directory as a .yaml file to be reused for all future projects. To support replacing this path without
|
|
24
|
+
searching for the user data directory, which is usually hidden, this function finds and updates the contents of the
|
|
25
|
+
file that stores the local root path.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
path: The path to the new local root directory.
|
|
29
|
+
"""
|
|
30
|
+
# Resolves the path to the static .txt file used to store the local path to the root directory
|
|
31
|
+
app_dir = Path(appdirs.user_data_dir(appname="sun_lab_data", appauthor="sun_lab"))
|
|
32
|
+
path_file = app_dir.joinpath("root_path.txt")
|
|
33
|
+
|
|
34
|
+
# In case this function is called before the app directory is created, ensures the app directory exists
|
|
35
|
+
ensure_directory_exists(path_file)
|
|
36
|
+
|
|
37
|
+
# Ensures that the input root directory exists
|
|
38
|
+
ensure_directory_exists(path)
|
|
39
|
+
|
|
40
|
+
# Replaces the contents of the root_path.txt file with the provided path
|
|
41
|
+
with open(path_file, "w") as f:
|
|
42
|
+
f.write(str(path))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass()
|
|
46
|
+
class ProjectConfiguration(YamlConfig):
|
|
47
|
+
"""Stores the project-specific configuration parameters that do not change between different animals and runtime
|
|
48
|
+
sessions.
|
|
49
|
+
|
|
50
|
+
An instance of this class is generated and saved as a .yaml file in the 'configuration' directory of each project
|
|
51
|
+
when it is created. After that, the stored data is reused for every runtime (training or experiment session) carried
|
|
52
|
+
out for each animal of the project.
|
|
53
|
+
|
|
54
|
+
Notes:
|
|
55
|
+
This class allows flexibly configuring sl_experiment and sl_forgery libraries for different projects in the
|
|
56
|
+
Sun lab. This allows hiding most inner workings of all libraries from the end-users, while providing a robust,
|
|
57
|
+
machine-independent way to interface with all data acquisition and processing libraries.
|
|
58
|
+
|
|
59
|
+
Most lab projects only need to adjust the "surgery_sheet_id" and "water_log_sheet_id" fields of the class.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
project_name: str = ""
|
|
63
|
+
"""Stores the descriptive name of the project. This name is used to create the root directory for the project and
|
|
64
|
+
to discover and load project's data during runtime."""
|
|
65
|
+
surgery_sheet_id: str = ""
|
|
66
|
+
"""The ID of the Google Sheet file that stores surgery information for the animal whose data is managed by this
|
|
67
|
+
instance. This is used to parse and write the surgery data for each managed animal into its 'metadata' folder, so
|
|
68
|
+
that the surgery data is always kept together with the rest of the training and experiment data."""
|
|
69
|
+
water_log_sheet_id: str = ""
|
|
70
|
+
"""The ID of the Google Sheet file that stores water restriction information for the animal whose data is managed
|
|
71
|
+
by this instance. This is used to synchronize the information inside the water restriction log with the state of
|
|
72
|
+
the animal at the end of each training or experiment session.
|
|
73
|
+
"""
|
|
74
|
+
google_credentials_path: str | Path = Path("/media/Data/Experiments/sl-surgery-log-0f651e492767.json")
|
|
75
|
+
"""
|
|
76
|
+
The path to the locally stored .JSON file that contains the service account credentials used to read and write
|
|
77
|
+
Google Sheet data. This is used to access and work with the surgery log and the water restriction log. Usually, the
|
|
78
|
+
same service account is used across all projects.
|
|
79
|
+
"""
|
|
80
|
+
server_credentials_path: str | Path = Path("/media/Data/Experiments/server_credentials.yaml")
|
|
81
|
+
"""
|
|
82
|
+
The path to the locally stored .YAML file that contains the credentials for accessing the BioHPC server machine.
|
|
83
|
+
While the storage (filesystem) of the server machine should already be mounted to the local PC via SMB, this data
|
|
84
|
+
is used to establish SSH connection to the machine and start data processing after it is transferred to the server.
|
|
85
|
+
This way, our data acquisition, preprocessing, and processing are controlled by the same runtime.
|
|
86
|
+
"""
|
|
87
|
+
local_root_directory: str | Path = Path("/media/Data/Experiments")
|
|
88
|
+
"""The absolute path to the root directory where all projects are stored on the local host-machine (VRPC). Note,
|
|
89
|
+
overwriting the value of this field is pointless, as it is automatically set each time the class is instantiated."""
|
|
90
|
+
local_server_directory: str | Path = Path("/media/cbsuwsun/storage/sun_data")
|
|
91
|
+
"""The absolute path to the locally-mapped (via SMB protocol) root BioHPC server machine directory where to store
|
|
92
|
+
all projects."""
|
|
93
|
+
local_nas_directory: str | Path = Path("/home/cybermouse/nas/rawdata")
|
|
94
|
+
"""The absolute path to the locally-mapped (via SMB protocol) root Synology NAS directory where to store all
|
|
95
|
+
projects."""
|
|
96
|
+
local_mesoscope_directory: str | Path = Path("/home/cybermouse/scanimage/mesodata")
|
|
97
|
+
"""The absolute path to the locally-mapped (via SMB protocol) root mesoscope (ScanImagePC) directory where all
|
|
98
|
+
mesoscope-acquired data is aggregated during runtime."""
|
|
99
|
+
remote_storage_directory: str | Path = Path("/storage/sun_data")
|
|
100
|
+
"""The absolute path, relative to the BioHPC server root, to the directory where all projects are stored on the
|
|
101
|
+
slow (SSD) volume of the server. This path is used when running remote (server-side) jobs and, therefore, has to
|
|
102
|
+
be relative to the server root."""
|
|
103
|
+
remote_working_directory: str | Path = Path("/workdir/sun_data")
|
|
104
|
+
"""The absolute path, relative to the BioHPC server root, to the directory where all projects are stored on the
|
|
105
|
+
fast (NVME) volume of the server. This path is used when running remote (server-side) jobs and, therefore, has to
|
|
106
|
+
be relative to the server root."""
|
|
107
|
+
face_camera_index: int = 0
|
|
108
|
+
"""The index of the face camera in the list of all available Harvester-managed cameras."""
|
|
109
|
+
left_camera_index: int = 0
|
|
110
|
+
"""The index of the left body camera in the list of all available OpenCV-managed cameras."""
|
|
111
|
+
right_camera_index: int = 2
|
|
112
|
+
"""The index of the right body camera in the list of all available OpenCV-managed cameras."""
|
|
113
|
+
harvesters_cti_path: str | Path = Path("/opt/mvIMPACT_Acquire/lib/x86_64/mvGenTLProducer.cti")
|
|
114
|
+
"""The path to the GeniCam CTI file used to connect to Harvesters-managed cameras. Currently, this is only used by
|
|
115
|
+
the face camera."""
|
|
116
|
+
actor_port: str = "/dev/ttyACM0"
|
|
117
|
+
"""The USB port used by the Actor Microcontroller."""
|
|
118
|
+
sensor_port: str = "/dev/ttyACM1"
|
|
119
|
+
"""The USB port used by the Sensor Microcontroller."""
|
|
120
|
+
encoder_port: str = "/dev/ttyACM2"
|
|
121
|
+
"""The USB port used by the Encoder Microcontroller."""
|
|
122
|
+
headbar_port: str = "/dev/ttyUSB0"
|
|
123
|
+
"""The USB port used by the HeadBar Zaber motor controllers (devices)."""
|
|
124
|
+
lickport_port: str = "/dev/ttyUSB1"
|
|
125
|
+
"""The USB port used by the LickPort Zaber motor controllers (devices)."""
|
|
126
|
+
unity_ip: str = "127.0.0.1"
|
|
127
|
+
"""The IP address of the MQTT broker used to communicate with the Unity game engine. Note, this is only used during
|
|
128
|
+
experiment runtimes. Training runtimes ignore this parameter."""
|
|
129
|
+
unity_port: int = 1883
|
|
130
|
+
"""The port number of the MQTT broker used to communicate with the Unity game engine. Note, this is only used during
|
|
131
|
+
experiment runtimes. Training runtimes ignore this parameter."""
|
|
132
|
+
valve_calibration_data: dict[int | float, int | float] | tuple[tuple[int | float, int | float], ...] = (
|
|
133
|
+
(15000, 1.8556),
|
|
134
|
+
(30000, 3.4844),
|
|
135
|
+
(45000, 7.1846),
|
|
136
|
+
(60000, 10.0854),
|
|
137
|
+
)
|
|
138
|
+
"""A dictionary or tuple of tuples that maps valve open times, in microseconds, to the dispensed volume of water,
|
|
139
|
+
in microliters. During runtime, this data is used by the ValveModule to translate the requested reward volumes into
|
|
140
|
+
times the valve needs to be open to deliver the desired volume.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def load(cls, project_name: str, configuration_path: None | Path = None) -> "ProjectConfiguration":
|
|
145
|
+
"""Loads the project configuration parameters from a project_configuration.yaml file and uses the loaded data
|
|
146
|
+
to initialize the ProjectConfiguration instance.
|
|
147
|
+
|
|
148
|
+
This method is called for each session runtime to reuse the configuration parameters generated at project
|
|
149
|
+
creation. When it is called for the first time (during new project creation), the method generates the default
|
|
150
|
+
configuration file and prompts the user to update the configuration before proceeding with the runtime.
|
|
151
|
+
|
|
152
|
+
Notes:
|
|
153
|
+
As part of its runtime, the method may prompt the user to provide the path to the local root directory.
|
|
154
|
+
This directory stores all project subdirectories and acts as the top level of the local data hierarchy.
|
|
155
|
+
The path to the directory will be saved inside user's default data directory, so that it can be reused for
|
|
156
|
+
all future projects. Use sl-replace_root_path CLI to replace the path that is saved in this way.
|
|
157
|
+
|
|
158
|
+
Since this class is used during both data acquisition and processing on different machines, this method
|
|
159
|
+
supports multiple ways of initializing the class. Use the project_name on the VRPC (via the sl_experiment
|
|
160
|
+
library). Use the configuration path on the BioHPC server (via the sl_forgery library).
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
project_name: The name of the project whose configuration file needs to be discovered and loaded. Note, this
|
|
164
|
+
way of resolving the project is the default way on the VRPC. When processing data on the server, the
|
|
165
|
+
pipeline preferentially uses the configuration_path.
|
|
166
|
+
configuration_path: The path to the project_configuration.yaml file from which to load the data. This is
|
|
167
|
+
an optional way of resolving the configuration data source that always takes precedence over the
|
|
168
|
+
project_name when both are provided.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
An initialized ProjectConfiguration instance.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
# Ensures console is enabled
|
|
175
|
+
if not console.enabled:
|
|
176
|
+
console.enable()
|
|
177
|
+
|
|
178
|
+
# If the configuration path is not provided, uses the 'default' resolution strategy that involves reading the
|
|
179
|
+
# user's data directory
|
|
180
|
+
if configuration_path is None:
|
|
181
|
+
# Uses appdirs to locate the user data directory and resolve the path to the storage file
|
|
182
|
+
app_dir = Path(appdirs.user_data_dir(appname="sl_assets", appauthor="sun_lab"))
|
|
183
|
+
path_file = app_dir.joinpath("root_path.txt")
|
|
184
|
+
|
|
185
|
+
# If the .txt file that stores the local root path does not exist, prompts the user to provide the path to
|
|
186
|
+
# the local root directory and creates the root_path.txt file
|
|
187
|
+
if not path_file.exists():
|
|
188
|
+
# Gets the path to the local root directory from the user via command line input
|
|
189
|
+
message = (
|
|
190
|
+
"Unable to resolve the local root directory automatically. Provide the absolute path to the local "
|
|
191
|
+
"directory that stores all project-specific directories. This is required when resolving project "
|
|
192
|
+
"configuration based on project's name."
|
|
193
|
+
)
|
|
194
|
+
console.echo(message=message, level=LogLevel.WARNING)
|
|
195
|
+
root_path_str = input("Local root path: ")
|
|
196
|
+
root_path = Path(root_path_str)
|
|
197
|
+
|
|
198
|
+
# If necessary, generates the local root directory
|
|
199
|
+
ensure_directory_exists(root_path)
|
|
200
|
+
|
|
201
|
+
# Also ensures that the app directory exists, so that the path_file can be created below.
|
|
202
|
+
ensure_directory_exists(path_file)
|
|
203
|
+
|
|
204
|
+
# Saves the root path to the file
|
|
205
|
+
with open(path_file, "w") as f:
|
|
206
|
+
f.write(str(root_path))
|
|
207
|
+
|
|
208
|
+
# Otherwise, uses the root path and the project name to resolve the path to the project configuration
|
|
209
|
+
# directory and load the project configuration data.
|
|
210
|
+
else:
|
|
211
|
+
# Reads the root path from the file
|
|
212
|
+
with open(path_file, "r") as f:
|
|
213
|
+
root_path = Path(f.read().strip())
|
|
214
|
+
|
|
215
|
+
# Uses the root experiment directory path to generate the path to the target project's configuration file.
|
|
216
|
+
configuration_path = root_path.joinpath(project_name, "configuration", "project_configuration.yaml")
|
|
217
|
+
ensure_directory_exists(configuration_path) # Ensures the directory tree for the config path exists.
|
|
218
|
+
|
|
219
|
+
# If the configuration file does not exist (this is the first time this class is initialized for a given
|
|
220
|
+
# project), generates a precursor (default) configuration file and prompts the user to update the configuration.
|
|
221
|
+
if not configuration_path.exists():
|
|
222
|
+
message = (
|
|
223
|
+
f"Unable to load project configuration data from disk as no 'project_configuration.yaml' file "
|
|
224
|
+
f"found at the provided project path. Generating a precursor (default) configuration file under "
|
|
225
|
+
f"{project_name}/configuration directory. Edit the file to specify project configuration before "
|
|
226
|
+
f"proceeding further to avoid runtime errors. Also, edit other configuration precursors saved to the "
|
|
227
|
+
f"same directory to control other aspects of data acquisition and processing."
|
|
228
|
+
)
|
|
229
|
+
console.echo(message=message, level=LogLevel.WARNING)
|
|
230
|
+
|
|
231
|
+
# Generates the default project configuration instance and dumps it as a .yaml file. Note, as part of
|
|
232
|
+
# this process, the class generates the correct 'local_root_path' based on the path provided by the
|
|
233
|
+
# user.
|
|
234
|
+
precursor = ProjectConfiguration(local_root_directory=Path(str(configuration_path.parents[2])))
|
|
235
|
+
precursor._to_path(path=configuration_path)
|
|
236
|
+
|
|
237
|
+
# Waits for the user to manually configure the newly created file.
|
|
238
|
+
input(f"Enter anything to continue: ")
|
|
239
|
+
|
|
240
|
+
# Loads the data from the YAML file and initializes the class instance. This now uses either the automatically
|
|
241
|
+
# resolved configuration path or the manually provided path
|
|
242
|
+
instance: ProjectConfiguration = cls.from_yaml(file_path=configuration_path) # type: ignore
|
|
243
|
+
|
|
244
|
+
# Converts all paths loaded as strings to Path objects used inside the library
|
|
245
|
+
instance.local_mesoscope_directory = Path(instance.local_mesoscope_directory)
|
|
246
|
+
instance.local_nas_directory = Path(instance.local_nas_directory)
|
|
247
|
+
instance.local_server_directory = Path(instance.local_server_directory)
|
|
248
|
+
instance.remote_storage_directory = Path(instance.remote_storage_directory)
|
|
249
|
+
instance.remote_working_directory = Path(instance.remote_working_directory)
|
|
250
|
+
instance.google_credentials_path = Path(instance.google_credentials_path)
|
|
251
|
+
instance.server_credentials_path = Path(instance.server_credentials_path)
|
|
252
|
+
instance.harvesters_cti_path = Path(instance.harvesters_cti_path)
|
|
253
|
+
|
|
254
|
+
# Local root path is always re-computed from the resolved configuration file's location
|
|
255
|
+
instance.local_root_directory = Path(str(configuration_path.parents[2]))
|
|
256
|
+
|
|
257
|
+
# Converts valve_calibration data from dictionary to a tuple of tuples format
|
|
258
|
+
if not isinstance(instance.valve_calibration_data, tuple):
|
|
259
|
+
instance.valve_calibration_data = tuple((k, v) for k, v in instance.valve_calibration_data.items())
|
|
260
|
+
|
|
261
|
+
# Partially verifies the loaded data. Most importantly, this step does not allow proceeding if the user did not
|
|
262
|
+
# replace the surgery log and water restriction log placeholders with valid ID values.
|
|
263
|
+
instance._verify_data()
|
|
264
|
+
|
|
265
|
+
# Returns the initialized class instance to caller
|
|
266
|
+
return instance
|
|
267
|
+
|
|
268
|
+
def _to_path(self, path: Path) -> None:
|
|
269
|
+
"""Saves the instance data to disk as a project_configuration.yaml file.
|
|
270
|
+
|
|
271
|
+
This method is automatically called when the project is created. All future runtimes should use the load()
|
|
272
|
+
method to load and reuse the configuration data saved to the .yaml file.
|
|
273
|
+
|
|
274
|
+
Notes:
|
|
275
|
+
This method also generates and dumps multiple other 'precursor' configuration files into the folder. This
|
|
276
|
+
includes the example 'default' experiment configuration and the DeepLabCut and Suite2P configuration files
|
|
277
|
+
used during data processing.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
path: The path to the .yaml file to save the data to.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
# Converts all Path objects to strings before dumping the data, as .yaml encoder does not properly recognize
|
|
284
|
+
# Path objects
|
|
285
|
+
self.local_root_directory = str(self.local_root_directory)
|
|
286
|
+
self.local_mesoscope_directory = str(self.local_mesoscope_directory)
|
|
287
|
+
self.local_nas_directory = str(self.local_nas_directory)
|
|
288
|
+
self.local_server_directory = str(self.local_server_directory)
|
|
289
|
+
self.remote_storage_directory = str(self.remote_storage_directory)
|
|
290
|
+
self.remote_working_directory = str(self.remote_working_directory)
|
|
291
|
+
self.google_credentials_path = str(self.google_credentials_path)
|
|
292
|
+
self.server_credentials_path = str(self.server_credentials_path)
|
|
293
|
+
self.harvesters_cti_path = str(self.harvesters_cti_path)
|
|
294
|
+
|
|
295
|
+
# Converts valve calibration data into dictionary format
|
|
296
|
+
if isinstance(self.valve_calibration_data, tuple):
|
|
297
|
+
self.valve_calibration_data = {k: v for k, v in self.valve_calibration_data}
|
|
298
|
+
|
|
299
|
+
# Saves the data to the YAML file
|
|
300
|
+
self.to_yaml(file_path=path)
|
|
301
|
+
|
|
302
|
+
# As part of this runtime, also generates and dumps the 'precursor' experiment configuration file.
|
|
303
|
+
example_experiment = ExperimentConfiguration()
|
|
304
|
+
example_experiment.to_yaml(path.parent.joinpath("default_experiment.yaml"))
|
|
305
|
+
|
|
306
|
+
def _verify_data(self) -> None:
|
|
307
|
+
"""Verifies the data loaded from the project_configuration.yaml file to ensure its validity.
|
|
308
|
+
|
|
309
|
+
Since this class is explicitly designed to be modified by the user, this verification step is carried out to
|
|
310
|
+
ensure that the loaded data matches expectations. This reduces the potential for user errors to impact the
|
|
311
|
+
runtime behavior of the library. This internal method is automatically called by the load() method.
|
|
312
|
+
|
|
313
|
+
Notes:
|
|
314
|
+
The method does not verify all fields loaded from the configuration file and instead focuses on fields that
|
|
315
|
+
do not have valid default values. Since these fields are expected to be frequently modified by users, they
|
|
316
|
+
are the ones that require additional validation.
|
|
317
|
+
|
|
318
|
+
Raises:
|
|
319
|
+
ValueError: If the loaded data does not match expected formats or values.
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
# Verifies Google Sheet ID formatting. Google Sheet IDs are usually 44 characters long, containing letters,
|
|
323
|
+
# numbers, hyphens, and underscores
|
|
324
|
+
pattern = r"^[a-zA-Z0-9_-]{44}$"
|
|
325
|
+
if not re.match(pattern, self.surgery_sheet_id):
|
|
326
|
+
message = (
|
|
327
|
+
f"Unable to verify the surgery_sheet_id field loaded from the 'project_configuration.yaml' file. "
|
|
328
|
+
f"Expected a string with 44 characters, using letters, numbers, hyphens, and underscores, but found: "
|
|
329
|
+
f"{self.surgery_sheet_id}."
|
|
330
|
+
)
|
|
331
|
+
console.error(message=message, error=ValueError)
|
|
332
|
+
if not re.match(pattern, self.water_log_sheet_id):
|
|
333
|
+
message = (
|
|
334
|
+
f"Unable to verify the surgery_sheet_id field loaded from the 'project_configuration.yaml' file. "
|
|
335
|
+
f"Expected a string with 44 characters, using letters, numbers, hyphens, and underscores, but found: "
|
|
336
|
+
f"{self.water_log_sheet_id}."
|
|
337
|
+
)
|
|
338
|
+
console.error(message=message, error=ValueError)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@dataclass()
|
|
342
|
+
class RawData:
|
|
343
|
+
"""Stores the paths to the directories and files that make up the 'raw_data' session directory.
|
|
344
|
+
|
|
345
|
+
The raw_data directory stores the data acquired during the session runtime before and after preprocessing. Since
|
|
346
|
+
preprocessing does not alter the data, any data in that folder is considered 'raw'. The raw_data folder is initially
|
|
347
|
+
created on the VRPC and, after preprocessing, is copied to the BioHPC server and the Synology NAS for long-term
|
|
348
|
+
storage and further processing.
|
|
349
|
+
|
|
350
|
+
Notes:
|
|
351
|
+
The overall structure of the raw_data directory remains fixed for the entire lifetime of the data. It is reused
|
|
352
|
+
across all destinations.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
raw_data_path: str | Path
|
|
356
|
+
"""Stores the path to the root raw_data directory of the session. This directory stores all raw data during
|
|
357
|
+
acquisition and preprocessing. Note, preprocessing does not alter raw data, so at any point in time all data inside
|
|
358
|
+
the folder is considered 'raw'."""
|
|
359
|
+
camera_data_path: str | Path
|
|
360
|
+
"""Stores the path to the directory that contains all camera data acquired during the session. Primarily, this
|
|
361
|
+
includes .mp4 video files from each recorded camera."""
|
|
362
|
+
mesoscope_data_path: str | Path
|
|
363
|
+
"""Stores the path to the directory that contains all Mesoscope data acquired during the session. Primarily, this
|
|
364
|
+
includes the mesoscope-acquired .tif files (brain activity data) and the motion estimation data."""
|
|
365
|
+
behavior_data_path: str | Path
|
|
366
|
+
"""Stores the path to the directory that contains all behavior data acquired during the session. Primarily, this
|
|
367
|
+
includes the .npz log files used by data-acquisition libraries to store all acquired data. The data stored in this
|
|
368
|
+
way includes the camera and mesoscope frame timestamps and the states of Mesoscope-VR components, such as lick
|
|
369
|
+
sensors, rotary encoders, and other modules."""
|
|
370
|
+
zaber_positions_path: str | Path
|
|
371
|
+
"""Stores the path to the zaber_positions.yaml file. This file contains the snapshot of all Zaber motor positions
|
|
372
|
+
at the end of the session. Zaber motors are used to position the LickPort and the HeadBar manipulators, which is
|
|
373
|
+
essential for supporting proper brain imaging and animal's running behavior during the session."""
|
|
374
|
+
session_descriptor_path: str | Path
|
|
375
|
+
"""Stores the path to the session_descriptor.yaml file. This file is partially filled by the system during runtime
|
|
376
|
+
and partially by the experimenter after the runtime. It contains session-specific information, such as the specific
|
|
377
|
+
training parameters, the positions of the Mesoscope objective and the notes made by the experimenter during
|
|
378
|
+
runtime."""
|
|
379
|
+
hardware_configuration_path: str | Path
|
|
380
|
+
"""Stores the path to the hardware_configuration.yaml file. This file contains the partial snapshot of the
|
|
381
|
+
calibration parameters used by the Mesoscope-VR system components during runtime. Primarily, this is used during
|
|
382
|
+
data processing to read the .npz data log files generated during runtime."""
|
|
383
|
+
surgery_metadata_path: str | Path
|
|
384
|
+
"""Stores the path to the surgery_metadata.yaml file. This file contains the most actual information about the
|
|
385
|
+
surgical intervention(s) performed on the animal prior to the session."""
|
|
386
|
+
project_configuration_path: str | Path
|
|
387
|
+
"""Stores the path to the project_configuration.yaml file. This file contains the snapshot of the configuration
|
|
388
|
+
parameters for the session's project."""
|
|
389
|
+
session_data_path: str | Path
|
|
390
|
+
"""Stores the path to the session_data.yaml file. This path is used b y the SessionData instance to save itself to
|
|
391
|
+
disk as a .yaml file. The file contains all paths used during data acquisition and processing on both the VRPC and
|
|
392
|
+
the BioHPC server."""
|
|
393
|
+
experiment_configuration_path: str | Path
|
|
394
|
+
"""Stores the path to the experiment_configuration.yaml file. This file contains the snapshot of the
|
|
395
|
+
experiment runtime configuration used by the session. This file is only created for experiment session. It does not
|
|
396
|
+
exist for behavior training sessions."""
|
|
397
|
+
mesoscope_positions_path: str | Path
|
|
398
|
+
"""Stores the path to the mesoscope_positions.yaml file. This file contains the snapshot of the positions used
|
|
399
|
+
by the Mesoscope at the end of the session. This includes both the physical position of the mesoscope objective and
|
|
400
|
+
the 'virtual' tip, tilt, and fastZ positions set via ScanImage software. This file is only created for experiment
|
|
401
|
+
sessions that use the mesoscope, it is omitted for behavior training sessions."""
|
|
402
|
+
window_screenshot_path: str | Path
|
|
403
|
+
"""Stores the path to the .png screenshot of the ScanImagePC screen. The screenshot should contain the image of the
|
|
404
|
+
cranial window and the red-dot alignment windows. This is used to generate a visual snapshot of the cranial window
|
|
405
|
+
alignment and appearance for each experiment session. This file is only created for experiment sessions that use
|
|
406
|
+
the mesoscope, it is omitted for behavior training sessions."""
|
|
407
|
+
|
|
408
|
+
def __post_init__(self) -> None:
|
|
409
|
+
"""This method is automatically called after class instantiation and ensures that all path fields of the class
|
|
410
|
+
are converted to Path objects.
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
self.raw_data_path = Path(self.raw_data_path)
|
|
414
|
+
self.camera_data_path = Path(self.camera_data_path)
|
|
415
|
+
self.mesoscope_data_path = Path(self.mesoscope_data_path)
|
|
416
|
+
self.behavior_data_path = Path(self.behavior_data_path)
|
|
417
|
+
self.zaber_positions_path = Path(self.zaber_positions_path)
|
|
418
|
+
self.session_descriptor_path = Path(self.session_descriptor_path)
|
|
419
|
+
self.hardware_configuration_path = Path(self.hardware_configuration_path)
|
|
420
|
+
self.surgery_metadata_path = Path(self.surgery_metadata_path)
|
|
421
|
+
self.project_configuration_path = Path(self.project_configuration_path)
|
|
422
|
+
self.session_data_path = Path(self.session_data_path)
|
|
423
|
+
self.experiment_configuration_path = Path(self.experiment_configuration_path)
|
|
424
|
+
self.mesoscope_positions_path = Path(self.mesoscope_positions_path)
|
|
425
|
+
self.window_screenshot_path = Path(self.window_screenshot_path)
|
|
426
|
+
|
|
427
|
+
def make_string(self) -> None:
|
|
428
|
+
"""Converts all Path objects stored inside the class to strings.
|
|
429
|
+
|
|
430
|
+
This transformation is required to support dumping class data into a .YAML file so that the data can be stored
|
|
431
|
+
on disk.
|
|
432
|
+
"""
|
|
433
|
+
self.raw_data_path = str(self.raw_data_path)
|
|
434
|
+
self.camera_data_path = str(self.camera_data_path)
|
|
435
|
+
self.mesoscope_data_path = str(self.mesoscope_data_path)
|
|
436
|
+
self.behavior_data_path = str(self.behavior_data_path)
|
|
437
|
+
self.zaber_positions_path = str(self.zaber_positions_path)
|
|
438
|
+
self.session_descriptor_path = str(self.session_descriptor_path)
|
|
439
|
+
self.hardware_configuration_path = str(self.hardware_configuration_path)
|
|
440
|
+
self.surgery_metadata_path = str(self.surgery_metadata_path)
|
|
441
|
+
self.project_configuration_path = str(self.project_configuration_path)
|
|
442
|
+
self.session_data_path = str(self.session_data_path)
|
|
443
|
+
self.experiment_configuration_path = str(self.experiment_configuration_path)
|
|
444
|
+
self.mesoscope_positions_path = str(self.mesoscope_positions_path)
|
|
445
|
+
self.window_screenshot_path = str(self.window_screenshot_path)
|
|
446
|
+
|
|
447
|
+
def make_dirs(self) -> None:
|
|
448
|
+
"""Ensures that all major subdirectories and the root raw_data directory exist.
|
|
449
|
+
|
|
450
|
+
This method is used by the VRPC to generate the raw_data directory when it creates a new session.
|
|
451
|
+
"""
|
|
452
|
+
ensure_directory_exists(Path(self.raw_data_path))
|
|
453
|
+
ensure_directory_exists(Path(self.camera_data_path))
|
|
454
|
+
ensure_directory_exists(Path(self.mesoscope_data_path))
|
|
455
|
+
ensure_directory_exists(Path(self.behavior_data_path))
|
|
456
|
+
|
|
457
|
+
def switch_root(self, new_root: Path) -> None:
|
|
458
|
+
"""Changes the root of the managed raw_data directory to the provided root path.
|
|
459
|
+
|
|
460
|
+
This service method is used by the SessionData class to convert all paths in this class to be relative to the
|
|
461
|
+
new root. This is used to adjust the SessionData instance to work for the VRPC (one root) or the BioHPC server
|
|
462
|
+
(another root). Since this is the only subclass used by both the VRPC and the BioHPC server, this method is
|
|
463
|
+
only implemented for this class.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
new_root: The new root directory to use for all paths inside the instance. This has to be the path to the
|
|
467
|
+
root session directory: pc_root/project/animal/session.
|
|
468
|
+
"""
|
|
469
|
+
# Gets current root from the raw_data_path.
|
|
470
|
+
old_root = Path(self.raw_data_path).parents[2]
|
|
471
|
+
|
|
472
|
+
# Updates all paths by replacing old_root with new_root
|
|
473
|
+
self.raw_data_path = new_root.joinpath(Path(self.raw_data_path).relative_to(old_root))
|
|
474
|
+
self.camera_data_path = new_root.joinpath(Path(self.camera_data_path).relative_to(old_root))
|
|
475
|
+
self.mesoscope_data_path = new_root.joinpath(Path(self.mesoscope_data_path).relative_to(old_root))
|
|
476
|
+
self.behavior_data_path = new_root.joinpath(Path(self.behavior_data_path).relative_to(old_root))
|
|
477
|
+
self.zaber_positions_path = new_root.joinpath(Path(self.zaber_positions_path).relative_to(old_root))
|
|
478
|
+
self.session_descriptor_path = new_root.joinpath(Path(self.session_descriptor_path).relative_to(old_root))
|
|
479
|
+
self.hardware_configuration_path = new_root.joinpath(
|
|
480
|
+
Path(self.hardware_configuration_path).relative_to(old_root)
|
|
481
|
+
)
|
|
482
|
+
self.surgery_metadata_path = new_root.joinpath(Path(self.surgery_metadata_path).relative_to(old_root))
|
|
483
|
+
self.project_configuration_path = new_root.joinpath(Path(self.project_configuration_path).relative_to(old_root))
|
|
484
|
+
self.session_data_path = new_root.joinpath(Path(self.session_data_path).relative_to(old_root))
|
|
485
|
+
self.experiment_configuration_path = new_root.joinpath(
|
|
486
|
+
Path(self.experiment_configuration_path).relative_to(old_root)
|
|
487
|
+
)
|
|
488
|
+
self.mesoscope_positions_path = new_root.joinpath(Path(self.mesoscope_positions_path).relative_to(old_root))
|
|
489
|
+
self.window_screenshot_path = new_root.joinpath(Path(self.window_screenshot_path).relative_to(old_root))
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@dataclass()
|
|
493
|
+
class ProcessedData:
|
|
494
|
+
"""Stores the paths to the directories and files that make up the 'processed_data' session directory.
|
|
495
|
+
|
|
496
|
+
The processed_data directory stores the processed session data, which is generated by running various processing
|
|
497
|
+
pipelines on the BioHPC server. These pipelines use raw data to generate processed data, and the processed data is
|
|
498
|
+
usually only stored on the BioHPC server. Processed data represents an intermediate step between raw data and the
|
|
499
|
+
dataset used in the data analysis.
|
|
500
|
+
"""
|
|
501
|
+
|
|
502
|
+
processed_data_path: str | Path
|
|
503
|
+
"""Stores the path to the root processed_data directory of the session. This directory stores the processed data
|
|
504
|
+
as it is generated by various pipelines running on the BioHPC server. This directory is only stored on the BioHPC
|
|
505
|
+
server and is not intended to be used directly for data analysis."""
|
|
506
|
+
camera_data_path: str | Path
|
|
507
|
+
"""Stores the output of the DeepLabCut pose estimation pipeline."""
|
|
508
|
+
mesoscope_data_path: str | Path
|
|
509
|
+
"""Stores the output of the suite2p cell registration pipeline."""
|
|
510
|
+
behavior_data_path: str | Path
|
|
511
|
+
"""Stores the output of the Sun lab behavior data extraction pipeline."""
|
|
512
|
+
deeplabcut_root_path: str | Path
|
|
513
|
+
"""Stores the path to the root DeepLabCut project directory. Since DeepLabCut adopts a project-based directory
|
|
514
|
+
management hierarchy, it is easier to have a single DLC folder shared by all animals and sessions of a given
|
|
515
|
+
project. This root folder is typically stored under the main project directory on the fast BioHPC server volume."""
|
|
516
|
+
suite2p_configuration_path: str | Path
|
|
517
|
+
"""Stores the path to the suite2p_configuration.yaml file stored inside the project's 'configuration' directory on
|
|
518
|
+
the fast BioHPC server volume. Since all sessions share the same suite2p configuration file, it is stored in a
|
|
519
|
+
general configuration directory, similar to how project configuration is stored on the VRPC."""
|
|
520
|
+
|
|
521
|
+
def __post_init__(self) -> None:
|
|
522
|
+
"""This method is automatically called after class instantiation and ensures that all path fields of the class
|
|
523
|
+
are converted to Path objects.
|
|
524
|
+
"""
|
|
525
|
+
|
|
526
|
+
self.processed_data_path = Path(self.processed_data_path)
|
|
527
|
+
self.camera_data_path = Path(self.camera_data_path)
|
|
528
|
+
self.mesoscope_data_path = Path(self.mesoscope_data_path)
|
|
529
|
+
self.behavior_data_path = Path(self.behavior_data_path)
|
|
530
|
+
self.deeplabcut_root_path = Path(self.deeplabcut_root_path)
|
|
531
|
+
self.suite2p_configuration_path = Path(self.suite2p_configuration_path)
|
|
532
|
+
|
|
533
|
+
def make_string(self) -> None:
|
|
534
|
+
"""Converts all Path objects stored inside the class to strings.
|
|
535
|
+
|
|
536
|
+
This transformation is required to support dumping class data into a .YAML file so that the data can be stored
|
|
537
|
+
on disk.
|
|
538
|
+
"""
|
|
539
|
+
self.processed_data_path = str(self.processed_data_path)
|
|
540
|
+
self.camera_data_path = str(self.camera_data_path)
|
|
541
|
+
self.mesoscope_data_path = str(self.mesoscope_data_path)
|
|
542
|
+
self.behavior_data_path = str(self.behavior_data_path)
|
|
543
|
+
self.deeplabcut_root_path = str(self.deeplabcut_root_path)
|
|
544
|
+
self.suite2p_configuration_path = str(self.suite2p_configuration_path)
|
|
545
|
+
|
|
546
|
+
def make_dirs(self) -> None:
|
|
547
|
+
"""Ensures that all major subdirectories of the processed_data directory exist.
|
|
548
|
+
|
|
549
|
+
This method is used by the BioHPC server to generate the processed_data directory as part of the sl-forgery
|
|
550
|
+
library runtime.
|
|
551
|
+
"""
|
|
552
|
+
ensure_directory_exists(Path(self.processed_data_path))
|
|
553
|
+
ensure_directory_exists(Path(self.camera_data_path))
|
|
554
|
+
ensure_directory_exists(Path(self.mesoscope_data_path))
|
|
555
|
+
ensure_directory_exists(Path(self.behavior_data_path))
|
|
556
|
+
ensure_directory_exists(Path(self.deeplabcut_root_path))
|
|
557
|
+
ensure_directory_exists(Path(self.suite2p_configuration_path))
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
@dataclass()
|
|
561
|
+
class PersistentData:
|
|
562
|
+
"""Stores the paths to the directories and files that make up the 'persistent_data' directories of the VRPC and
|
|
563
|
+
the ScanImagePC.
|
|
564
|
+
|
|
565
|
+
Persistent data directories are used to keep certain files on the VRPC and the ScanImagePC. Typically, this data
|
|
566
|
+
is reused during the following sessions. For example, a copy of Zaber motor positions is persisted on the VRPC for
|
|
567
|
+
each animal after every session to support automatically restoring Zaber motors to the positions used during the
|
|
568
|
+
previous session.
|
|
569
|
+
|
|
570
|
+
Notes:
|
|
571
|
+
Persistent data includes the project and experiment configuration data. Some persistent data is overwritten
|
|
572
|
+
after each session, other data is generated once and kept through the animal's lifetime. Primarily, this data is
|
|
573
|
+
only used internally by the sl-experiment or sl-forgery libraries and is not intended for end-users.
|
|
574
|
+
"""
|
|
575
|
+
|
|
576
|
+
zaber_positions_path: str | Path
|
|
577
|
+
"""Stores the path to the Zaber motor positions snapshot generated at the end of the previous session runtime. This
|
|
578
|
+
is used to automatically restore all Zaber motors to the same position across all sessions."""
|
|
579
|
+
mesoscope_positions_path: str | Path
|
|
580
|
+
"""Stores the path to the Mesoscope positions snapshot generated at the end of the previous session runtime. This
|
|
581
|
+
is used to help the user to (manually) restore the Mesoscope to the same position across all sessions."""
|
|
582
|
+
motion_estimator_path: str | Path
|
|
583
|
+
"""Stores the 'reference' motion estimator file generated during the first experiment session of each animal. This
|
|
584
|
+
file is kept on the ScanImagePC to image the same population of cells across all experiment sessions."""
|
|
585
|
+
|
|
586
|
+
def __post_init__(self) -> None:
|
|
587
|
+
"""This method is automatically called after class instantiation and ensures that all path fields of the class
|
|
588
|
+
are converted to Path objects.
|
|
589
|
+
"""
|
|
590
|
+
|
|
591
|
+
self.zaber_positions_path = Path(self.zaber_positions_path)
|
|
592
|
+
self.mesoscope_positions_path = Path(self.mesoscope_positions_path)
|
|
593
|
+
self.motion_estimator_path = Path(self.motion_estimator_path)
|
|
594
|
+
|
|
595
|
+
def make_string(self) -> None:
|
|
596
|
+
"""Converts all Path objects stored inside the class to strings.
|
|
597
|
+
|
|
598
|
+
This transformation is required to support dumping class data into a .YAML file so that the data can be stored
|
|
599
|
+
on disk.
|
|
600
|
+
"""
|
|
601
|
+
self.zaber_positions_path = str(self.zaber_positions_path)
|
|
602
|
+
self.mesoscope_positions_path = str(self.mesoscope_positions_path)
|
|
603
|
+
self.motion_estimator_path = str(self.motion_estimator_path)
|
|
604
|
+
|
|
605
|
+
def make_dirs(self) -> None:
|
|
606
|
+
"""Ensures that the VRPC and the ScanImagePC persistent_data directories exist."""
|
|
607
|
+
|
|
608
|
+
# We need to call ensure_directory_exists one for each unique directory tree
|
|
609
|
+
ensure_directory_exists(Path(self.zaber_positions_path)) # vrpc_root/project/animal/persistent_data
|
|
610
|
+
ensure_directory_exists(Path(self.motion_estimator_path)) # scanimagepc_root/project/animal/persistent_data
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
@dataclass()
|
|
614
|
+
class MesoscopeData:
|
|
615
|
+
"""Stores the paths to the directories used by the ScanImagePC to save mesoscope-generated data during session
|
|
616
|
+
runtime.
|
|
617
|
+
|
|
618
|
+
The ScanImagePC is largely isolated from the VRPC during runtime. For the VRPC to pull the data acquired by the
|
|
619
|
+
ScanImagePC, it has to use the predefined directory structure to save the data. This class stores the predefined
|
|
620
|
+
path to various directories where ScanImagePC is expected to save the data and store it after acquisition.sers.
|
|
621
|
+
"""
|
|
622
|
+
|
|
623
|
+
root_data_path: str | Path
|
|
624
|
+
"""Stores the path to the root ScanImagePC data directory, mounted to the VRPC filesystem via the SMB or equivalent
|
|
625
|
+
protocol. This path is used during experiment session runtimes to discover the cranial window screenshots
|
|
626
|
+
taken by the user before starting the experiment."""
|
|
627
|
+
mesoscope_data_path: str | Path
|
|
628
|
+
"""Stores the path to the 'general' mesoscope_data directory. All experiment sessions (across all animals and
|
|
629
|
+
projects) use the same mesoscope_data directory to save the data generated by the mesoscope via ScanImage
|
|
630
|
+
software. This simplifies ScanImagePC configuration process during runtime. The data is moved into a
|
|
631
|
+
session-specific directory during preprocessing."""
|
|
632
|
+
session_specific_mesoscope_data_path: str | Path
|
|
633
|
+
"""Stores the path to the session-specific mesoscope_data directory. This directory is generated at the end of
|
|
634
|
+
each experiment runtime to prepare mesoscope data for further processing and to reset the 'shared' folder for the
|
|
635
|
+
next session's runtime."""
|
|
636
|
+
|
|
637
|
+
def __post_init__(self) -> None:
|
|
638
|
+
"""This method is automatically called after class instantiation and ensures that all path fields of the class
|
|
639
|
+
are converted to Path objects.
|
|
640
|
+
"""
|
|
641
|
+
self.root_data_path = Path(self.root_data_path)
|
|
642
|
+
self.mesoscope_data_path = Path(self.mesoscope_data_path)
|
|
643
|
+
self.session_specific_mesoscope_data_path = Path(self.session_specific_mesoscope_data_path)
|
|
644
|
+
|
|
645
|
+
def make_string(self) -> None:
|
|
646
|
+
"""Converts all Path objects stored inside the class to strings.
|
|
647
|
+
|
|
648
|
+
This transformation is required to support dumping class data into a .YAML file so that the data can be stored
|
|
649
|
+
on disk.
|
|
650
|
+
"""
|
|
651
|
+
self.root_data_path = str(self.root_data_path)
|
|
652
|
+
self.mesoscope_data_path = str(self.mesoscope_data_path)
|
|
653
|
+
self.session_specific_mesoscope_data_path = str(self.session_specific_mesoscope_data_path)
|
|
654
|
+
|
|
655
|
+
def make_dirs(self) -> None:
|
|
656
|
+
"""Ensures that the ScanImagePC data acquisition directories exist."""
|
|
657
|
+
|
|
658
|
+
# Does not create the session-specific directory. This is on purpose, as the session-specific directory
|
|
659
|
+
# is generated during runtime by renaming the 'general' mesoscope_data directory. The 'general' directory is
|
|
660
|
+
# then recreated from scratch. This ensures that the general directory is empty (ready for the next session)
|
|
661
|
+
# with minimal I/O overhead.
|
|
662
|
+
ensure_directory_exists(Path(self.mesoscope_data_path))
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
@dataclass()
|
|
666
|
+
class Destinations:
|
|
667
|
+
"""Stores the paths to the VRPC filesystem-mounted Synology NAS and BioHPC server directories.
|
|
668
|
+
|
|
669
|
+
These directories are used during data preprocessing to transfer the preprocessed raw_data directory from the
|
|
670
|
+
VRPC to the long-term storage destinations.
|
|
671
|
+
"""
|
|
672
|
+
|
|
673
|
+
nas_raw_data_path: str | Path
|
|
674
|
+
"""Stores the path to the session's raw_data directory on the Synology NAS, which is mounted to the VRPC via the
|
|
675
|
+
SMB or equivalent protocol."""
|
|
676
|
+
server_raw_data_path: str | Path
|
|
677
|
+
"""Stores the path to the session's raw_data directory on the BioHPC server, which is mounted to the VRPC via the
|
|
678
|
+
SMB or equivalent protocol."""
|
|
679
|
+
|
|
680
|
+
def __post_init__(self) -> None:
|
|
681
|
+
"""This method is automatically called after class instantiation and ensures that all path fields of the class
|
|
682
|
+
are converted to Path objects.
|
|
683
|
+
"""
|
|
684
|
+
self.nas_raw_data_path = Path(self.nas_raw_data_path)
|
|
685
|
+
self.server_raw_data_path = Path(self.server_raw_data_path)
|
|
686
|
+
|
|
687
|
+
def make_string(self) -> None:
|
|
688
|
+
"""Converts all Path objects stored inside the class to strings.
|
|
689
|
+
|
|
690
|
+
This transformation is required to support dumping class data into a .YAML file so that the data can be stored
|
|
691
|
+
on disk.
|
|
692
|
+
"""
|
|
693
|
+
self.nas_raw_data_path = str(self.nas_raw_data_path)
|
|
694
|
+
self.server_raw_data_path = str(self.server_raw_data_path)
|
|
695
|
+
|
|
696
|
+
def make_dirs(self) -> None:
|
|
697
|
+
"""Ensures that all destination directories exist."""
|
|
698
|
+
ensure_directory_exists(Path(self.nas_raw_data_path))
|
|
699
|
+
ensure_directory_exists(Path(self.server_raw_data_path))
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
@dataclass
|
|
703
|
+
class SessionData(YamlConfig):
|
|
704
|
+
"""Provides methods for managing the data of a single experiment or training session across all destinations.
|
|
705
|
+
|
|
706
|
+
The primary purpose of this class is to maintain the session data structure across all supported destinations. It
|
|
707
|
+
generates the paths used by all other classes from this library and classes from sl-experiment and sl-forgery
|
|
708
|
+
libraries.
|
|
709
|
+
|
|
710
|
+
If necessary, the class can be used to either generate a new session or to load an already existing session's data.
|
|
711
|
+
When the class is used to create a new session, it automatically resolves the new session's name using the current
|
|
712
|
+
UTC timestamp, down to microseconds. This ensures that each session name is unique and preserves the overall
|
|
713
|
+
session order.
|
|
714
|
+
|
|
715
|
+
Notes:
|
|
716
|
+
If this class is instantiated on the VRPC, it is expected that the BioHPC server, Synology NAS, and ScanImagePC
|
|
717
|
+
data directories are mounted on the local host-machine via the SMB or equivalent protocol. All manipulations
|
|
718
|
+
with these destinations are carried out with the assumption that the OS has full access to these directories
|
|
719
|
+
and filesystems.
|
|
720
|
+
|
|
721
|
+
If this class is instantiated on the BioHPC server, some methods from this class will not work as expected. It
|
|
722
|
+
is essential that this class is not used outside the default sl-experiment and sl-forgery library runtimes to
|
|
723
|
+
ensure it is used safely.
|
|
724
|
+
|
|
725
|
+
This class is specifically designed for working with the data from a single session, performed by a single
|
|
726
|
+
animal under the specific experiment. The class is used to manage both raw and processed data. It follows the
|
|
727
|
+
data through acquisition, preprocessing and processing stages of the Sun lab data workflow.
|
|
728
|
+
"""
|
|
729
|
+
|
|
730
|
+
animal_id: str
|
|
731
|
+
"""Stores the unique identifier of the animal that participates in the managed session."""
|
|
732
|
+
session_type: str
|
|
733
|
+
"""Stores the type of the session. Primarily, this determines how to read the session_descriptor.yaml file. Has
|
|
734
|
+
to be set to one of the three supported types: 'Lick training', 'Run training' or 'Experiment'.
|
|
735
|
+
"""
|
|
736
|
+
experiment_name: str | None
|
|
737
|
+
"""Stores the name of the experiment configuration file. If the session_name field is set to 'Experiment', this
|
|
738
|
+
field is used to communicate the specific experiment configuration used by the session. During runtime, this is
|
|
739
|
+
used to load the experiment configuration (to run the experiment) and to save the experiment configuration to the
|
|
740
|
+
session raw_data folder. If the session is not an experiment session, this is statically set to None."""
|
|
741
|
+
raw_data: RawData | None
|
|
742
|
+
"""Stores the paths to various directories and files used to store raw and preprocessed session data. Depending on
|
|
743
|
+
class initialization location (VRPC or BioHPC server), the class automatically resolves the root directory path to
|
|
744
|
+
either the VRPC project directory or the BioHPC cluster storage volume."""
|
|
745
|
+
processed_data: ProcessedData | None
|
|
746
|
+
"""Stores the paths to various directories used to store processed session data. This is automatically
|
|
747
|
+
resolved to the fast BioHPC volume (workdir) in all cases, as processed data should only exist on the server."""
|
|
748
|
+
persistent_data: PersistentData | None
|
|
749
|
+
"""Stores the paths to various files and directories kept on VRPC and ScanImagePC after the session data is
|
|
750
|
+
transferred to long-term storage destinations."""
|
|
751
|
+
mesoscope_data: MesoscopeData | None
|
|
752
|
+
"""Stores the paths to various directories used by the ScanImagePC to store mesoscope-acquired session data,
|
|
753
|
+
before it is moved to the VRPC during preprocessing."""
|
|
754
|
+
destinations: Destinations | None
|
|
755
|
+
"""Stores the paths to the destination directories on the BioHPC server and Synology NAS, to which the data is
|
|
756
|
+
copied as part of preprocessing. Both of these directories should be accessible for the VRPC's filesystem via an
|
|
757
|
+
SMB or equivalent protocol."""
|
|
758
|
+
|
|
759
|
+
@classmethod
|
|
760
|
+
def create_session(
|
|
761
|
+
cls,
|
|
762
|
+
animal_id: str,
|
|
763
|
+
session_type: str,
|
|
764
|
+
project_configuration: ProjectConfiguration,
|
|
765
|
+
experiment_name: str | None = None,
|
|
766
|
+
) -> "SessionData":
|
|
767
|
+
"""Creates a new SessionData object and uses it to generate the session's data structure.
|
|
768
|
+
|
|
769
|
+
This method is used to initialize new session runtimes. It always assumes it is called on the VRPC and, as part
|
|
770
|
+
of its runtime, resolves and generates the necessary local and ScanImagePC directories to support acquiring and
|
|
771
|
+
preprocessing session's data.
|
|
772
|
+
|
|
773
|
+
Notes:
|
|
774
|
+
To load an already existing session data structure, use the load_session() method instead.
|
|
775
|
+
|
|
776
|
+
This method automatically dumps the data of the created SessionData instance into the session_data.yaml file
|
|
777
|
+
inside the root raw_data directory of the created hierarchy. It also finds and dumps other configuration
|
|
778
|
+
files, such as project_configuration.yaml, suite2p_configuration.yaml, and experiment_configuration.yaml.
|
|
779
|
+
This way, if the session's runtime is interrupted unexpectedly, it can still be processed.
|
|
780
|
+
|
|
781
|
+
Args:
|
|
782
|
+
animal_id: The ID code of the animal for which the data is acquired.
|
|
783
|
+
session_type: The type of the session. Primarily, this determines how to read the session_descriptor.yaml
|
|
784
|
+
file. Valid options are 'Lick training', 'Run training', or 'Experiment'.
|
|
785
|
+
experiment_name: The name of the experiment to be executed as part of this session. This option is only used
|
|
786
|
+
for 'Experiment' session types. It is used to find the target experiment configuration .YAML file and
|
|
787
|
+
copy it into the session's raw_data directory.
|
|
788
|
+
project_configuration: The initialized ProjectConfiguration instance that stores the data for the session's
|
|
789
|
+
project. This is used to determine the root directory paths for all PCs used in the data workflow.
|
|
790
|
+
|
|
791
|
+
Returns:
|
|
792
|
+
An initialized SessionData instance for the newly created session.
|
|
793
|
+
"""
|
|
794
|
+
|
|
795
|
+
# Acquires the UTC timestamp to use as the session name
|
|
796
|
+
session_name = str(get_timestamp(time_separator="-"))
|
|
797
|
+
|
|
798
|
+
# Extracts the root directory paths stored inside the project configuration file. All roots are expected to be
|
|
799
|
+
# mounted on the local (VRPC) via SMB or equivalent protocol and be relative to the VRPC root.
|
|
800
|
+
vrpc_root = Path(project_configuration.local_root_directory)
|
|
801
|
+
mesoscope_root = Path(project_configuration.local_mesoscope_directory)
|
|
802
|
+
biohpc_root = Path(project_configuration.local_server_directory)
|
|
803
|
+
nas_root = Path(project_configuration.local_nas_directory)
|
|
804
|
+
|
|
805
|
+
# Also extracts the path to fast (working) directory on the BioHPC server. This is used to configure the
|
|
806
|
+
# paths for data processing, which happens on the server.
|
|
807
|
+
biohpc_workdir = Path(project_configuration.remote_working_directory)
|
|
808
|
+
|
|
809
|
+
# Extracts the name of the project stored inside the project configuration file.
|
|
810
|
+
project_name = project_configuration.project_name
|
|
811
|
+
|
|
812
|
+
# Constructs the session directory path and generates the directory
|
|
813
|
+
session_path = vrpc_root.joinpath(project_name, animal_id, session_name)
|
|
814
|
+
remote_session_path = biohpc_workdir.joinpath(project_name, animal_id, session_name)
|
|
815
|
+
|
|
816
|
+
# Handles potential session name conflicts
|
|
817
|
+
counter = 0
|
|
818
|
+
while session_path.exists():
|
|
819
|
+
counter += 1
|
|
820
|
+
new_session_name = f"{session_name}_{counter}"
|
|
821
|
+
session_path = vrpc_root.joinpath(project_name, animal_id, new_session_name)
|
|
822
|
+
remote_session_path = biohpc_workdir.joinpath(project_name, animal_id, new_session_name)
|
|
823
|
+
|
|
824
|
+
# If a conflict is detected and resolved, warns the user about the resolved conflict.
|
|
825
|
+
if counter > 0:
|
|
826
|
+
message = (
|
|
827
|
+
f"Session name conflict occurred for animal '{animal_id}' of project '{project_name}' "
|
|
828
|
+
f"when adding the new session with timestamp {session_name}. The session with identical name "
|
|
829
|
+
f"already exists. The newly created session directory uses a '_{counter}' postfix to distinguish "
|
|
830
|
+
f"itself from the already existing session directory."
|
|
831
|
+
)
|
|
832
|
+
warnings.warn(message=message)
|
|
833
|
+
|
|
834
|
+
# Generates subclasses stored inside the main class instance based on the data resolved above.
|
|
835
|
+
raw_data = RawData(
|
|
836
|
+
raw_data_path=session_path.joinpath("raw_data"),
|
|
837
|
+
camera_data_path=session_path.joinpath("raw_data", "camera_data"),
|
|
838
|
+
mesoscope_data_path=session_path.joinpath("raw_data", "mesoscope_data"),
|
|
839
|
+
behavior_data_path=session_path.joinpath("raw_data", "behavior_data"),
|
|
840
|
+
zaber_positions_path=session_path.joinpath("raw_data", "zaber_positions.yaml"),
|
|
841
|
+
mesoscope_positions_path=session_path.joinpath("raw_data", "mesoscope_positions.yaml"),
|
|
842
|
+
session_descriptor_path=session_path.joinpath("raw_data", "session_descriptor.yaml"),
|
|
843
|
+
hardware_configuration_path=session_path.joinpath("raw_data", "hardware_configuration.yaml"),
|
|
844
|
+
surgery_metadata_path=session_path.joinpath("raw_data", "surgery_metadata.yaml"),
|
|
845
|
+
project_configuration_path=session_path.joinpath("raw_data", "project_configuration.yaml"),
|
|
846
|
+
session_data_path=session_path.joinpath("raw_data", "session_data.yaml"),
|
|
847
|
+
experiment_configuration_path=session_path.joinpath("raw_data", "experiment_configuration.yaml"),
|
|
848
|
+
window_screenshot_path=session_path.joinpath("raw_data", "window_screenshot.png"),
|
|
849
|
+
)
|
|
850
|
+
raw_data.make_dirs() # Generates the local directory tree
|
|
851
|
+
|
|
852
|
+
processed_data = ProcessedData(
|
|
853
|
+
processed_data_path=remote_session_path.joinpath("processed_data"),
|
|
854
|
+
camera_data_path=remote_session_path.joinpath("processed_data", "camera_data"),
|
|
855
|
+
mesoscope_data_path=remote_session_path.joinpath("processed_data", "mesoscope_data"),
|
|
856
|
+
behavior_data_path=remote_session_path.joinpath("processed_data", "behavior_data"),
|
|
857
|
+
deeplabcut_root_path=biohpc_workdir.joinpath(project_name, "deeplabcut"),
|
|
858
|
+
suite2p_configuration_path=biohpc_workdir.joinpath(
|
|
859
|
+
project_name, "configuration", "suite2p_configuration.yaml"
|
|
860
|
+
),
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
vrpc_persistent_path = vrpc_root.joinpath(project_name, animal_id, "persistent_data")
|
|
864
|
+
scanimagepc_persistent_path = mesoscope_root.joinpath(project_name, animal_id, "persistent_data")
|
|
865
|
+
persistent_data = PersistentData(
|
|
866
|
+
zaber_positions_path=vrpc_persistent_path.joinpath("zaber_positions.yaml"),
|
|
867
|
+
mesoscope_positions_path=vrpc_persistent_path.joinpath("mesoscope_positions.yaml"),
|
|
868
|
+
motion_estimator_path=scanimagepc_persistent_path.joinpath("MotionEstimator.me"),
|
|
869
|
+
)
|
|
870
|
+
persistent_data.make_dirs() # Generates all persistent directory trees
|
|
871
|
+
|
|
872
|
+
mesoscope_data = MesoscopeData(
|
|
873
|
+
root_data_path=mesoscope_root,
|
|
874
|
+
mesoscope_data_path=mesoscope_root.joinpath("mesoscope_data"),
|
|
875
|
+
session_specific_mesoscope_data_path=mesoscope_root.joinpath(f"{session_name}_mesoscope_data"),
|
|
876
|
+
)
|
|
877
|
+
mesoscope_data.make_dirs() # Generates all Mesoscope directory trees
|
|
878
|
+
|
|
879
|
+
destinations = Destinations(
|
|
880
|
+
nas_raw_data_path=nas_root.joinpath(project_name, animal_id, session_name, "raw_data"),
|
|
881
|
+
server_raw_data_path=biohpc_root.joinpath(project_name, animal_id, session_name, "raw_data"),
|
|
882
|
+
)
|
|
883
|
+
destinations.make_dirs() # Generates all destination directory trees
|
|
884
|
+
|
|
885
|
+
# Packages the sections generated above into a SessionData instance
|
|
886
|
+
instance = SessionData(
|
|
887
|
+
animal_id=animal_id,
|
|
888
|
+
session_type=session_type,
|
|
889
|
+
raw_data=raw_data,
|
|
890
|
+
processed_data=processed_data,
|
|
891
|
+
persistent_data=persistent_data,
|
|
892
|
+
mesoscope_data=mesoscope_data,
|
|
893
|
+
destinations=destinations,
|
|
894
|
+
experiment_name=experiment_name,
|
|
895
|
+
)
|
|
896
|
+
|
|
897
|
+
# Saves the configured instance data to the session's folder, so that it can be reused during processing or
|
|
898
|
+
# preprocessing
|
|
899
|
+
instance._to_path()
|
|
900
|
+
|
|
901
|
+
# Removes the processed_data section, as it is not used on the VRPC. This makes it impossible to accidentally
|
|
902
|
+
# interact with this section without errors.
|
|
903
|
+
instance.processed_data = None
|
|
904
|
+
|
|
905
|
+
# Extracts and saves the necessary configuration classes to the session raw_data folder. Note, this list of
|
|
906
|
+
# classes is not exhaustive. More classes are saved as part of the session runtime management class start() and
|
|
907
|
+
# __init__() method runtimes:
|
|
908
|
+
|
|
909
|
+
# Resolves the path to the project configuration folder
|
|
910
|
+
vrpc_configuration_path = vrpc_root.joinpath(project_name, "configuration")
|
|
911
|
+
|
|
912
|
+
# Discovers and saves the necessary configuration class instances to the raw_data folder of the managed session:
|
|
913
|
+
# Project Configuration
|
|
914
|
+
sh.copy2(
|
|
915
|
+
src=vrpc_configuration_path.joinpath("project_configuration.yaml"),
|
|
916
|
+
dst=instance.raw_data.project_configuration_path, # type: ignore
|
|
917
|
+
)
|
|
918
|
+
# Experiment Configuration, if the session type is Experiment.
|
|
919
|
+
if experiment_name is not None:
|
|
920
|
+
sh.copy2(
|
|
921
|
+
src=vrpc_configuration_path.joinpath(f"{experiment_name}.yaml"),
|
|
922
|
+
dst=instance.raw_data.experiment_configuration_path, # type: ignore
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
# Returns the initialized SessionData instance to caller
|
|
926
|
+
return instance
|
|
927
|
+
|
|
928
|
+
@classmethod
|
|
929
|
+
def load_session(
|
|
930
|
+
cls,
|
|
931
|
+
session_path: Path,
|
|
932
|
+
on_server: bool,
|
|
933
|
+
) -> "SessionData":
|
|
934
|
+
"""Loads the SessionData instance from the session_data.yaml file of the target session.
|
|
935
|
+
|
|
936
|
+
This method is used to load the data for an already existing session. This is used to call preprocessing
|
|
937
|
+
or processing runtime(s) for the target session. Depending on the call location, the method automatically
|
|
938
|
+
resolves all necessary paths and creates the necessary directories.
|
|
939
|
+
|
|
940
|
+
Notes:
|
|
941
|
+
To create a new session, use the create_session() method instead.
|
|
942
|
+
|
|
943
|
+
Args:
|
|
944
|
+
session_path: The path to the root directory of an existing session, e.g.: vrpc_root/project/animal/session.
|
|
945
|
+
on_server: Determines whether the method is used to initialize an existing session on the VRPC or the
|
|
946
|
+
BioHPC server.
|
|
947
|
+
|
|
948
|
+
Returns:
|
|
949
|
+
An initialized SessionData instance for the session whose data is stored at the provided path.
|
|
950
|
+
|
|
951
|
+
Raises:
|
|
952
|
+
FileNotFoundError: If the 'session_data.yaml' file is not found after resolving the provided path.
|
|
953
|
+
"""
|
|
954
|
+
# To properly initialize the SessionData instance, the provided path should contain the raw_data directory
|
|
955
|
+
# with session_data.yaml file.
|
|
956
|
+
session_data_path = session_path.joinpath("raw_data", "session_data.yaml")
|
|
957
|
+
if not session_data_path.exists():
|
|
958
|
+
message = (
|
|
959
|
+
f"Unable to load the SessionData class for the target session: {session_path.stem}. No "
|
|
960
|
+
f"session_data.yaml file was found inside the raw_data folder of the session. This likely "
|
|
961
|
+
f"indicates that the session runtime was interrupted before recording any data, or that the "
|
|
962
|
+
f"session path does not point to a valid session."
|
|
963
|
+
)
|
|
964
|
+
console.error(message=message, error=FileNotFoundError)
|
|
965
|
+
|
|
966
|
+
# Loads class data from .yaml
|
|
967
|
+
instance: SessionData = cls.from_yaml(file_path=session_path) # type: ignore
|
|
968
|
+
|
|
969
|
+
# Depending on whether the class is initialized on the VRPC or BioHPC server, resolves the local (raw_data)
|
|
970
|
+
# directory path. With the way this class is used, if on_server is False, the class is already
|
|
971
|
+
# well-configured. This is because the class is always created on the VRPC and, when it leaves VRPC, it is
|
|
972
|
+
# never used on VRPC again. Therefore, additional processing is ONLY done when on_server is True.
|
|
973
|
+
if on_server:
|
|
974
|
+
# Disables VRPC-only sections. This makes it impossible to call these sections on BioHPC server without
|
|
975
|
+
# runtime interruption.
|
|
976
|
+
instance.mesoscope_data = None
|
|
977
|
+
instance.persistent_data = None
|
|
978
|
+
instance.destinations = None
|
|
979
|
+
|
|
980
|
+
# Reconfigures the raw_data section to use the root provided as part of the session_path.
|
|
981
|
+
instance.raw_data.switch_root(new_root=session_path) # type: ignore
|
|
982
|
+
|
|
983
|
+
# Processed Data section is always configured to use the BioHPC server root. Calls its' make_dirs() method
|
|
984
|
+
# to setup directories
|
|
985
|
+
instance.processed_data.make_dirs() # type: ignore
|
|
986
|
+
|
|
987
|
+
# Returns the initialized SessionData instance to caller
|
|
988
|
+
return instance
|
|
989
|
+
|
|
990
|
+
def _to_path(self) -> None:
|
|
991
|
+
"""Saves the instance data to the 'raw_data' directory of the managed session as a 'session_data.yaml' file.
|
|
992
|
+
|
|
993
|
+
This is used to save the data stored in the instance to disk, so that it can be reused during preprocessing or
|
|
994
|
+
data processing. The method is intended to only be used by the SessionData instance itself during its
|
|
995
|
+
create_session() method runtime.
|
|
996
|
+
"""
|
|
997
|
+
|
|
998
|
+
# Extracts the target file path before it is converted to a string.
|
|
999
|
+
file_path: Path = copy.copy(self.raw_data.session_data_path) # type: ignore
|
|
1000
|
+
|
|
1001
|
+
# Converts all Paths objects to strings before dumping the data to YAML.
|
|
1002
|
+
if self.raw_data is not None:
|
|
1003
|
+
self.raw_data.make_string()
|
|
1004
|
+
if self.processed_data is not None:
|
|
1005
|
+
self.processed_data.make_string()
|
|
1006
|
+
if self.persistent_data is not None:
|
|
1007
|
+
self.persistent_data.make_string()
|
|
1008
|
+
if self.mesoscope_data is not None:
|
|
1009
|
+
self.mesoscope_data.make_string()
|
|
1010
|
+
if self.destinations is not None:
|
|
1011
|
+
self.destinations.make_string()
|
|
1012
|
+
|
|
1013
|
+
# Saves instance data as a .YAML file
|
|
1014
|
+
self.to_yaml(file_path=file_path)
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
@dataclass()
|
|
1018
|
+
class ExperimentState:
|
|
1019
|
+
"""Encapsulates the information used to set and maintain the desired experiment and Mesoscope-VR system state.
|
|
1020
|
+
|
|
1021
|
+
Primarily, experiment runtime logic (task logic) is resolved by the Unity game engine. However, the Mesoscope-VR
|
|
1022
|
+
system configuration may also need to change throughout the experiment to optimize the runtime by disabling or
|
|
1023
|
+
reconfiguring specific hardware modules. For example, some experiment stages may require the running wheel to be
|
|
1024
|
+
locked to prevent the animal from running, and other may require the VR screens to be turned off.
|
|
1025
|
+
"""
|
|
1026
|
+
|
|
1027
|
+
experiment_state_code: int
|
|
1028
|
+
"""The integer code of the experiment state. Experiment states do not have a predefined meaning, Instead, each
|
|
1029
|
+
project is expected to define and follow its own experiment state code mapping. Typically, the experiment state
|
|
1030
|
+
code is used to denote major experiment stages, such as 'baseline', 'task', 'cooldown', etc. Note, the same
|
|
1031
|
+
experiment state code can be used by multiple sequential ExperimentState instances to change the VR system states
|
|
1032
|
+
while maintaining the same experiment state."""
|
|
1033
|
+
vr_state_code: int
|
|
1034
|
+
"""One of the supported VR system state-codes. Currently, the Mesoscope-VR system supports two state codes. State
|
|
1035
|
+
code '1' denotes 'REST' state and code '2' denotes 'RUN' state. Note, multiple consecutive ExperimentState
|
|
1036
|
+
instances with different experiment state codes can reuse the same VR state code."""
|
|
1037
|
+
state_duration_s: float
|
|
1038
|
+
"""The time, in seconds, to maintain the current combination of the experiment and VR states."""
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
@dataclass()
|
|
1042
|
+
class ExperimentConfiguration(YamlConfig):
|
|
1043
|
+
"""Stores the configuration of a single experiment runtime.
|
|
1044
|
+
|
|
1045
|
+
Primarily, this includes the sequence of experiment and Virtual Reality (Mesoscope-VR) states that defines the flow
|
|
1046
|
+
of the experiment runtime. During runtime, the main runtime control function traverses the sequence of states
|
|
1047
|
+
stored in this class instance start-to-end in the exact order specified by the user. Together with custom Unity
|
|
1048
|
+
projects that define the task logic (how the system responds to animal interactions with the VR system) this class
|
|
1049
|
+
allows flexibly implementing a wide range of experiments.
|
|
1050
|
+
|
|
1051
|
+
Each project should define one or more experiment configurations and save them as .yaml files inside the project
|
|
1052
|
+
'configuration' folder. The name for each configuration file is defined by the user and is used to identify and load
|
|
1053
|
+
the experiment configuration when 'sl-run-experiment' CLI command exposed by the sl-experiment library is executed.
|
|
1054
|
+
"""
|
|
1055
|
+
|
|
1056
|
+
cue_map: dict[int, float] = field(default_factory=lambda: {0: 30.0, 1: 30.0, 2: 30.0, 3: 30.0, 4: 30.0})
|
|
1057
|
+
"""A dictionary that maps each integer-code associated with a wall cue used in the Virtual Reality experiment
|
|
1058
|
+
environment to its length in real-world centimeters. It is used to map each VR cue to the distance the animal needs
|
|
1059
|
+
to travel to fully traverse the wall cue region from start to end."""
|
|
1060
|
+
experiment_states: dict[str, ExperimentState] = field(
|
|
1061
|
+
default_factory=lambda: {
|
|
1062
|
+
"baseline": ExperimentState(experiment_state_code=1, vr_state_code=1, state_duration_s=30),
|
|
1063
|
+
"experiment": ExperimentState(experiment_state_code=2, vr_state_code=2, state_duration_s=120),
|
|
1064
|
+
"cooldown": ExperimentState(experiment_state_code=3, vr_state_code=1, state_duration_s=15),
|
|
1065
|
+
}
|
|
1066
|
+
)
|
|
1067
|
+
"""A dictionary that uses human-readable state-names as keys and ExperimentState instances as values. Each
|
|
1068
|
+
ExperimentState instance represents a phase of the experiment."""
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
@dataclass()
|
|
1072
|
+
class HardwareConfiguration(YamlConfig):
|
|
1073
|
+
"""This class is used to save the runtime hardware configuration parameters as a .yaml file.
|
|
1074
|
+
|
|
1075
|
+
This information is used to read and decode the data saved to the .npz log files during runtime as part of data
|
|
1076
|
+
processing.
|
|
1077
|
+
|
|
1078
|
+
Notes:
|
|
1079
|
+
All fields in this dataclass initialize to None. During log processing, any log associated with a hardware
|
|
1080
|
+
module that provides the data stored in a field will be processed, unless that field is None. Therefore, setting
|
|
1081
|
+
any field in this dataclass to None also functions as a flag for whether to parse the log associated with the
|
|
1082
|
+
module that provides this field's information.
|
|
1083
|
+
|
|
1084
|
+
This class is automatically configured by MesoscopeExperiment and BehaviorTraining classes from sl-experiment
|
|
1085
|
+
library to facilitate log parsing.
|
|
1086
|
+
"""
|
|
1087
|
+
|
|
1088
|
+
cue_map: dict[int, float] | None = None
|
|
1089
|
+
"""MesoscopeExperiment instance property. Stores the dictionary that maps the integer id-codes associated with each
|
|
1090
|
+
wall cue in the Virtual Reality task environment with distances in real-world centimeters animals should run on the
|
|
1091
|
+
wheel to fully traverse the cue region on a linearized track."""
|
|
1092
|
+
cm_per_pulse: float | None = None
|
|
1093
|
+
"""EncoderInterface instance property. Stores the conversion factor used to translate encoder pulses into
|
|
1094
|
+
real-world centimeters."""
|
|
1095
|
+
maximum_break_strength: float | None = None
|
|
1096
|
+
"""BreakInterface instance property. Stores the breaking torque, in Newton centimeters, applied by the break to
|
|
1097
|
+
the edge of the running wheel when it is engaged at 100% strength."""
|
|
1098
|
+
minimum_break_strength: float | None = None
|
|
1099
|
+
"""BreakInterface instance property. Stores the breaking torque, in Newton centimeters, applied by the break to
|
|
1100
|
+
the edge of the running wheel when it is engaged at 0% strength (completely disengaged)."""
|
|
1101
|
+
lick_threshold: int | None = None
|
|
1102
|
+
"""LickInterface instance property. Determines the threshold, in 12-bit Analog to Digital Converter (ADC) units,
|
|
1103
|
+
above which an interaction value reported by the lick sensor is considered a lick (compared to noise or non-lick
|
|
1104
|
+
touch)."""
|
|
1105
|
+
valve_scale_coefficient: float | None = None
|
|
1106
|
+
"""ValveInterface instance property. To dispense precise water volumes during runtime, ValveInterface uses power
|
|
1107
|
+
law equation applied to valve calibration data to determine how long to keep the valve open. This stores the
|
|
1108
|
+
scale_coefficient of the power law equation that describes the relationship between valve open time and dispensed
|
|
1109
|
+
water volume, derived from calibration data."""
|
|
1110
|
+
valve_nonlinearity_exponent: float | None = None
|
|
1111
|
+
"""ValveInterface instance property. To dispense precise water volumes during runtime, ValveInterface uses power
|
|
1112
|
+
law equation applied to valve calibration data to determine how long to keep the valve open. This stores the
|
|
1113
|
+
nonlinearity_exponent of the power law equation that describes the relationship between valve open time and
|
|
1114
|
+
dispensed water volume, derived from calibration data."""
|
|
1115
|
+
torque_per_adc_unit: float | None = None
|
|
1116
|
+
"""TorqueInterface instance property. Stores the conversion factor used to translate torque values reported by the
|
|
1117
|
+
sensor as 12-bit Analog to Digital Converter (ADC) units, into real-world Newton centimeters (N·cm) of torque that
|
|
1118
|
+
had to be applied to the edge of the running wheel to produce the observed ADC value."""
|
|
1119
|
+
screens_initially_on: bool | None = None
|
|
1120
|
+
"""ScreenInterface instance property. Stores the initial state of the Virtual Reality screens at the beginning of
|
|
1121
|
+
the session runtime."""
|
|
1122
|
+
recorded_mesoscope_ttl: bool | None = None
|
|
1123
|
+
"""TTLInterface instance property. A boolean flag that determines whether the processed session recorded brain
|
|
1124
|
+
activity data with the mesoscope."""
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
@dataclass()
|
|
1128
|
+
class LickTrainingDescriptor(YamlConfig):
|
|
1129
|
+
"""This class is used to save the description information specific to lick training sessions as a .yaml file.
|
|
1130
|
+
|
|
1131
|
+
The information stored in this class instance is filled in two steps. The main runtime function fills most fields
|
|
1132
|
+
of the class, before it is saved as a .yaml file. After runtime, the experimenter manually fills leftover fields,
|
|
1133
|
+
such as 'experimenter_notes,' before the class instance is transferred to the long-term storage destination.
|
|
1134
|
+
|
|
1135
|
+
The fully filled instance data is also used during preprocessing to write the water restriction log entry for the
|
|
1136
|
+
trained animal.
|
|
1137
|
+
"""
|
|
1138
|
+
|
|
1139
|
+
experimenter: str
|
|
1140
|
+
"""The ID of the experimenter running the session."""
|
|
1141
|
+
mouse_weight_g: float
|
|
1142
|
+
"""The weight of the animal, in grams, at the beginning of the session."""
|
|
1143
|
+
dispensed_water_volume_ml: float
|
|
1144
|
+
"""Stores the total water volume, in milliliters, dispensed during runtime."""
|
|
1145
|
+
minimum_reward_delay: int
|
|
1146
|
+
"""Stores the minimum delay, in seconds, that can separate the delivery of two consecutive water rewards."""
|
|
1147
|
+
maximum_reward_delay_s: int
|
|
1148
|
+
"""Stores the maximum delay, in seconds, that can separate the delivery of two consecutive water rewards."""
|
|
1149
|
+
maximum_water_volume_ml: float
|
|
1150
|
+
"""Stores the maximum volume of water the system is allowed to dispense during training."""
|
|
1151
|
+
maximum_training_time_m: int
|
|
1152
|
+
"""Stores the maximum time, in minutes, the system is allowed to run the training for."""
|
|
1153
|
+
experimenter_notes: str = "Replace this with your notes."
|
|
1154
|
+
"""This field is not set during runtime. It is expected that each experimenter replaces this field with their
|
|
1155
|
+
notes made during runtime."""
|
|
1156
|
+
experimenter_given_water_volume_ml: float = 0.0
|
|
1157
|
+
"""The additional volume of water, in milliliters, administered by the experimenter to the animal after the session.
|
|
1158
|
+
"""
|
|
1159
|
+
|
|
1160
|
+
|
|
1161
|
+
@dataclass()
|
|
1162
|
+
class RunTrainingDescriptor(YamlConfig):
|
|
1163
|
+
"""This class is used to save the description information specific to run training sessions as a .yaml file.
|
|
1164
|
+
|
|
1165
|
+
The information stored in this class instance is filled in two steps. The main runtime function fills most fields
|
|
1166
|
+
of the class, before it is saved as a .yaml file. After runtime, the experimenter manually fills leftover fields,
|
|
1167
|
+
such as 'experimenter_notes,' before the class instance is transferred to the long-term storage destination.
|
|
1168
|
+
|
|
1169
|
+
The fully filled instance data is also used during preprocessing to write the water restriction log entry for the
|
|
1170
|
+
trained animal.
|
|
1171
|
+
"""
|
|
1172
|
+
|
|
1173
|
+
experimenter: str
|
|
1174
|
+
"""The ID of the experimenter running the session."""
|
|
1175
|
+
mouse_weight_g: float
|
|
1176
|
+
"""The weight of the animal, in grams, at the beginning of the session."""
|
|
1177
|
+
dispensed_water_volume_ml: float
|
|
1178
|
+
"""Stores the total water volume, in milliliters, dispensed during runtime."""
|
|
1179
|
+
final_run_speed_threshold_cm_s: float
|
|
1180
|
+
"""Stores the final running speed threshold, in centimeters per second, that was active at the end of training."""
|
|
1181
|
+
final_run_duration_threshold_s: float
|
|
1182
|
+
"""Stores the final running duration threshold, in seconds, that was active at the end of training."""
|
|
1183
|
+
initial_run_speed_threshold_cm_s: float
|
|
1184
|
+
"""Stores the initial running speed threshold, in centimeters per second, used during training."""
|
|
1185
|
+
initial_run_duration_threshold_s: float
|
|
1186
|
+
"""Stores the initial running duration threshold, in seconds, used during training."""
|
|
1187
|
+
increase_threshold_ml: float
|
|
1188
|
+
"""Stores the volume of water delivered to the animal, in milliliters, that triggers the increase in the running
|
|
1189
|
+
speed and duration thresholds."""
|
|
1190
|
+
run_speed_increase_step_cm_s: float
|
|
1191
|
+
"""Stores the value, in centimeters per second, used by the system to increment the running speed threshold each
|
|
1192
|
+
time the animal receives 'increase_threshold' volume of water."""
|
|
1193
|
+
run_duration_increase_step_s: float
|
|
1194
|
+
"""Stores the value, in seconds, used by the system to increment the duration threshold each time the animal
|
|
1195
|
+
receives 'increase_threshold' volume of water."""
|
|
1196
|
+
maximum_water_volume_ml: float
|
|
1197
|
+
"""Stores the maximum volume of water the system is allowed to dispense during training."""
|
|
1198
|
+
maximum_training_time_m: int
|
|
1199
|
+
"""Stores the maximum time, in minutes, the system is allowed to run the training for."""
|
|
1200
|
+
experimenter_notes: str = "Replace this with your notes."
|
|
1201
|
+
"""This field is not set during runtime. It is expected that each experimenter will replace this field with their
|
|
1202
|
+
notes made during runtime."""
|
|
1203
|
+
experimenter_given_water_volume_ml: float = 0.0
|
|
1204
|
+
"""The additional volume of water, in milliliters, administered by the experimenter to the animal after the session.
|
|
1205
|
+
"""
|
|
1206
|
+
|
|
1207
|
+
|
|
1208
|
+
@dataclass()
|
|
1209
|
+
class MesoscopeExperimentDescriptor(YamlConfig):
|
|
1210
|
+
"""This class is used to save the description information specific to experiment sessions as a .yaml file.
|
|
1211
|
+
|
|
1212
|
+
The information stored in this class instance is filled in two steps. The main runtime function fills most fields
|
|
1213
|
+
of the class, before it is saved as a .yaml file. After runtime, the experimenter manually fills leftover fields,
|
|
1214
|
+
such as 'experimenter_notes,' before the class instance is transferred to the long-term storage destination.
|
|
1215
|
+
|
|
1216
|
+
The fully filled instance data is also used during preprocessing to write the water restriction log entry for the
|
|
1217
|
+
animal participating in the experiment runtime.
|
|
1218
|
+
"""
|
|
1219
|
+
|
|
1220
|
+
experimenter: str
|
|
1221
|
+
"""The ID of the experimenter running the session."""
|
|
1222
|
+
mouse_weight_g: float
|
|
1223
|
+
"""The weight of the animal, in grams, at the beginning of the session."""
|
|
1224
|
+
dispensed_water_volume_ml: float
|
|
1225
|
+
"""Stores the total water volume, in milliliters, dispensed during runtime."""
|
|
1226
|
+
experimenter_notes: str = "Replace this with your notes."
|
|
1227
|
+
"""This field is not set during runtime. It is expected that each experimenter will replace this field with their
|
|
1228
|
+
notes made during runtime."""
|
|
1229
|
+
experimenter_given_water_volume_ml: float = 0.0
|
|
1230
|
+
"""The additional volume of water, in milliliters, administered by the experimenter to the animal after the session.
|
|
1231
|
+
"""
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
@dataclass()
|
|
1235
|
+
class ZaberPositions(YamlConfig):
|
|
1236
|
+
"""This class is used to save Zaber motor positions as a .yaml file to reuse them between sessions.
|
|
1237
|
+
|
|
1238
|
+
The class is specifically designed to store, save, and load the positions of the LickPort and HeadBar motors
|
|
1239
|
+
(axes). It is used to both store Zaber motor positions for each session for future analysis and to restore the same
|
|
1240
|
+
Zaber motor positions across consecutive runtimes for the same project and animal combination.
|
|
1241
|
+
|
|
1242
|
+
Notes:
|
|
1243
|
+
All positions are saved using native motor units. All class fields initialize to default placeholders that are
|
|
1244
|
+
likely NOT safe to apply to the VR system. Do not apply the positions loaded from the file unless you are
|
|
1245
|
+
certain they are safe to use.
|
|
1246
|
+
|
|
1247
|
+
Exercise caution when working with Zaber motors. The motors are powerful enough to damage the surrounding
|
|
1248
|
+
equipment and manipulated objects. Do not modify the data stored inside the .yaml file unless you know what you
|
|
1249
|
+
are doing.
|
|
1250
|
+
"""
|
|
1251
|
+
|
|
1252
|
+
headbar_z: int = 0
|
|
1253
|
+
"""The absolute position, in native motor units, of the HeadBar z-axis motor."""
|
|
1254
|
+
headbar_pitch: int = 0
|
|
1255
|
+
"""The absolute position, in native motor units, of the HeadBar pitch-axis motor."""
|
|
1256
|
+
headbar_roll: int = 0
|
|
1257
|
+
"""The absolute position, in native motor units, of the HeadBar roll-axis motor."""
|
|
1258
|
+
lickport_z: int = 0
|
|
1259
|
+
"""The absolute position, in native motor units, of the LickPort z-axis motor."""
|
|
1260
|
+
lickport_x: int = 0
|
|
1261
|
+
"""The absolute position, in native motor units, of the LickPort x-axis motor."""
|
|
1262
|
+
lickport_y: int = 0
|
|
1263
|
+
"""The absolute position, in native motor units, of the LickPort y-axis motor."""
|
|
1264
|
+
|
|
1265
|
+
|
|
1266
|
+
@dataclass()
|
|
1267
|
+
class MesoscopePositions(YamlConfig):
|
|
1268
|
+
"""This class is used to save the real and virtual Mesoscope objective positions as a .yaml file to reuse it
|
|
1269
|
+
between experiment sessions.
|
|
1270
|
+
|
|
1271
|
+
Primarily, the class is used to help the experimenter to position the Mesoscope at the same position across
|
|
1272
|
+
multiple imaging sessions. It stores both the physical (real) position of the objective along the motorized
|
|
1273
|
+
X, Y, Z, and Roll axes and the virtual (ScanImage software) tip, tilt, and fastZ focus axes.
|
|
1274
|
+
|
|
1275
|
+
Notes:
|
|
1276
|
+
Since the API to read and write these positions automatically is currently not available, this class relies on
|
|
1277
|
+
the experimenter manually entering all positions and setting the mesoscope to these positions when necessary.
|
|
1278
|
+
"""
|
|
1279
|
+
|
|
1280
|
+
mesoscope_x_position: float = 0.0
|
|
1281
|
+
"""The X-axis position, in centimeters, of the Mesoscope objective used during session runtime."""
|
|
1282
|
+
mesoscope_y_position: float = 0.0
|
|
1283
|
+
"""The Y-axis position, in centimeters, of the Mesoscope objective used during session runtime."""
|
|
1284
|
+
mesoscope_roll_position: float = 0.0
|
|
1285
|
+
"""The Roll-axis position, in degrees, of the Mesoscope objective used during session runtime."""
|
|
1286
|
+
mesoscope_z_position: float = 0.0
|
|
1287
|
+
"""The Z-axis position, in centimeters, of the Mesoscope objective used during session runtime."""
|
|
1288
|
+
mesoscope_fast_z_position: float = 0.0
|
|
1289
|
+
"""The Fast-Z-axis position, in micrometers, of the Mesoscope objective used during session runtime."""
|
|
1290
|
+
mesoscope_tip_position: float = 0.0
|
|
1291
|
+
"""The Tilt-axis position, in degrees, of the Mesoscope objective used during session runtime."""
|
|
1292
|
+
mesoscope_tilt_position: float = 0.0
|
|
1293
|
+
"""The Tip-axis position, in degrees, of the Mesoscope objective used during session runtime."""
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
@dataclass()
|
|
1297
|
+
class SubjectData:
|
|
1298
|
+
"""Stores the ID information of the surgical intervention's subject (animal)."""
|
|
1299
|
+
|
|
1300
|
+
id: int
|
|
1301
|
+
"""Stores the unique ID (name) of the subject. Assumes all animals are given a numeric ID, rather than a string
|
|
1302
|
+
name."""
|
|
1303
|
+
ear_punch: str
|
|
1304
|
+
"""Stores the ear tag location of the subject."""
|
|
1305
|
+
sex: str
|
|
1306
|
+
"""Stores the gender of the subject."""
|
|
1307
|
+
genotype: str
|
|
1308
|
+
"""Stores the genotype of the subject."""
|
|
1309
|
+
date_of_birth_us: int
|
|
1310
|
+
"""Stores the date of birth of the subject as the number of microseconds elapsed since UTC epoch onset."""
|
|
1311
|
+
weight_g: float
|
|
1312
|
+
"""Stores the weight of the subject pre-surgery, in grams."""
|
|
1313
|
+
cage: int
|
|
1314
|
+
"""Stores the number of the cage used to house the subject after surgery."""
|
|
1315
|
+
location_housed: str
|
|
1316
|
+
"""Stores the location used to house the subject after the surgery."""
|
|
1317
|
+
status: str
|
|
1318
|
+
"""Stores the current status of the subject (alive / deceased)."""
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
@dataclass()
|
|
1322
|
+
class ProcedureData:
|
|
1323
|
+
"""Stores the general information about the surgical intervention."""
|
|
1324
|
+
|
|
1325
|
+
surgery_start_us: int
|
|
1326
|
+
"""Stores the date and time when the surgery has started as microseconds elapsed since UTC epoch onset."""
|
|
1327
|
+
surgery_end_us: int
|
|
1328
|
+
"""Stores the date and time when the surgery has ended as microseconds elapsed since UTC epoch onset."""
|
|
1329
|
+
surgeon: str
|
|
1330
|
+
"""Stores the name or ID of the surgeon. If the intervention was carried out by multiple surgeons, all participating
|
|
1331
|
+
surgeon names and IDs are stored as part of the same string."""
|
|
1332
|
+
protocol: str
|
|
1333
|
+
"""Stores the experiment protocol number (ID) used during the surgery."""
|
|
1334
|
+
surgery_notes: str
|
|
1335
|
+
"""Stores surgeon's notes taken during the surgery."""
|
|
1336
|
+
post_op_notes: str
|
|
1337
|
+
"""Stores surgeon's notes taken during the post-surgery recovery period."""
|
|
1338
|
+
|
|
1339
|
+
|
|
1340
|
+
@dataclass
|
|
1341
|
+
class ImplantData:
|
|
1342
|
+
"""Stores the information about a single implantation performed during the surgical intervention.
|
|
1343
|
+
|
|
1344
|
+
Multiple ImplantData instances are used at the same time if the surgery involved multiple implants.
|
|
1345
|
+
"""
|
|
1346
|
+
|
|
1347
|
+
implant: str
|
|
1348
|
+
"""The descriptive name of the implant."""
|
|
1349
|
+
implant_target: str
|
|
1350
|
+
"""The name of the brain region or cranium section targeted by the implant."""
|
|
1351
|
+
implant_code: int
|
|
1352
|
+
"""The manufacturer code or internal reference code for the implant. This code is used to identify the implant in
|
|
1353
|
+
additional datasheets and lab ordering documents."""
|
|
1354
|
+
implant_ap_coordinate_mm: float
|
|
1355
|
+
"""Stores implant's antero-posterior stereotactic coordinate, in millimeters, relative to bregma."""
|
|
1356
|
+
implant_ml_coordinate_mm: float
|
|
1357
|
+
"""Stores implant's medial-lateral stereotactic coordinate, in millimeters, relative to bregma."""
|
|
1358
|
+
implant_dv_coordinate_mm: float
|
|
1359
|
+
"""Stores implant's dorsal-ventral stereotactic coordinate, in millimeters, relative to bregma."""
|
|
1360
|
+
|
|
1361
|
+
|
|
1362
|
+
@dataclass
|
|
1363
|
+
class InjectionData:
|
|
1364
|
+
"""Stores the information about a single injection performed during surgical intervention.
|
|
1365
|
+
|
|
1366
|
+
Multiple InjectionData instances are used at the same time if the surgery involved multiple injections.
|
|
1367
|
+
"""
|
|
1368
|
+
|
|
1369
|
+
injection: str
|
|
1370
|
+
"""The descriptive name of the injection."""
|
|
1371
|
+
injection_target: str
|
|
1372
|
+
"""The name of the brain region targeted by the injection."""
|
|
1373
|
+
injection_volume_nl: float
|
|
1374
|
+
"""The volume of substance, in nanoliters, delivered during the injection."""
|
|
1375
|
+
injection_code: int
|
|
1376
|
+
"""The manufacturer code or internal reference code for the injected substance. This code is used to identify the
|
|
1377
|
+
substance in additional datasheets and lab ordering documents."""
|
|
1378
|
+
injection_ap_coordinate_mm: float
|
|
1379
|
+
"""Stores injection's antero-posterior stereotactic coordinate, in millimeters, relative to bregma."""
|
|
1380
|
+
injection_ml_coordinate_mm: float
|
|
1381
|
+
"""Stores injection's medial-lateral stereotactic coordinate, in millimeters, relative to bregma."""
|
|
1382
|
+
injection_dv_coordinate_mm: float
|
|
1383
|
+
"""Stores injection's dorsal-ventral stereotactic coordinate, in millimeters, relative to bregma."""
|
|
1384
|
+
|
|
1385
|
+
|
|
1386
|
+
@dataclass
|
|
1387
|
+
class DrugData:
|
|
1388
|
+
"""Stores the information about all drugs administered to the subject before, during, and immediately after the
|
|
1389
|
+
surgical intervention.
|
|
1390
|
+
"""
|
|
1391
|
+
|
|
1392
|
+
lactated_ringers_solution_volume_ml: float
|
|
1393
|
+
"""Stores the volume of Lactated Ringer's Solution (LRS) administered during surgery, in ml."""
|
|
1394
|
+
lactated_ringers_solution_code: int
|
|
1395
|
+
"""Stores the manufacturer code or internal reference code for Lactated Ringer's Solution (LRS). This code is used
|
|
1396
|
+
to identify the LRS batch in additional datasheets and lab ordering documents."""
|
|
1397
|
+
ketoprofen_volume_ml: float
|
|
1398
|
+
"""Stores the volume of ketoprofen diluted with saline administered during surgery, in ml."""
|
|
1399
|
+
ketoprofen_code: int
|
|
1400
|
+
"""Stores the manufacturer code or internal reference code for ketoprofen. This code is used to identify the
|
|
1401
|
+
ketoprofen batch in additional datasheets and lab ordering documents."""
|
|
1402
|
+
buprenorphine_volume_ml: float
|
|
1403
|
+
"""Stores the volume of buprenorphine diluted with saline administered during surgery, in ml."""
|
|
1404
|
+
buprenorphine_code: int
|
|
1405
|
+
"""Stores the manufacturer code or internal reference code for buprenorphine. This code is used to identify the
|
|
1406
|
+
buprenorphine batch in additional datasheets and lab ordering documents."""
|
|
1407
|
+
dexamethasone_volume_ml: float
|
|
1408
|
+
"""Stores the volume of dexamethasone diluted with saline administered during surgery, in ml."""
|
|
1409
|
+
dexamethasone_code: int
|
|
1410
|
+
"""Stores the manufacturer code or internal reference code for dexamethasone. This code is used to identify the
|
|
1411
|
+
dexamethasone batch in additional datasheets and lab ordering documents."""
|
|
1412
|
+
|
|
1413
|
+
|
|
1414
|
+
@dataclass
|
|
1415
|
+
class SurgeryData(YamlConfig):
|
|
1416
|
+
"""Stores the data about a single mouse surgical intervention.
|
|
1417
|
+
|
|
1418
|
+
This class aggregates other dataclass instances that store specific data about the surgical procedure. Primarily, it
|
|
1419
|
+
is used to save the data as a .yaml file to every session's raw_data directory of each animal used in every lab
|
|
1420
|
+
project. This way, the surgery data is always stored alongside the behavior and brain activity data collected
|
|
1421
|
+
during the session.
|
|
1422
|
+
"""
|
|
1423
|
+
|
|
1424
|
+
subject: SubjectData
|
|
1425
|
+
"""Stores the ID information about the subject (mouse)."""
|
|
1426
|
+
procedure: ProcedureData
|
|
1427
|
+
"""Stores general data about the surgical intervention."""
|
|
1428
|
+
drugs: DrugData
|
|
1429
|
+
"""Stores the data about the substances subcutaneously injected into the subject before, during and immediately
|
|
1430
|
+
after the surgical intervention."""
|
|
1431
|
+
implants: list[ImplantData]
|
|
1432
|
+
"""Stores the data for all cranial and transcranial implants introduced to the subject during the surgical
|
|
1433
|
+
intervention."""
|
|
1434
|
+
injections: list[InjectionData]
|
|
1435
|
+
"""Stores the data about all substances infused into the brain of the subject during the surgical intervention."""
|