sl-shared-assets 3.0.0rc1__tar.gz → 3.0.0rc3__tar.gz

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 (45) hide show
  1. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/PKG-INFO +1 -1
  2. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/pyproject.toml +1 -1
  3. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/data_classes/configuration_data.py +19 -1
  4. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/data_classes/configuration_data.pyi +5 -0
  5. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/data_classes/runtime_data.py +17 -2
  6. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/data_classes/runtime_data.pyi +6 -2
  7. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/server/server.py +66 -0
  8. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/server/server.pyi +14 -0
  9. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/.gitignore +0 -0
  10. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/LICENSE +0 -0
  11. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/README.md +0 -0
  12. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/docs/Makefile +0 -0
  13. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/docs/make.bat +0 -0
  14. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/docs/source/api.rst +0 -0
  15. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/docs/source/conf.py +0 -0
  16. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/docs/source/index.rst +0 -0
  17. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/docs/source/welcome.rst +0 -0
  18. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/envs/slsa_dev_lin.yml +0 -0
  19. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/envs/slsa_dev_lin_spec.txt +0 -0
  20. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/__init__.py +0 -0
  21. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/__init__.pyi +0 -0
  22. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/cli.py +0 -0
  23. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/cli.pyi +0 -0
  24. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/data_classes/__init__.py +0 -0
  25. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/data_classes/__init__.pyi +0 -0
  26. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/data_classes/session_data.py +0 -0
  27. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/data_classes/session_data.pyi +0 -0
  28. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/data_classes/surgery_data.py +0 -0
  29. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/data_classes/surgery_data.pyi +0 -0
  30. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/py.typed +0 -0
  31. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/server/__init__.py +0 -0
  32. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/server/__init__.pyi +0 -0
  33. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/server/job.py +0 -0
  34. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/server/job.pyi +0 -0
  35. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/tools/__init__.py +0 -0
  36. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/tools/__init__.pyi +0 -0
  37. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/tools/ascension_tools.py +0 -0
  38. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/tools/ascension_tools.pyi +0 -0
  39. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/tools/packaging_tools.py +0 -0
  40. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/tools/packaging_tools.pyi +0 -0
  41. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/tools/project_management_tools.py +0 -0
  42. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/tools/project_management_tools.pyi +0 -0
  43. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/tools/transfer_tools.py +0 -0
  44. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/src/sl_shared_assets/tools/transfer_tools.pyi +0 -0
  45. {sl_shared_assets-3.0.0rc1 → sl_shared_assets-3.0.0rc3}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sl-shared-assets
3
- Version: 3.0.0rc1
3
+ Version: 3.0.0rc3
4
4
  Summary: Provides data acquisition and processing assets shared between Sun (NeuroAI) lab libraries.
5
5
  Project-URL: Homepage, https://github.com/Sun-Lab-NBB/sl-shared-assets
6
6
  Project-URL: Documentation, https://sl-shared-assets-api-docs.netlify.app/
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
8
8
  # Project metdata section. Provides the genral ID information about the project.
9
9
  [project]
10
10
  name = "sl-shared-assets"
11
- version = "3.0.0rc1"
11
+ version = "3.0.0rc3"
12
12
  description = "Provides data acquisition and processing assets shared between Sun (NeuroAI) lab libraries."
13
13
  readme = "README.md"
14
14
  license = { file = "LICENSE" }
