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.

Files changed (35) hide show
  1. sl_shared_assets/__init__.py +27 -27
  2. sl_shared_assets/__init__.pyi +24 -22
  3. sl_shared_assets/cli.py +266 -40
  4. sl_shared_assets/cli.pyi +73 -14
  5. sl_shared_assets/data_classes/__init__.py +23 -20
  6. sl_shared_assets/data_classes/__init__.pyi +18 -18
  7. sl_shared_assets/data_classes/configuration_data.py +407 -26
  8. sl_shared_assets/data_classes/configuration_data.pyi +172 -15
  9. sl_shared_assets/data_classes/runtime_data.py +49 -43
  10. sl_shared_assets/data_classes/runtime_data.pyi +37 -40
  11. sl_shared_assets/data_classes/session_data.py +168 -914
  12. sl_shared_assets/data_classes/session_data.pyi +55 -350
  13. sl_shared_assets/data_classes/surgery_data.py +3 -3
  14. sl_shared_assets/data_classes/surgery_data.pyi +2 -2
  15. sl_shared_assets/tools/__init__.py +8 -1
  16. sl_shared_assets/tools/__init__.pyi +11 -1
  17. sl_shared_assets/tools/ascension_tools.py +27 -26
  18. sl_shared_assets/tools/ascension_tools.pyi +5 -5
  19. sl_shared_assets/tools/packaging_tools.py +14 -1
  20. sl_shared_assets/tools/packaging_tools.pyi +4 -0
  21. sl_shared_assets/tools/project_management_tools.py +164 -0
  22. sl_shared_assets/tools/project_management_tools.pyi +48 -0
  23. {sl_shared_assets-1.0.0rc20.dist-info → sl_shared_assets-1.0.0rc21.dist-info}/METADATA +21 -4
  24. sl_shared_assets-1.0.0rc21.dist-info/RECORD +36 -0
  25. sl_shared_assets-1.0.0rc21.dist-info/entry_points.txt +8 -0
  26. sl_shared_assets/suite2p/__init__.py +0 -8
  27. sl_shared_assets/suite2p/__init__.pyi +0 -4
  28. sl_shared_assets/suite2p/multi_day.py +0 -224
  29. sl_shared_assets/suite2p/multi_day.pyi +0 -104
  30. sl_shared_assets/suite2p/single_day.py +0 -564
  31. sl_shared_assets/suite2p/single_day.pyi +0 -220
  32. sl_shared_assets-1.0.0rc20.dist-info/RECORD +0 -40
  33. sl_shared_assets-1.0.0rc20.dist-info/entry_points.txt +0 -4
  34. {sl_shared_assets-1.0.0rc20.dist-info → sl_shared_assets-1.0.0rc21.dist-info}/WHEEL +0 -0
  35. {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
- Classes from this library are saved as .yaml files to be edited by the user when a new project and / or session
3
- is created by the sl-experiment library. The runtime settings are then loaded from user-edited .yaml files by various
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 Mesoscope-VR system state.
16
+ """Encapsulates the information used to set and maintain the desired experiment and system state.
16
17
 
17
- Primarily, experiment runtime logic (task logic) is resolved by the Unity game engine. However, the Mesoscope-VR
18
- system configuration may also need to change throughout the experiment to optimize the runtime by disabling or
19
- reconfiguring specific hardware modules. For example, some experiment stages may require the running wheel to be
20
- locked to prevent the animal from running, and other may require the VR screens to be turned off.
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 VR system states
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
- vr_state_code: int
30
- """One of the supported VR system state-codes. Currently, the Mesoscope-VR system supports two state codes. State
31
- code '1' denotes 'REST' state and code '2' denotes 'RUN' state. Note, multiple consecutive ExperimentState
32
- instances with different experiment state codes can reuse the same VR state code."""
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 VR states."""
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 ExperimentConfiguration(YamlConfig):
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 Virtual Reality (Mesoscope-VR) states that defines the flow
42
- of the experiment runtime. During runtime, the main runtime control function traverses the sequence of states
43
- stored in this class instance start-to-end in the exact order specified by the user. Together with custom Unity
44
- projects that define the task logic (how the system responds to animal interactions with the VR system) this class
45
- allows flexibly implementing a wide range of experiments.
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-run-experiment' CLI command exposed by the sl-experiment library is executed.
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, vr_state_code=1, state_duration_s=30),
59
- "experiment": ExperimentState(experiment_state_code=2, vr_state_code=2, state_duration_s=120),
60
- "cooldown": ExperimentState(experiment_state_code=3, vr_state_code=1, state_duration_s=15),
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