sl-shared-assets 1.0.0rc20__py3-none-any.whl → 1.0.0rc21__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 +27 -27
- sl_shared_assets/__init__.pyi +24 -22
- sl_shared_assets/cli.py +266 -40
- sl_shared_assets/cli.pyi +73 -14
- sl_shared_assets/data_classes/__init__.py +23 -20
- sl_shared_assets/data_classes/__init__.pyi +18 -18
- sl_shared_assets/data_classes/configuration_data.py +407 -26
- sl_shared_assets/data_classes/configuration_data.pyi +172 -15
- sl_shared_assets/data_classes/runtime_data.py +49 -43
- sl_shared_assets/data_classes/runtime_data.pyi +37 -40
- sl_shared_assets/data_classes/session_data.py +168 -914
- sl_shared_assets/data_classes/session_data.pyi +55 -350
- sl_shared_assets/data_classes/surgery_data.py +3 -3
- sl_shared_assets/data_classes/surgery_data.pyi +2 -2
- sl_shared_assets/tools/__init__.py +8 -1
- sl_shared_assets/tools/__init__.pyi +11 -1
- sl_shared_assets/tools/ascension_tools.py +27 -26
- sl_shared_assets/tools/ascension_tools.pyi +5 -5
- sl_shared_assets/tools/packaging_tools.py +14 -1
- sl_shared_assets/tools/packaging_tools.pyi +4 -0
- sl_shared_assets/tools/project_management_tools.py +164 -0
- sl_shared_assets/tools/project_management_tools.pyi +48 -0
- {sl_shared_assets-1.0.0rc20.dist-info → sl_shared_assets-1.0.0rc21.dist-info}/METADATA +21 -4
- sl_shared_assets-1.0.0rc21.dist-info/RECORD +36 -0
- sl_shared_assets-1.0.0rc21.dist-info/entry_points.txt +8 -0
- sl_shared_assets/suite2p/__init__.py +0 -8
- sl_shared_assets/suite2p/__init__.pyi +0 -4
- sl_shared_assets/suite2p/multi_day.py +0 -224
- sl_shared_assets/suite2p/multi_day.pyi +0 -104
- sl_shared_assets/suite2p/single_day.py +0 -564
- sl_shared_assets/suite2p/single_day.pyi +0 -220
- sl_shared_assets-1.0.0rc20.dist-info/RECORD +0 -40
- sl_shared_assets-1.0.0rc20.dist-info/entry_points.txt +0 -4
- {sl_shared_assets-1.0.0rc20.dist-info → sl_shared_assets-1.0.0rc21.dist-info}/WHEEL +0 -0
- {sl_shared_assets-1.0.0rc20.dist-info → sl_shared_assets-1.0.0rc21.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,52 +1,64 @@
|
|
|
1
|
-
"""This module provides classes used to configure data acquisition and processing runtimes in the Sun lab.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
lab pipelines."""
|
|
1
|
+
"""This module provides classes used to configure data acquisition and processing runtimes in the Sun lab. All lab
|
|
2
|
+
projects use classes from this module to configure experiment runtimes and determine how to interact with the
|
|
3
|
+
particular data acquisition and runtime management system (hardware) they run on."""
|
|
5
4
|
|
|
6
5
|
import copy
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
from dataclasses import field, dataclass
|
|
9
8
|
|
|
9
|
+
import appdirs
|
|
10
|
+
from ataraxis_base_utilities import LogLevel, console, ensure_directory_exists
|
|
10
11
|
from ataraxis_data_structures import YamlConfig
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@dataclass()
|
|
14
15
|
class ExperimentState:
|
|
15
|
-
"""Encapsulates the information used to set and maintain the desired experiment and
|
|
16
|
+
"""Encapsulates the information used to set and maintain the desired experiment and system state.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
Broadly, each experiment runtime can be conceptualized as a two state-system. The first state is that of the
|
|
19
|
+
experimental task, which reflects the behavior goal, the rules for achieving the goal, and the reward for
|
|
20
|
+
achieving the goal. The second state is that of the data acquisition and experiment control system, which is a
|
|
21
|
+
snapshot of all hardware module states that make up the system that acquires the data and controls the task
|
|
22
|
+
environment. Overall, experiment state is about 'what the animal is doing', while the system state is about
|
|
23
|
+
'what the hardware is doing'.
|
|
24
|
+
|
|
25
|
+
Note:
|
|
26
|
+
This class is acquisition-system-agnostic. It can be used to define the ExperimentConfiguration class for any
|
|
27
|
+
valid data acquisition system.
|
|
21
28
|
"""
|
|
22
29
|
|
|
23
30
|
experiment_state_code: int
|
|
24
31
|
"""The integer code of the experiment state. Experiment states do not have a predefined meaning, Instead, each
|
|
25
32
|
project is expected to define and follow its own experiment state code mapping. Typically, the experiment state
|
|
26
33
|
code is used to denote major experiment stages, such as 'baseline', 'task', 'cooldown', etc. Note, the same
|
|
27
|
-
experiment state code can be used by multiple sequential ExperimentState instances to change the
|
|
34
|
+
experiment state code can be used by multiple sequential ExperimentState instances to change the system states
|
|
28
35
|
while maintaining the same experiment state."""
|
|
29
|
-
|
|
30
|
-
"""One of the supported
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
system_state_code: int
|
|
37
|
+
"""One of the supported system state-codes. Note, the meaning of each system state code depends on the specific
|
|
38
|
+
data acquisition and experiment control system used by the project. For example, projects using the 'mesoscope-vr'
|
|
39
|
+
system currently support two system state codes: REST (1) and RUN (2)."""
|
|
33
40
|
state_duration_s: float
|
|
34
|
-
"""The time, in seconds, to maintain the current combination of the experiment and
|
|
41
|
+
"""The time, in seconds, to maintain the current combination of the experiment and system states."""
|
|
35
42
|
|
|
36
43
|
|
|
44
|
+
# noinspection PyArgumentList
|
|
37
45
|
@dataclass()
|
|
38
|
-
class
|
|
39
|
-
"""Stores the configuration of a single experiment runtime.
|
|
46
|
+
class MesoscopeExperimentConfiguration(YamlConfig):
|
|
47
|
+
"""Stores the configuration of a single experiment runtime that uses the Mesoscope_VR data acquisition system.
|
|
40
48
|
|
|
41
|
-
Primarily, this includes the sequence of experiment and
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
Primarily, this includes the sequence of experiment and system states that defines the flow of the experiment
|
|
50
|
+
runtime. During runtime, the main runtime control function traverses the sequence of states stored in this class
|
|
51
|
+
instance start-to-end in the exact order specified by the user. Together with custom Unity projects that define
|
|
52
|
+
the task logic (how the system responds to animal interactions with the VR system) this class allows flexibly
|
|
53
|
+
implementing a wide range of experiments using the Mesoscope-VR system.
|
|
46
54
|
|
|
47
55
|
Each project should define one or more experiment configurations and save them as .yaml files inside the project
|
|
48
56
|
'configuration' folder. The name for each configuration file is defined by the user and is used to identify and load
|
|
49
|
-
the experiment configuration when 'sl-
|
|
57
|
+
the experiment configuration when 'sl-experiment' CLI command exposed by the sl-experiment library is executed.
|
|
58
|
+
|
|
59
|
+
Notes:
|
|
60
|
+
This class is designed exclusively for the Mesoscope-VR system. Any other system needs to define a separate
|
|
61
|
+
ExperimentConfiguration class to specify its experiment runtimes and additional data.
|
|
50
62
|
"""
|
|
51
63
|
|
|
52
64
|
cue_map: dict[int, float] = field(default_factory=lambda: {0: 30.0, 1: 30.0, 2: 30.0, 3: 30.0, 4: 30.0})
|
|
@@ -55,10 +67,379 @@ class ExperimentConfiguration(YamlConfig):
|
|
|
55
67
|
to travel to fully traverse the wall cue region from start to end."""
|
|
56
68
|
experiment_states: dict[str, ExperimentState] = field(
|
|
57
69
|
default_factory=lambda: {
|
|
58
|
-
"baseline": ExperimentState(experiment_state_code=1,
|
|
59
|
-
"experiment": ExperimentState(experiment_state_code=2,
|
|
60
|
-
"cooldown": ExperimentState(experiment_state_code=3,
|
|
70
|
+
"baseline": ExperimentState(experiment_state_code=1, system_state_code=1, state_duration_s=30),
|
|
71
|
+
"experiment": ExperimentState(experiment_state_code=2, system_state_code=2, state_duration_s=120),
|
|
72
|
+
"cooldown": ExperimentState(experiment_state_code=3, system_state_code=1, state_duration_s=15),
|
|
61
73
|
}
|
|
62
74
|
)
|
|
63
75
|
"""A dictionary that uses human-readable state-names as keys and ExperimentState instances as values. Each
|
|
64
76
|
ExperimentState instance represents a phase of the experiment."""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass()
|
|
80
|
+
class MesoscopePaths:
|
|
81
|
+
"""Stores the filesystem configuration parameters for the Mesoscope-VR data acquisition system."""
|
|
82
|
+
|
|
83
|
+
server_credentials_path: Path = Path("/media/Data/Experiments/server_credentials.yaml")
|
|
84
|
+
"""
|
|
85
|
+
The path to the locally stored .YAML file that contains the credentials for accessing the BioHPC server machine.
|
|
86
|
+
While the filesystem of the server machine should already be mounted to the local machine via SMB or equivalent
|
|
87
|
+
protocol, this data is used to establish SSH connection to the server and start newly acquired data processing
|
|
88
|
+
after it is transferred to the server. This allows data acquisition, preprocessing, and processing to be controlled
|
|
89
|
+
by the same runtime and prevents unprocessed data from piling up on the server.
|
|
90
|
+
"""
|
|
91
|
+
google_credentials_path: Path = Path("/media/Data/Experiments/sl-surgery-log-0f651e492767.json")
|
|
92
|
+
"""
|
|
93
|
+
The path to the locally stored .JSON file that contains the service account credentials used to read and write
|
|
94
|
+
Google Sheet data. This is used to access and work with various Google Sheet files used by Sun lab projects,
|
|
95
|
+
eliminating the need to manually synchronize the data in various Google sheets and other data files.
|
|
96
|
+
"""
|
|
97
|
+
root_directory: Path = Path("/media/Data/Experiments")
|
|
98
|
+
"""The absolute path to the directory where all projects are stored on the local host-machine (VRPC)."""
|
|
99
|
+
server_storage_directory: Path = Path("/home/cybermouse/server/storage/sun_data")
|
|
100
|
+
"""The absolute path to the directory where the raw data from all projects is stored on the BioHPC server.
|
|
101
|
+
This directory should be locally accessible (mounted) using a network sharing protocol, such as SMB."""
|
|
102
|
+
server_working_directory: Path = Path("/home/cybermouse/server/workdir/sun_data")
|
|
103
|
+
"""The absolute path to the directory where the processed data from all projects is stored on the BioHPC
|
|
104
|
+
server. This directory should be locally accessible (mounted) using a network sharing protocol, such as SMB."""
|
|
105
|
+
nas_directory: Path = Path("/home/cybermouse/nas/rawdata")
|
|
106
|
+
"""The absolute path to the directory where the raw data from all projects is stored on the Synology NAS.
|
|
107
|
+
This directory should be locally accessible (mounted) using a network sharing protocol, such as SMB."""
|
|
108
|
+
mesoscope_directory: Path = Path("/home/cybermouse/scanimage/mesodata")
|
|
109
|
+
"""The absolute path to the root ScanImagePC (mesoscope-connected PC) directory where all mesoscope-acquired data
|
|
110
|
+
is aggregated during acquisition runtime. This directory should be locally accessible (mounted) using a network
|
|
111
|
+
sharing protocol, such as SMB."""
|
|
112
|
+
harvesters_cti_path: Path = Path("/opt/mvIMPACT_Acquire/lib/x86_64/mvGenTLProducer.cti")
|
|
113
|
+
"""The path to the GeniCam CTI file used to connect to Harvesters-managed cameras."""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass()
|
|
117
|
+
class MesoscopeCameras:
|
|
118
|
+
"""Stores the configuration parameters for the cameras used by the Mesoscope-VR system to record behavior videos."""
|
|
119
|
+
|
|
120
|
+
face_camera_index: int = 0
|
|
121
|
+
"""The index of the face camera in the list of all available Harvester-managed cameras."""
|
|
122
|
+
left_camera_index: int = 0
|
|
123
|
+
"""The index of the left body camera (from animal's perspective) in the list of all available OpenCV-managed
|
|
124
|
+
cameras."""
|
|
125
|
+
right_camera_index: int = 2
|
|
126
|
+
"""The index of the right body camera (from animal's perspective) in the list of all available OpenCV-managed
|
|
127
|
+
cameras."""
|
|
128
|
+
quantization_parameter: int = 15
|
|
129
|
+
"""The quantization parameter used by all cameras to encode acquired frames as video files. This controls how much
|
|
130
|
+
data is discarded when encoding each video frame, directly contributing to the encoding speed, resultant video file
|
|
131
|
+
size and video quality."""
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@dataclass()
|
|
135
|
+
class MesoscopeMicroControllers:
|
|
136
|
+
"""Stores the configuration parameters for the microcontrollers used by the Mesoscope-VR system."""
|
|
137
|
+
|
|
138
|
+
actor_port: str = "/dev/ttyACM0"
|
|
139
|
+
"""The USB port used by the Actor Microcontroller."""
|
|
140
|
+
sensor_port: str = "/dev/ttyACM1"
|
|
141
|
+
"""The USB port used by the Sensor Microcontroller."""
|
|
142
|
+
encoder_port: str = "/dev/ttyACM2"
|
|
143
|
+
"""The USB port used by the Encoder Microcontroller."""
|
|
144
|
+
mesoscope_start_ttl_module_id: int = 1
|
|
145
|
+
"""The unique byte-code ID of the TTL module instance used to send mesoscope frame acquisition start trigger
|
|
146
|
+
signals to the ScanImagePC."""
|
|
147
|
+
mesoscope_stop_ttl_module_id: int = 2
|
|
148
|
+
"""The unique byte-code ID of the TTL module instance used to send mesoscope frame acquisition stop trigger
|
|
149
|
+
signals to the ScanImagePC."""
|
|
150
|
+
mesoscope_ttl_pulse_duration_ms: int = 10
|
|
151
|
+
"""The duration of the HIGH phase of all outgoing mesoscope TTL pulses, in milliseconds."""
|
|
152
|
+
minimum_break_strength_g_cm: float = 43.2047
|
|
153
|
+
"""The minimum torque applied by the running wheel break in gram centimeter. This is the torque the break delivers
|
|
154
|
+
at minimum voltage (break is disabled)."""
|
|
155
|
+
maximum_break_strength_g_cm: float = 1152.1246
|
|
156
|
+
"""The maximum torque applied by the running wheel break in gram centimeter. This is the torque the break delivers
|
|
157
|
+
at maximum voltage (break is fully engaged)."""
|
|
158
|
+
wheel_diameter_cm: float = 15.0333
|
|
159
|
+
"""The diameter of the running wheel connected to the break and torque sensor, in centimeters."""
|
|
160
|
+
lick_threshold_adc: int = 500
|
|
161
|
+
"""The threshold voltage, in raw analog units recorded by a 12-bit Analog-to-Digital-Converter (ADC), interpreted
|
|
162
|
+
as the animal's tongue contacting the sensor. Note, 12-bit ADC only supports values between 0 and 4095, so setting
|
|
163
|
+
the threshold above 4095 will result in no licks being reported to Unity."""
|
|
164
|
+
lick_signal_threshold_adc: int = 300
|
|
165
|
+
"""The minimum voltage, in raw analog units recorded by a 12-bit Analog-to-Digital-Converter (ADC), reported to the
|
|
166
|
+
PC as a non-zero value. Voltages below this level are interpreted as 'no-lick' noise and are always pulled to 0."""
|
|
167
|
+
lick_delta_threshold_adc: int = 300
|
|
168
|
+
"""The minimum absolute difference in raw analog units recorded by a 12-bit Analog-to-Digital-Converter (ADC) for
|
|
169
|
+
the change to be reported to the PC. This is used to prevent reporting repeated non-lick or lick readouts to the
|
|
170
|
+
PC, conserving communication bandwidth."""
|
|
171
|
+
lick_averaging_pool_size: int = 10
|
|
172
|
+
"""The number of lick sensor readouts to average together to produce the final lick sensor readout value."""
|
|
173
|
+
torque_baseline_voltage_adc: int = 2046
|
|
174
|
+
"""The voltage level, in raw analog units measured by 3.3v Analog-to-Digital-Converter (ADC) at 12-bit resolution
|
|
175
|
+
after the AD620 amplifier, that corresponds to no (0) torque readout. Usually, for a 3.3v ADC, this would be
|
|
176
|
+
around 2046 (the midpoint, ~1.65 V)."""
|
|
177
|
+
torque_maximum_voltage_adc: int = 2750
|
|
178
|
+
"""The voltage level, in raw analog units measured by 3.3v Analog-to-Digital-Converter (ADC) at 12-bit resolution
|
|
179
|
+
after the AD620 amplifier, that corresponds to the absolute maximum torque detectable by the sensor. At most,
|
|
180
|
+
this value can be 4095 (~3.3 V)."""
|
|
181
|
+
torque_sensor_capacity_g_cm: float = 720.0779
|
|
182
|
+
"""The maximum torque detectable by the sensor, in grams centimeter (g cm)."""
|
|
183
|
+
torque_report_cw: bool = True
|
|
184
|
+
"""Determines whether the sensor should report torque in the Clockwise (CW) direction. This direction corresponds
|
|
185
|
+
to the animal trying to move the wheel backward."""
|
|
186
|
+
torque_report_ccw: bool = True
|
|
187
|
+
"""Determines whether the sensor should report torque in the Counter-Clockwise (CCW) direction. This direction
|
|
188
|
+
corresponds to the animal trying to move the wheel forward."""
|
|
189
|
+
torque_signal_threshold_adc: int = 300
|
|
190
|
+
"""The minimum voltage, in raw analog units recorded by a 12-bit Analog-to-Digital-Converter (ADC), reported to the
|
|
191
|
+
PC as a non-zero value. Voltages below this level are interpreted as noise and are always pulled to 0."""
|
|
192
|
+
torque_delta_threshold_adc: int = 300
|
|
193
|
+
"""The minimum absolute difference in raw analog units recorded by a 12-bit Analog-to-Digital-Converter (ADC) for
|
|
194
|
+
the change to be reported to the PC. This is used to prevent reporting repeated static torque readouts to the
|
|
195
|
+
PC, conserving communication bandwidth."""
|
|
196
|
+
torque_averaging_pool_size: int = 10
|
|
197
|
+
"""The number of torque sensor readouts to average together to produce the final torque sensor readout value."""
|
|
198
|
+
wheel_encoder_ppr = 8192
|
|
199
|
+
"""The resolution of the managed quadrature encoder, in Pulses Per Revolution (PPR). This is the number of
|
|
200
|
+
quadrature pulses the encoder emits per full 360-degree rotation."""
|
|
201
|
+
wheel_encoder_report_cw: bool = False
|
|
202
|
+
"""Determines whether to report encoder rotation in the CW (negative) direction. This corresponds to the animal
|
|
203
|
+
moving backward on the wheel."""
|
|
204
|
+
wheel_encoder_report_ccw: bool = True
|
|
205
|
+
"""Determines whether to report encoder rotation in the CCW (positive) direction. This corresponds to the animal
|
|
206
|
+
moving forward on the wheel."""
|
|
207
|
+
wheel_encoder_delta_threshold_pulse: int = 15
|
|
208
|
+
"""The minimum difference, in encoder pulse counts, between two encoder readouts for the change to be reported to
|
|
209
|
+
the PC. This is used to prevent reporting idle readouts and filter out sub-threshold noise."""
|
|
210
|
+
wheel_encoder_polling_delay_us = 500
|
|
211
|
+
"""The delay, in microseconds, between any two successive encoder state readouts."""
|
|
212
|
+
cm_per_unity_unit = 10.0
|
|
213
|
+
"""The length of each Unity 'unit' in real-world centimeters recorded by the running wheel encoder."""
|
|
214
|
+
screen_trigger_pulse_duration_ms: int = 500
|
|
215
|
+
"""The duration of the HIGH phase of the TTL pulse used to toggle the VR screens between ON and OFF states."""
|
|
216
|
+
auditory_tone_duration_ms: int = 300
|
|
217
|
+
"""The time, in milliseconds, to sound the auditory tone when water rewards are delivered to the animal."""
|
|
218
|
+
valve_calibration_pulse_count: int = 200
|
|
219
|
+
"""The number of times to cycle opening and closing (pulsing) the valve during each calibration runtime. This
|
|
220
|
+
determines how many reward deliveries are used at each calibrated time-interval to produce the average dispensed
|
|
221
|
+
water volume readout used to calibrate the valve."""
|
|
222
|
+
sensor_polling_delay_ms: int = 1
|
|
223
|
+
"""The delay, in milliseconds, between any two successive readouts of any sensor other than the encoder. Note, the
|
|
224
|
+
encoder uses a dedicated parameter, as the encoder needs to be sampled at a higher frequency than all other sensors.
|
|
225
|
+
"""
|
|
226
|
+
valve_calibration_data: dict[int | float, int | float] | tuple[tuple[int | float, int | float], ...] = (
|
|
227
|
+
(15000, 1.75),
|
|
228
|
+
(30000, 3.85),
|
|
229
|
+
(45000, 7.95),
|
|
230
|
+
(60000, 12.65),
|
|
231
|
+
)
|
|
232
|
+
"""A tuple of tuples that maps water delivery solenoid valve open times, in microseconds, to the dispensed volume
|
|
233
|
+
of water, in microliters. During training and experiment runtimes, this data is used by the ValveModule to translate
|
|
234
|
+
the requested reward volumes into times the valve needs to be open to deliver the desired volume of water.
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@dataclass()
|
|
239
|
+
class MesoscopeAdditionalFirmware:
|
|
240
|
+
"""Stores the configuration parameters for all firmware and hardware components not assembled in the Sun lab."""
|
|
241
|
+
|
|
242
|
+
headbar_port: str = "/dev/ttyUSB0"
|
|
243
|
+
"""The USB port used by the HeadBar Zaber motor controllers (devices). Note, this motor group also includes the
|
|
244
|
+
running wheel x-axis motor."""
|
|
245
|
+
lickport_port: str = "/dev/ttyUSB1"
|
|
246
|
+
"""The USB port used by the LickPort Zaber motor controllers (devices)."""
|
|
247
|
+
unity_ip: str = "127.0.0.1"
|
|
248
|
+
"""The IP address of the MQTT broker used to communicate with the Unity game engine."""
|
|
249
|
+
unity_port: int = 1883
|
|
250
|
+
"""The port number of the MQTT broker used to communicate with the Unity game engine."""
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@dataclass()
|
|
254
|
+
class MesoscopeSystemConfiguration(YamlConfig):
|
|
255
|
+
"""Stores the hardware and filesystem configuration parameters for the Mesoscope-VR data acquisition system used in
|
|
256
|
+
the Sun lab.
|
|
257
|
+
|
|
258
|
+
This class is specifically designed to encapsulate the configuration parameters for the Mesoscope-VR system. It
|
|
259
|
+
expects the system to be configured according to the specifications available from the sl_experiment repository
|
|
260
|
+
(https://github.com/Sun-Lab-NBB/sl-experiment) and should be used exclusively by the VRPC machine
|
|
261
|
+
(main Mesoscope-VR PC).
|
|
262
|
+
|
|
263
|
+
Notes:
|
|
264
|
+
Each SystemConfiguration class is uniquely tied to a specific hardware configuration used in the lab. This
|
|
265
|
+
class will only work with the Mesoscope-VR system. Any other data acquisition and runtime management system in
|
|
266
|
+
the lab should define its own SystemConfiguration class to specify its own hardware and filesystem configuration
|
|
267
|
+
parameters.
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
name: str = "mesoscope-vr"
|
|
271
|
+
"""Stores the descriptive name of the data acquisition system."""
|
|
272
|
+
paths: MesoscopePaths = field(default_factory=MesoscopePaths)
|
|
273
|
+
"""Stores the filesystem configuration parameters for the Mesoscope-VR data acquisition system."""
|
|
274
|
+
cameras: MesoscopeCameras = field(default_factory=MesoscopeCameras)
|
|
275
|
+
"""Stores the configuration parameters for the cameras used by the Mesoscope-VR system to record behavior videos."""
|
|
276
|
+
microcontrollers: MesoscopeMicroControllers = field(default_factory=MesoscopeMicroControllers)
|
|
277
|
+
"""Stores the configuration parameters for the microcontrollers used by the Mesoscope-VR system."""
|
|
278
|
+
additional_firmware: MesoscopeAdditionalFirmware = field(default_factory=MesoscopeAdditionalFirmware)
|
|
279
|
+
"""Stores the configuration parameters for all firmware and hardware components not assembled in the Sun lab."""
|
|
280
|
+
|
|
281
|
+
def __post_init__(self) -> None:
|
|
282
|
+
"""Ensures that variables converted to different types for storage purposes are always set to expected types
|
|
283
|
+
upon class instantiation."""
|
|
284
|
+
|
|
285
|
+
# Converts all paths loaded as strings to Path objects used inside the library
|
|
286
|
+
self.paths.server_credentials_path = Path(self.paths.server_credentials_path)
|
|
287
|
+
self.paths.google_credentials_path = Path(self.paths.google_credentials_path)
|
|
288
|
+
self.paths.root_directory = Path(self.paths.root_directory)
|
|
289
|
+
self.paths.server_storage_directory = Path(self.paths.server_storage_directory)
|
|
290
|
+
self.paths.server_working_directory = Path(self.paths.server_working_directory)
|
|
291
|
+
self.paths.nas_directory = Path(self.paths.nas_directory)
|
|
292
|
+
self.paths.mesoscope_directory = Path(self.paths.mesoscope_directory)
|
|
293
|
+
self.paths.harvesters_cti_path = Path(self.paths.harvesters_cti_path)
|
|
294
|
+
|
|
295
|
+
# Converts valve_calibration data from dictionary to a tuple of tuples format
|
|
296
|
+
if not isinstance(self.microcontrollers.valve_calibration_data, tuple):
|
|
297
|
+
self.microcontrollers.valve_calibration_data = tuple(
|
|
298
|
+
(k, v) for k, v in self.microcontrollers.valve_calibration_data.items()
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def save(self, path: Path) -> None:
|
|
302
|
+
"""Saves class instance data to disk as a 'mesoscope_system_configuration.yaml' file.
|
|
303
|
+
|
|
304
|
+
This method converts certain class variables to yaml-safe types (for example, Path objects -> strings) and
|
|
305
|
+
saves class data to disk as a .yaml file. The method is intended to be used solely by the
|
|
306
|
+
set_system_configuration_file() function and should not be called from any other context.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
path: The path to the .yaml file to save the data to.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
# Copies instance data to prevent it from being modified by reference when executing the steps below
|
|
313
|
+
original = copy.deepcopy(self)
|
|
314
|
+
|
|
315
|
+
# Converts all Path objects to strings before dumping the data, as .yaml encoder does not properly recognize
|
|
316
|
+
# Path objects
|
|
317
|
+
original.paths.server_credentials_path = str(original.paths.server_credentials_path) # type: ignore
|
|
318
|
+
original.paths.google_credentials_path = str(original.paths.google_credentials_path) # type: ignore
|
|
319
|
+
original.paths.root_directory = str(original.paths.root_directory) # type: ignore
|
|
320
|
+
original.paths.server_storage_directory = str(original.paths.server_storage_directory) # type: ignore
|
|
321
|
+
original.paths.server_working_directory = str(original.paths.server_working_directory) # type: ignore
|
|
322
|
+
original.paths.nas_directory = str(original.paths.nas_directory) # type: ignore
|
|
323
|
+
original.paths.mesoscope_directory = str(original.paths.mesoscope_directory) # type: ignore
|
|
324
|
+
original.paths.harvesters_cti_path = str(original.paths.harvesters_cti_path) # type: ignore
|
|
325
|
+
|
|
326
|
+
# Converts valve calibration data into dictionary format
|
|
327
|
+
if isinstance(original.microcontrollers.valve_calibration_data, tuple):
|
|
328
|
+
original.microcontrollers.valve_calibration_data = {
|
|
329
|
+
k: v for k, v in original.microcontrollers.valve_calibration_data
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
# Saves the data to the YAML file
|
|
333
|
+
original.to_yaml(file_path=path)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
# A dictionary that maps the file names for supported data acquisition systems to their configuration classes. This
|
|
337
|
+
# dictionary always contains all data acquisition systems used in the lab.
|
|
338
|
+
_supported_configuration_files = {"mesoscope_system_configuration.yaml": MesoscopeSystemConfiguration}
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def set_system_configuration_file(path: Path) -> None:
|
|
342
|
+
"""Sets the system configuration .yaml file specified by the input path as the default system configuration file for
|
|
343
|
+
the managed machine (PC).
|
|
344
|
+
|
|
345
|
+
This function is used to initially configure or override the existing configuration of any data acquisition system
|
|
346
|
+
used in the lab. The path to the configuration file is stored inside the user's data directory, so that all
|
|
347
|
+
Sun lab libraries can automatically access that information during every runtime. Since the storage directory is
|
|
348
|
+
typically hidden and varies between OSes and machines, this function provides a convenient way for setting that
|
|
349
|
+
path without manually editing the storage cache.
|
|
350
|
+
|
|
351
|
+
Notes:
|
|
352
|
+
If the input path does not point to an existing file, but the file name and extension are correct, the function
|
|
353
|
+
will automatically generate a default SystemConfiguration class instance and save it under the specified path.
|
|
354
|
+
|
|
355
|
+
A data acquisition system can include multiple machines (PCs). However, the configuration file is typically
|
|
356
|
+
only present on the 'main' machine that manages all runtimes.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
path: The path to the new system configuration file to be used by the local data acquisition system (PC).
|
|
360
|
+
|
|
361
|
+
Raises:
|
|
362
|
+
ValueError: If the input path is not a valid system configuration file or does not use a supported data
|
|
363
|
+
acquisition system name.
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
# Prevents setting the path to an invalid file.
|
|
367
|
+
if path.name not in _supported_configuration_files.keys():
|
|
368
|
+
message = (
|
|
369
|
+
f"Unable to set the input path {path} as the default system configuration file path. The input path has "
|
|
370
|
+
f"to point to a configuration file ending with a '.yaml' extension and using one of the supported system "
|
|
371
|
+
f"names: {', '.join(_supported_configuration_files.keys())}."
|
|
372
|
+
)
|
|
373
|
+
console.error(message=message, error=ValueError)
|
|
374
|
+
|
|
375
|
+
# If the configuration file specified by the 'path' does not exist, generates a default SystemConfiguration instance
|
|
376
|
+
# and saves it to the specified path.
|
|
377
|
+
if not path.exists():
|
|
378
|
+
precursor = _supported_configuration_files[path.name]() # Instantiates default class instance
|
|
379
|
+
precursor.save(path=path)
|
|
380
|
+
message = (
|
|
381
|
+
f"The file specified by the input system configuration path {path} does not exist. Generating and saving "
|
|
382
|
+
f"the default system configuration class instance to the specified path."
|
|
383
|
+
)
|
|
384
|
+
console.echo(message=message, level=LogLevel.WARNING)
|
|
385
|
+
|
|
386
|
+
# Resolves the path to the static .txt file used to store the path to the system configuration file
|
|
387
|
+
app_dir = Path(appdirs.user_data_dir(appname="sun_lab_data", appauthor="sun_lab"))
|
|
388
|
+
path_file = app_dir.joinpath("configuration_path.txt")
|
|
389
|
+
|
|
390
|
+
# In case this function is called before the app directory is created, ensures the app directory exists
|
|
391
|
+
ensure_directory_exists(path_file)
|
|
392
|
+
|
|
393
|
+
# Ensures that the input path's directory exists
|
|
394
|
+
ensure_directory_exists(path)
|
|
395
|
+
|
|
396
|
+
# Replaces the contents of the configuration_path.txt file with the provided path
|
|
397
|
+
with open(path_file, "w") as f:
|
|
398
|
+
f.write(str(path))
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def get_system_configuration_data() -> MesoscopeSystemConfiguration:
|
|
402
|
+
"""Resolves the path to the local system configuration file and loads the system configuration data.
|
|
403
|
+
|
|
404
|
+
This service function is used by all Sun lab data acquisition runtimes to load the system configuration data from
|
|
405
|
+
the shared configuration file. It supports resolving and returning the data for all data acquisition systems used
|
|
406
|
+
in the lab.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
The initialized SystemConfiguration class instance for the local acquisition system that stores the loaded
|
|
410
|
+
configuration parameters.
|
|
411
|
+
|
|
412
|
+
Raises:
|
|
413
|
+
FileNotFoundError: If the local machine does not have the Sun lab data directory, or the system configuration
|
|
414
|
+
file does not exist.
|
|
415
|
+
"""
|
|
416
|
+
# Uses appdirs to locate the user data directory and resolve the path to the configuration file
|
|
417
|
+
app_dir = Path(appdirs.user_data_dir(appname="sun_lab_data", appauthor="sun_lab"))
|
|
418
|
+
path_file = app_dir.joinpath("configuration_path.txt")
|
|
419
|
+
|
|
420
|
+
# If the cache file or the Sun lab data directory do not exist, aborts with an error
|
|
421
|
+
if not path_file.exists():
|
|
422
|
+
message = (
|
|
423
|
+
"Unable to resolve the path to the local system configuration file, as local machine does not have the "
|
|
424
|
+
"Sun lab data directory. Generate the local configuration file and Sun lab data directory by calling the "
|
|
425
|
+
"'sl-config' CLI command and rerun the command that produced this error."
|
|
426
|
+
)
|
|
427
|
+
console.error(message=message, error=FileNotFoundError)
|
|
428
|
+
|
|
429
|
+
# Once the location of the path storage file is resolved, reads the file path from the file
|
|
430
|
+
with open(path_file, "r") as f:
|
|
431
|
+
configuration_file = Path(f.read().strip())
|
|
432
|
+
|
|
433
|
+
# If the configuration file does not exist, also aborts with an error
|
|
434
|
+
if not configuration_file.exists():
|
|
435
|
+
message = (
|
|
436
|
+
"Unable to resolve the path to the local system configuration file, as the file pointed by the path stored "
|
|
437
|
+
"in Sun lab data directory does not exist. Generate a new local configuration file by calling the "
|
|
438
|
+
"'sl-config' CLI command and rerun the command that produced this error."
|
|
439
|
+
)
|
|
440
|
+
console.error(message=message, error=FileNotFoundError)
|
|
441
|
+
|
|
442
|
+
# Loads the data stored inside the .yaml file into the class instance that matches the file name and returns the
|
|
443
|
+
# instantiated class to caller
|
|
444
|
+
file_name = configuration_file.name
|
|
445
|
+
return _supported_configuration_files[file_name].from_yaml(file_path=configuration_file) # type: ignore
|