@@ -39,6 +39,20 @@ class ExperimentState:
39
39
  system currently support two system state codes: REST (1) and RUN (2)."""
40
40
  state_duration_s: float
41
41
  """The time, in seconds, to maintain the current combination of the experiment and system states."""
42
+ initial_guided_trials: int = 3
43
+ """Specifies the number of trials (laps) at the onset of the experiment state, for which lick guidance will be
44
+ automatically enabled. Specifically, if the experiment state supports running linearized Virtual Reality track, the
45
+ system will enable lick guidance for this many trials at the beginning of the experiment state and automatically
46
+ disable it for the following trials."""
47
+ failed_trial_threshold: int = 6
48
+ """Specifies the number of failed (non-rewarded) non-guided trials (laps), after which the system will re-enable
49
+ guidance for the 'recovery_guided_trials' number of following trials. For this to take effect, the trials must be
50
+ failed this many times in a row."""
51
+ recovery_guided_trials: int = 3
52
+ """Specifies the number of trials (laps) for which the system will re-enable lick guidance, when the animal
53
+ repeatedly fails 'failed_trial_threshold' number of trials. This field works similar to the 'initial_guided_trials'
54
+ field, but is triggered by repeated performance failures, rather than experiment state onset. After the animal
55
+ runs this many guided trials, the system will automatically disable guidance for the following trials."""
42
56
 
43
57
 
44
58
  @dataclass()
@@ -54,6 +68,10 @@ class TrialCueSequence:
54
68
 
55
69
  cue_sequence: tuple[int, ...]
56
70
  """Specifies the sequence of wall cues experienced by the animal while running this trial."""
71
+ trial_length_unity_unit: float = 24.0
72
+ """The length of the trial cue sequence, in Unity units."""
73
+ trial_length_cm: float = 240.0
74
+ """The length of the trial cue sequence in centimeters."""
57
75
 
58
76
 
59
77
  # noinspection PyArgumentList
@@ -90,7 +108,7 @@ class MesoscopeExperimentConfiguration(YamlConfig):
90
108
  """A dictionary that uses human-readable state-names as keys and ExperimentState instances as values. Each
91
109
  ExperimentState instance represents a phase of the experiment."""
92
110
  trial_structures: dict[str, TrialCueSequence] = field(
93
- default_factory=lambda: {"circular_4cue": TrialCueSequence(cue_sequence=(0, 1, 0, 2, 0, 3, 0, 4))}
111
+ default_factory=lambda: {"circular 4 cue": TrialCueSequence(cue_sequence=(0, 1, 0, 2, 0, 3, 0, 4))}
94
112
  )
95
113
  """A dictionary that maps human-readable trial structure names as keys and TrialCueSequence instances as values.
96
114
  Each TrialCueSequence instance represents a specific VR wall cue sequence used by a given trial structure."""
@@ -23,6 +23,9 @@ class ExperimentState:
23
23
  experiment_state_code: int
24
24
  system_state_code: int
25
25
  state_duration_s: float
26
+ initial_guided_trials: int = ...
27
+ failed_trial_threshold: int = ...
28
+ recovery_guided_trials: int = ...
26
29
 
27
30
  @dataclass()
28
31
  class TrialCueSequence:
@@ -36,6 +39,8 @@ class TrialCueSequence:
36
39
  """
37
40
 
38
41
  cue_sequence: tuple[int, ...]
42
+ trial_length_unity_unit: float = ...
43
+ trial_length_cm: float = ...
39
44
 
40
45
  @dataclass()
41
46
  class MesoscopeExperimentConfiguration(YamlConfig):
@@ -4,7 +4,7 @@ restore the data acquisition and runtime management system to the same state acr
4
4
  the same animal.
5
5
  """
6
6
 
7
- from dataclasses import dataclass
7
+ from dataclasses import field, dataclass
8
8
 
9
9
  from ataraxis_data_structures import YamlConfig
10
10
 
@@ -88,7 +88,7 @@ class LickTrainingDescriptor(YamlConfig):
88
88
  """The weight of the animal, in grams, at the beginning of the session."""
89
89
  dispensed_water_volume_ml: float
90
90
  """Stores the total water volume, in milliliters, dispensed during runtime."""
91
- minimum_reward_delay: int
91
+ minimum_reward_delay_s: int
92
92
  """Stores the minimum delay, in seconds, that can separate the delivery of two consecutive water rewards."""
93
93
  maximum_reward_delay_s: int
94
94
  """Stores the maximum delay, in seconds, that can separate the delivery of two consecutive water rewards."""
@@ -100,6 +100,11 @@ class LickTrainingDescriptor(YamlConfig):
100
100
  """Stores the maximum number of consecutive rewards that can be delivered without the animal consuming them. If
101
101
  the animal receives this many rewards without licking (consuming) them, reward delivery is paused until the animal
102
102
  consumes the rewards."""
103
+ system_state_codes: dict[str, int] = field(
104
+ default_factory=lambda: {"idle": 0, "rest": 1, "run": 2, "lick training": 3, "run training": 4}
105
+ )
106
+ """Maps integer state-codes used by the acquisition system to communicate its states to human-readable state
107
+ names."""
103
108
  experimenter_notes: str = "Replace this with your notes."
104
109
  """This field is not set during runtime. It is expected that each experimenter replaces this field with their
105
110
  notes made during runtime."""
@@ -150,6 +155,11 @@ class RunTrainingDescriptor(YamlConfig):
150
155
  """Stores the maximum time, in seconds, the animal can dip below the running speed threshold to still receive the
151
156
  reward. This allows animals that 'run' by taking a series of large steps, briefly dipping below speed threshold at
152
157
  the end of each step, to still get water rewards."""
158
+ system_state_codes: dict[str, int] = field(
159
+ default_factory=lambda: {"idle": 0, "rest": 1, "run": 2, "lick training": 3, "run training": 4}
160
+ )
161
+ """Maps integer state-codes used by the acquisition system to communicate its states to human-readable state
162
+ names."""
153
163
  experimenter_notes: str = "Replace this with your notes."
154
164
  """This field is not set during runtime. It is expected that each experimenter will replace this field with their
155
165
  notes made during runtime."""
@@ -175,6 +185,11 @@ class MesoscopeExperimentDescriptor(YamlConfig):
175
185
  """Stores the maximum number of consecutive rewards that can be delivered without the animal consuming them. If
176
186
  the animal receives this many rewards without licking (consuming) them, reward delivery is paused until the animal
177
187
  consumes the rewards."""
188
+ system_state_codes: dict[str, int] = field(
189
+ default_factory=lambda: {"idle": 0, "rest": 1, "run": 2, "lick training": 3, "run training": 4}
190
+ )
191
+ """Maps integer state-codes used by the acquisition system to communicate its states to human-readable state
192
+ names."""
178
193
  experimenter_notes: str = "Replace this with your notes."
179
194
  """This field is not set during runtime. It is expected that each experimenter will replace this field with their
180
195
  notes made during runtime."""
@@ -1,5 +1,6 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import field, dataclass
2
2
 
3
+ from _typeshed import Incomplete
3
4
  from ataraxis_data_structures import YamlConfig
4
5
 
5
6
  @dataclass()
@@ -50,11 +51,12 @@ class LickTrainingDescriptor(YamlConfig):
50
51
  experimenter: str
51
52
  mouse_weight_g: float
52
53
  dispensed_water_volume_ml: float
53
- minimum_reward_delay: int
54
+ minimum_reward_delay_s: int
54
55
  maximum_reward_delay_s: int
55
56
  maximum_water_volume_ml: float
56
57
  maximum_training_time_m: int
57
58
  maximum_unconsumed_rewards: int = ...
59
+ system_state_codes: dict[str, int] = field(default_factory=Incomplete)
58
60
  experimenter_notes: str = ...
59
61
  experimenter_given_water_volume_ml: float = ...
60
62
  incomplete: bool = ...
@@ -77,6 +79,7 @@ class RunTrainingDescriptor(YamlConfig):
77
79
  maximum_training_time_m: int
78
80
  maximum_unconsumed_rewards: int = ...
79
81
  maximum_idle_time_s: float = ...
82
+ system_state_codes: dict[str, int] = field(default_factory=Incomplete)
80
83
  experimenter_notes: str = ...
81
84
  experimenter_given_water_volume_ml: float = ...
82
85
  incomplete: bool = ...
@@ -89,6 +92,7 @@ class MesoscopeExperimentDescriptor(YamlConfig):
89
92
  mouse_weight_g: float
90
93
  dispensed_water_volume_ml: float
91
94
  maximum_unconsumed_rewards: int = ...
95
+ system_state_codes: dict[str, int] = field(default_factory=Incomplete)
92
96
  experimenter_notes: str = ...
93
97
  experimenter_given_water_volume_ml: float = ...
94
98
  incomplete: bool = ...
@@ -4,6 +4,7 @@ the running job status. All lab processing and analysis pipelines use this inter
4
4
  resources.
5
5
  """
6
6
 
7
+ import stat
7
8
  from random import randint
8
9
  from pathlib import Path
9
10
  import tempfile
@@ -486,6 +487,71 @@ class Server:
486
487
  finally:
487
488
  sftp.close()
488
489
 
490
+ def pull_directory(self, local_directory_path: Path, remote_directory_path: Path) -> None:
491
+ """Recursively downloads the entire target directory from the remote server to the local machine.
492
+
493
+ Args:
494
+ local_directory_path: The path to the local directory where the remote directory will be copied.
495
+ remote_directory_path: The path to the directory on the remote server to be downloaded.
496
+ """
497
+ sftp = self._client.open_sftp()
498
+
499
+ try:
500
+ # Creates the local directory if it doesn't exist
501
+ local_directory_path.mkdir(parents=True, exist_ok=True)
502
+
503
+ # Gets the list of items in the remote directory
504
+ remote_items = sftp.listdir_attr(str(remote_directory_path))
505
+
506
+ for item in remote_items:
507
+ remote_item_path = remote_directory_path.joinpath(item.filename)
508
+ local_item_path = local_directory_path.joinpath(item.filename)
509
+
510
+ # Checks if item is a directory
511
+ if stat.S_ISDIR(item.st_mode): # type: ignore
512
+ # Recursively pulls the subdirectory
513
+ self.pull_directory(local_item_path, remote_item_path)
514
+ else:
515
+ # Pulls the individual file using existing method
516
+ sftp.get(localpath=str(local_item_path), remotepath=str(remote_item_path))
517
+
518
+ finally:
519
+ sftp.close()
520
+
521
+ def push_directory(self, local_directory_path: Path, remote_directory_path: Path) -> None:
522
+ """Recursively uploads the entire target directory from the local machine to the remote server.
523
+
524
+ Args:
525
+ local_directory_path: The path to the local directory to be uploaded.
526
+ remote_directory_path: The path on the remote server where the directory will be copied.
527
+ """
528
+ if not local_directory_path.exists() or not local_directory_path.is_dir():
529
+ message = (
530
+ f"Unable to upload the target local directory {local_directory_path} to the server, as it does not "
531
+ f"exist."
532
+ )
533
+ console.error(message=message, error=FileNotFoundError)
534
+
535
+ sftp = self._client.open_sftp()
536
+
537
+ try:
538
+ # Creates the remote directory using existing method
539
+ self.create_directory(remote_directory_path, parents=True)
540
+
541
+ # Iterates through all items in the local directory
542
+ for local_item_path in local_directory_path.iterdir():
543
+ remote_item_path = remote_directory_path.joinpath(local_item_path.name)
544
+
545
+ if local_item_path.is_dir():
546
+ # Recursively pushes subdirectory
547
+ self.push_directory(local_item_path, remote_item_path)
548
+ else:
549
+ # Pushes the individual file using existing method
550
+ sftp.put(localpath=str(local_item_path), remotepath=str(remote_item_path))
551
+
552
+ finally:
553
+ sftp.close()
554
+
489
555
  def remove(self, remote_path: Path, is_dir: bool) -> None:
490
556
  """Removes the specified file or directory from the remote server.
491
557
 
@@ -216,6 +216,20 @@ class Server:
216
216
  local_file_path: The path to the file that needs to be copied to the remote server.
217
217
  remote_file_path: The path to the file on the remote server (where to copy the file).
218
218
  """
219
+ def pull_directory(self, local_directory_path: Path, remote_directory_path: Path) -> None:
220
+ """Recursively downloads the entire target directory from the remote server to the local machine.
221
+
222
+ Args:
223
+ local_directory_path: The path to the local directory where the remote directory will be copied.
224
+ remote_directory_path: The path to the directory on the remote server to be downloaded.
225
+ """
226
+ def push_directory(self, local_directory_path: Path, remote_directory_path: Path) -> None:
227
+ """Recursively uploads the entire target directory from the local machine to the remote server.
228
+
229
+ Args:
230
+ local_directory_path: The path to the local directory to be uploaded.
231
+ remote_directory_path: The path on the remote server where the directory will be copied.
232
+ """
219
233
  def remove(self, remote_path: Path, is_dir: bool) -> None:
220
234
  """Removes the specified file or directory from the remote server.
221
235