mxbiflow 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. mxbiflow/__init__.py +3 -0
  2. mxbiflow/assets/__init__.py +5 -0
  3. mxbiflow/assets/clicker.wav +0 -0
  4. mxbiflow/config_store.py +68 -0
  5. mxbiflow/data_logger.py +114 -0
  6. mxbiflow/default/__init__.py +4 -0
  7. mxbiflow/default/idle/assets/apple_v1.png +0 -0
  8. mxbiflow/default/idle/idle.py +57 -0
  9. mxbiflow/detector_bridge.py +87 -0
  10. mxbiflow/game.py +84 -0
  11. mxbiflow/infra/eventbus.py +31 -0
  12. mxbiflow/main.py +106 -0
  13. mxbiflow/models/animal.py +130 -0
  14. mxbiflow/models/reward.py +7 -0
  15. mxbiflow/models/session.py +145 -0
  16. mxbiflow/mxbiflow.py +43 -0
  17. mxbiflow/path.py +41 -0
  18. mxbiflow/scene/__init__.py +8 -0
  19. mxbiflow/scene/scene_manager.py +64 -0
  20. mxbiflow/scene/scene_protocol.py +22 -0
  21. mxbiflow/scheduler.py +90 -0
  22. mxbiflow/tasks/GNGSiD/models.py +70 -0
  23. mxbiflow/tasks/GNGSiD/stages/detect_stage/config.json +116 -0
  24. mxbiflow/tasks/GNGSiD/stages/detect_stage/detect_stage.py +161 -0
  25. mxbiflow/tasks/GNGSiD/stages/detect_stage/detect_stage_models.py +65 -0
  26. mxbiflow/tasks/GNGSiD/stages/discriminate_stage/config.json +70 -0
  27. mxbiflow/tasks/GNGSiD/stages/discriminate_stage/discriminate_stage.py +173 -0
  28. mxbiflow/tasks/GNGSiD/stages/discriminate_stage/discriminate_stage_models.py +80 -0
  29. mxbiflow/tasks/GNGSiD/stages/size_reduction_stage/config.json +83 -0
  30. mxbiflow/tasks/GNGSiD/stages/size_reduction_stage/size_reduction_models.py +58 -0
  31. mxbiflow/tasks/GNGSiD/stages/size_reduction_stage/size_reduction_stage.py +149 -0
  32. mxbiflow/tasks/GNGSiD/tasks/artifacts.py +13 -0
  33. mxbiflow/tasks/GNGSiD/tasks/detect/models.py +21 -0
  34. mxbiflow/tasks/GNGSiD/tasks/detect/scene.py +271 -0
  35. mxbiflow/tasks/GNGSiD/tasks/discriminate/discriminate_models.py +31 -0
  36. mxbiflow/tasks/GNGSiD/tasks/discriminate/discriminate_scene.py +336 -0
  37. mxbiflow/tasks/GNGSiD/tasks/touch/touch_models.py +17 -0
  38. mxbiflow/tasks/GNGSiD/tasks/touch/touch_scene.py +256 -0
  39. mxbiflow/tasks/GNGSiD/tasks/utils/targets.py +57 -0
  40. mxbiflow/tasks/cross_modal/bundle_dir.py +553 -0
  41. mxbiflow/tasks/cross_modal/config.py +41 -0
  42. mxbiflow/tasks/cross_modal/media.py +61 -0
  43. mxbiflow/tasks/cross_modal/models.py +57 -0
  44. mxbiflow/tasks/cross_modal/scene.py +252 -0
  45. mxbiflow/tasks/cross_modal/stage.py +218 -0
  46. mxbiflow/tasks/cross_modal/trial_io.py +23 -0
  47. mxbiflow/tasks/cross_modal/trial_schema.py +113 -0
  48. mxbiflow/tasks/default/error_task/error_scene.py +53 -0
  49. mxbiflow/tasks/default/idle_task/assets/apple_v1.png +0 -0
  50. mxbiflow/tasks/default/idle_task/idle_scene.py +85 -0
  51. mxbiflow/tasks/default/initial_habituation_training/README.md +188 -0
  52. mxbiflow/tasks/default/initial_habituation_training/stages/config.csv +7 -0
  53. mxbiflow/tasks/default/initial_habituation_training/stages/config.json +67 -0
  54. mxbiflow/tasks/default/initial_habituation_training/stages/initial_habituation_training_stage.py +172 -0
  55. mxbiflow/tasks/default/initial_habituation_training/stages/models.py +56 -0
  56. mxbiflow/tasks/default/initial_habituation_training/tasks/stay_to_reward/stay_to_reward.py +244 -0
  57. mxbiflow/tasks/default/initial_habituation_training/tasks/stay_to_reward/stay_to_reward_models.py +50 -0
  58. mxbiflow/tasks/task_protocol.py +26 -0
  59. mxbiflow/tasks/task_table.py +29 -0
  60. mxbiflow/tasks/two_alternative_choice/assets/starter.py +27 -0
  61. mxbiflow/tasks/two_alternative_choice/models.py +68 -0
  62. mxbiflow/tasks/two_alternative_choice/stages/size_reduction_stage/config.json +118 -0
  63. mxbiflow/tasks/two_alternative_choice/stages/size_reduction_stage/size_reduction_models.py +41 -0
  64. mxbiflow/tasks/two_alternative_choice/stages/size_reduction_stage/size_reduction_stage.py +122 -0
  65. mxbiflow/tasks/two_alternative_choice/tasks/touch/touch_models.py +19 -0
  66. mxbiflow/tasks/two_alternative_choice/tasks/touch/touch_scene.py +249 -0
  67. mxbiflow/timer/__init__.py +3 -0
  68. mxbiflow/timer/frame_timer.py +47 -0
  69. mxbiflow/timer/realtime_timer.py +0 -0
  70. mxbiflow/tmp_email.py +13 -0
  71. mxbiflow/ui/components/animal.py +87 -0
  72. mxbiflow/ui/components/baseconfig.py +68 -0
  73. mxbiflow/ui/components/card.py +18 -0
  74. mxbiflow/ui/components/device_card/__init__.py +17 -0
  75. mxbiflow/ui/components/device_card/detector/beambreak_detector_card.py +29 -0
  76. mxbiflow/ui/components/device_card/detector/fusion_detector.py +45 -0
  77. mxbiflow/ui/components/device_card/detector/mock_detector_card.py +20 -0
  78. mxbiflow/ui/components/device_card/detector/rfid_detector.py +40 -0
  79. mxbiflow/ui/components/device_card/device_card.py +67 -0
  80. mxbiflow/ui/components/device_card/rewarder/mock_rewarder_card.py +20 -0
  81. mxbiflow/ui/components/device_card/rewarder/rpi_gpio_rewarder.py +33 -0
  82. mxbiflow/ui/components/devices.py +183 -0
  83. mxbiflow/ui/components/dialog/__init__.py +3 -0
  84. mxbiflow/ui/components/dialog/add_devices_dialog.py +64 -0
  85. mxbiflow/ui/components/experiment_groups.py +122 -0
  86. mxbiflow/ui/experiment_panel.py +91 -0
  87. mxbiflow/ui/mxbi_panel.py +152 -0
  88. mxbiflow/utils/logger.py +19 -0
  89. mxbiflow/utils/serial.py +10 -0
  90. mxbiflow-0.1.1.dist-info/METADATA +168 -0
  91. mxbiflow-0.1.1.dist-info/RECORD +93 -0
  92. mxbiflow-0.1.1.dist-info/WHEEL +4 -0
  93. mxbiflow-0.1.1.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,244 @@
1
+ from datetime import datetime
2
+ from math import ceil
3
+ from random import uniform
4
+ from tkinter import Frame
5
+ from typing import TYPE_CHECKING, Final
6
+
7
+ from mxbi.tasks.default.initial_habituation_training.tasks.stay_to_reward.stay_to_reward_models import (
8
+ DataToShow,
9
+ Result,
10
+ TrialData,
11
+ )
12
+ from mxbi.utils.aplayer import ToneConfig
13
+ from mxbi.utils.tkinter.components.canvas_with_border import CanvasWithInnerBorder
14
+ from mxbi.utils.tkinter.components.showdata_widget import ShowDataWidget
15
+
16
+ if TYPE_CHECKING:
17
+ from concurrent.futures import Future
18
+
19
+ from mxbi.models.animal import AnimalState
20
+ from mxbi.models.session import ScreenConfig
21
+ from mxbi.tasks.default.initial_habituation_training.stages.models import (
22
+ StageContext,
23
+ )
24
+ from mxbi.tasks.default.initial_habituation_training.tasks.stay_to_reward.stay_to_reward_models import (
25
+ TrialConfig,
26
+ )
27
+ from mxbi.theater import Theater
28
+ from numpy import int16
29
+ from numpy.typing import NDArray
30
+
31
+
32
+ class DefaultStayToRewardScene:
33
+ def __init__(
34
+ self,
35
+ theater: "Theater",
36
+ animal_state: "AnimalState",
37
+ screen_type: "ScreenConfig",
38
+ trial_config: "TrialConfig",
39
+ context: "StageContext",
40
+ background: "CanvasWithInnerBorder",
41
+ ):
42
+ self._theater: "Final[Theater]" = theater
43
+ self._animal_state: "Final[AnimalState]" = animal_state
44
+ self._screen_type: "Final[ScreenConfig]" = screen_type
45
+ self._trial_config: "Final[TrialConfig]" = trial_config
46
+ self._context: "Final[StageContext]" = context
47
+ self._background = background
48
+
49
+ self._play_future: Future[bool] | None = None
50
+
51
+ self._tone = self._prepare_stimulus()
52
+ self._standard_reward_stimulus = self._theater.new_standard_reward_stimulus(
53
+ self._trial_config.stimulus_duration
54
+ )
55
+
56
+ self._set_stimulus_intensity()
57
+ self._on_trial_start()
58
+
59
+ # region public api
60
+ def start(self) -> "TrialData":
61
+ self._theater.mainloop()
62
+
63
+ return self._data
64
+
65
+ def cancle(self) -> None:
66
+ self._cleanup()
67
+
68
+ # endregion
69
+
70
+ # region lifecycle
71
+ def _on_trial_start(self) -> None:
72
+ self._create_view()
73
+ self._init_data()
74
+ self._bind_events()
75
+ self._start_tracking_data()
76
+
77
+ if (
78
+ self._trial_config.entry_reward
79
+ and self._animal_state.current_animal_session_trial_id == 1
80
+ ):
81
+ self._direct_stimulus()
82
+ else:
83
+ self._start_timing()
84
+ self._stimulus_loop()
85
+
86
+ def _on_trial_end(self) -> None:
87
+ if self._play_future is not None:
88
+ self._play_future.add_done_callback(lambda _: self._cleanup())
89
+ else:
90
+ self._cleanup()
91
+
92
+ def _cleanup(self) -> None:
93
+ self._data.trial_end_time = datetime.now().timestamp()
94
+ self._data.stay_duration = (
95
+ self._data.trial_end_time - self._data.trial_start_time
96
+ )
97
+
98
+ self._theater.aplayer.stop()
99
+ self._trigger.destroy()
100
+ self._show_data_widget.destroy()
101
+ self._theater.root.quit()
102
+
103
+ # endregion
104
+
105
+ # region views
106
+
107
+ def _create_view(self) -> None:
108
+ self._create_background()
109
+ self._create_trigger()
110
+ self._create_show_data_widget()
111
+
112
+ def _create_background(self) -> None:
113
+ self._background.place(relx=0.5, rely=0.5, anchor="center")
114
+
115
+ def _create_trigger(self) -> None:
116
+ self._trigger = Frame(bg="red", width=0, height=0)
117
+ self._trigger.place(relx=0.5, rely=0.5, anchor="center")
118
+ self._trigger.lower()
119
+
120
+ def _create_show_data_widget(self) -> None:
121
+ self._show_data_widget = ShowDataWidget(self._background)
122
+ self._show_data_widget.place(relx=0, rely=1, anchor="sw")
123
+ _data = DataToShow(
124
+ name=self._animal_state.name,
125
+ ald=self._animal_state.current_animal_session_trial_id,
126
+ level=self._animal_state.level,
127
+ id=self._animal_state.trial_id,
128
+ lid=self._animal_state.current_level_trial_id,
129
+ t_dur=f"{self._context.duration} s",
130
+ dur="0 s",
131
+ rewards=0,
132
+ )
133
+ self._show_data_widget.show_data(_data.model_dump())
134
+
135
+ # endregion
136
+
137
+ # region event binding
138
+ def _bind_events(self) -> None:
139
+ self._trigger.focus_set()
140
+
141
+ def _start_timing(self) -> None:
142
+ target_ms = int(self._trial_config.target * 1000)
143
+ self._trigger.after(target_ms, self._on_correct)
144
+
145
+ def _stimulus_loop(self) -> None:
146
+ stimulus_interval = uniform(
147
+ self._trial_config.min_stimulus_interval,
148
+ self._trial_config.max_stimulus_interval,
149
+ )
150
+ stimulus_interval_ms = int(stimulus_interval * 1000)
151
+
152
+ self._trigger.after(stimulus_interval_ms, self._give_stimulus)
153
+
154
+ # endregion
155
+
156
+ # region data
157
+ def _start_tracking_data(self) -> None:
158
+ data = DataToShow(
159
+ name=self._animal_state.name,
160
+ ald=self._animal_state.current_animal_session_trial_id,
161
+ level=self._animal_state.level,
162
+ id=self._animal_state.trial_id,
163
+ lid=self._animal_state.current_level_trial_id,
164
+ t_dur=f"{self._context.duration} s",
165
+ dur=f"{int(self._data.stay_duration)} s",
166
+ rewards=self._context.rewards,
167
+ )
168
+
169
+ self._data.stay_duration += 1
170
+ self._context.duration += 1
171
+
172
+ self._show_data_widget.update_data(data.model_dump())
173
+ self._trigger.after(1000, self._start_tracking_data)
174
+
175
+ def _init_data(self) -> None:
176
+ self._data = TrialData(
177
+ level=self._trial_config.level,
178
+ level_trial_id=self._animal_state.current_level_trial_id,
179
+ animal_session_trial_id=self._animal_state.current_animal_session_trial_id,
180
+ animal=self._animal_state.name,
181
+ trial_id=self._animal_state.trial_id,
182
+ trial_start_time=datetime.now().timestamp(),
183
+ trial_end_time=0,
184
+ stay_duration=0,
185
+ result=Result.CORRECT,
186
+ )
187
+
188
+ # endregion
189
+
190
+ # region stimulus and reward
191
+ def _prepare_stimulus(self) -> "NDArray[int16]":
192
+ unit_duration = (
193
+ self._trial_config.stimulus_freq_duration
194
+ + self._trial_config.stimulus_freq_interval
195
+ )
196
+ times = ceil(self._trial_config.stimulus_duration / unit_duration)
197
+ times = max(times, 1)
198
+
199
+ freq_1 = ToneConfig(
200
+ frequency=self._trial_config.stimulus_freq,
201
+ duration=self._trial_config.stimulus_freq_duration,
202
+ )
203
+ freq_2 = ToneConfig(
204
+ frequency=0,
205
+ duration=self._trial_config.stimulus_freq_interval,
206
+ )
207
+
208
+ return self._theater.aplayer.generate_stimulus([freq_1, freq_2], times)
209
+
210
+ def _set_stimulus_intensity(self) -> None:
211
+ self._theater.acontroller.set_master_volume(self._trial_config.stimulus_density)
212
+
213
+ def _give_stimulus(self) -> None:
214
+ self._play_future = self._theater.aplayer.play_stimulus(self._tone)
215
+ self._play_future.add_done_callback(self._on_stimulus_complete)
216
+
217
+ def _direct_stimulus(self) -> None:
218
+ self._play_future = self._theater.aplayer.play_stimulus(self._tone)
219
+ self._play_future.add_done_callback(self._on_direct_stimulus_complete)
220
+
221
+ def _on_direct_stimulus_complete(self, future: "Future[bool]") -> None:
222
+ if future.result():
223
+ self._trigger.after(0, self._give_reward)
224
+
225
+ self._trigger.after(
226
+ self._trial_config.reward_duration,
227
+ lambda: (self._start_timing(), self._stimulus_loop()),
228
+ )
229
+
230
+ def _on_stimulus_complete(self, future: "Future[bool]") -> None:
231
+ if future.result():
232
+ self._trigger.after(0, self._give_reward)
233
+
234
+ self._trigger.after(self._trial_config.reward_duration, self._stimulus_loop)
235
+
236
+ def _on_correct(self) -> None:
237
+ self._data.result = Result.CORRECT
238
+ self._on_trial_end()
239
+
240
+ def _give_reward(self) -> None:
241
+ self._context.rewards += 1
242
+ self._theater.reward.give_reward(self._trial_config.reward_duration)
243
+
244
+ # endregion
@@ -0,0 +1,50 @@
1
+ from enum import StrEnum, auto
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class Result(StrEnum):
7
+ CORRECT = auto()
8
+ INCORRECT = auto()
9
+
10
+
11
+ class TrialConfig(BaseModel):
12
+ level: int
13
+ entry_reward: bool
14
+
15
+ min_stimulus_interval: float # seconds
16
+ max_stimulus_interval: float # seconds
17
+ target: float # seconds
18
+
19
+ reward_duration: int = 1000 # milliseconds
20
+ stimulus_duration: int = 1000 # milliseconds
21
+
22
+ stimulus_density: int # volume 0-100
23
+
24
+ stimulus_freq: int = 2000
25
+ stimulus_freq_duration: int = 100
26
+ stimulus_freq_interval: int = 100
27
+
28
+
29
+ class TrialData(BaseModel):
30
+ level: int
31
+ animal: str
32
+ trial_id: int
33
+ level_trial_id: int
34
+ animal_session_trial_id: int
35
+ trial_start_time: float
36
+ trial_end_time: float
37
+ result: Result
38
+
39
+ stay_duration: float
40
+
41
+
42
+ class DataToShow(BaseModel):
43
+ level: int
44
+ name: str
45
+ id: int
46
+ lid: int
47
+ ald: int
48
+ t_dur: str
49
+ dur: str
50
+ rewards: int
@@ -0,0 +1,26 @@
1
+ from typing import TYPE_CHECKING, Protocol
2
+
3
+ if TYPE_CHECKING:
4
+ from mxbi.models.animal import AnimalState, ScheduleCondition
5
+ from mxbi.models.session import SessionState
6
+ from mxbi.models.task import Feedback
7
+ from mxbi.theater import Theater
8
+
9
+
10
+ class Task(Protocol):
11
+ def __init__(
12
+ self,
13
+ theater: "Theater",
14
+ session_state: "SessionState",
15
+ animal_state: "AnimalState",
16
+ ) -> None: ...
17
+ def start(self) -> "Feedback": ...
18
+
19
+ def quit(self) -> None: ...
20
+
21
+ def on_idle(self) -> None: ...
22
+
23
+ def on_return(self) -> None: ...
24
+
25
+ @property
26
+ def condition(self) -> "ScheduleCondition | None": ...
@@ -0,0 +1,29 @@
1
+ from mxbi.models.task import TaskEnum
2
+ from mxbi.tasks.cross_modal.stage import CrossModalTask
3
+ from mxbi.tasks.default.error_task.error_scene import ErrorScene
4
+ from mxbi.tasks.default.idle_task.idle_scene import IDLEScene
5
+ from mxbi.tasks.default.initial_habituation_training.stages.initial_habituation_training_stage import (
6
+ InitialHabituationTrainingStage,
7
+ )
8
+ from mxbi.tasks.GNGSiD.stages.detect_stage.detect_stage import GNGSiDDetectStage
9
+ from mxbi.tasks.GNGSiD.stages.discriminate_stage.discriminate_stage import (
10
+ GNGSiDDiscriminateStage,
11
+ )
12
+ from mxbi.tasks.GNGSiD.stages.size_reduction_stage.size_reduction_stage import (
13
+ SizeReductionStage,
14
+ )
15
+ from mxbi.tasks.task_protocol import Task
16
+ from mxbi.tasks.two_alternative_choice.stages.size_reduction_stage.size_reduction_stage import (
17
+ TWOACSizeReductionStage,
18
+ )
19
+
20
+ task_table: dict[TaskEnum, type[Task]] = {
21
+ TaskEnum.IDEL: IDLEScene,
22
+ TaskEnum.ERROR: ErrorScene,
23
+ TaskEnum.HABITUATION: InitialHabituationTrainingStage,
24
+ TaskEnum.GNGSiD_SIZE_REDUCTION_STAGE: SizeReductionStage,
25
+ TaskEnum.GNGSiD_DETECT_STAGE: GNGSiDDetectStage,
26
+ TaskEnum.GNGSiD_DISCRIMINATE_STAGE: GNGSiDDiscriminateStage,
27
+ TaskEnum.TWOAC_SIZE_REDUCTION_STAGE: TWOACSizeReductionStage,
28
+ TaskEnum.CROSS_MODAL: CrossModalTask,
29
+ }
@@ -0,0 +1,27 @@
1
+ from tkinter import Canvas
2
+
3
+ from mxbi.utils.tkinter.create_circle import create_circle
4
+
5
+
6
+ class Starter(Canvas):
7
+ def __init__(self, master, size) -> None:
8
+ super().__init__(
9
+ master, width=size, height=size, bg="blue", highlightthickness=0
10
+ )
11
+
12
+ create_circle(
13
+ size * 0.5,
14
+ size * 0.5,
15
+ (size - 1) / 2,
16
+ self,
17
+ "white",
18
+ )
19
+
20
+
21
+ if __name__ == "__main__":
22
+ from tkinter import Tk
23
+
24
+ root = Tk()
25
+ root.geometry("300x300")
26
+ Starter(root, 300).pack()
27
+ root.mainloop()
@@ -0,0 +1,68 @@
1
+ from enum import StrEnum, auto
2
+ from typing import TypeAlias
3
+
4
+ from pydantic import BaseModel
5
+
6
+ LevelID: TypeAlias = int
7
+ MonkeyName: TypeAlias = str
8
+
9
+
10
+ class Result(StrEnum):
11
+ CORRECT = auto()
12
+ INCORRECT = auto()
13
+ TIMEOUT = auto()
14
+ CANCEL = auto()
15
+
16
+
17
+ class TouchEvent(BaseModel):
18
+ time: float
19
+ x: int
20
+ y: int
21
+
22
+
23
+ class BaseTrialConfig(BaseModel):
24
+ # basic config
25
+ level: int = 0
26
+
27
+ # visual stimulation config
28
+ stimulation_size: int = 0
29
+
30
+ # audio stimulation config
31
+ stimulus_duration: int = 0
32
+
33
+ # trial lifecycle
34
+ time_out: int = 0
35
+ inter_trial_interval: int = 0
36
+
37
+ # reward config
38
+ reward_duration: int = 0
39
+ reward_delay: int = 0
40
+
41
+
42
+ class BaseTrialData(BaseModel):
43
+ animal: str
44
+ trial_id: int
45
+ current_level_trial_id: int
46
+ trial_start_time: float
47
+ trial_end_time: float
48
+ result: Result
49
+ correct_rate: float
50
+ touch_events: list[TouchEvent]
51
+
52
+
53
+ class BaseDataToShow(BaseModel):
54
+ name: str
55
+ id: int
56
+ level_id: int
57
+ level: int
58
+ rewards: int
59
+ correct: int
60
+ incorrect: int
61
+ timeout: int
62
+
63
+
64
+ class PersistentData(BaseModel):
65
+ rewards: int
66
+ correct: int
67
+ incorrect: int
68
+ timeout: int
@@ -0,0 +1,118 @@
1
+ {
2
+ "default": {
3
+ "condition": {
4
+ "config": {
5
+ "evaluation_interval": 20,
6
+ "difficulty_increase_threshold": 0.8,
7
+ "difficulty_decrease_threshold": 0.45,
8
+ "next_task": "gngsid_detect_stage"
9
+ }
10
+ },
11
+ "trial_config": {
12
+ "stimulus_duration": 1000,
13
+ "time_out": 10000,
14
+ "inter_trial_interval": 1000,
15
+ "reward_duration": 1000,
16
+ "reward_delay": 0,
17
+ "stimulus_freq": 2000,
18
+ "stimulus_freq_duration": 100,
19
+ "stimulus_interval": 100
20
+ },
21
+ "levels_table": {
22
+ "1": {
23
+ "level": 1,
24
+ "stimulation_size": 300
25
+ },
26
+ "2": {
27
+ "level": 2,
28
+ "stimulation_size": 295
29
+ },
30
+ "3": {
31
+ "level": 3,
32
+ "stimulation_size": 390
33
+ },
34
+ "4": {
35
+ "level": 4,
36
+ "stimulation_size": 285
37
+ },
38
+ "5": {
39
+ "level": 5,
40
+ "stimulation_size": 280
41
+ },
42
+ "6": {
43
+ "level": 6,
44
+ "stimulation_size": 275
45
+ },
46
+ "7": {
47
+ "level": 7,
48
+ "stimulation_size": 270
49
+ },
50
+ "8": {
51
+ "level": 8,
52
+ "stimulation_size": 260
53
+ },
54
+ "9": {
55
+ "level": 9,
56
+ "stimulation_size": 255
57
+ },
58
+ "10": {
59
+ "level": 10,
60
+ "stimulation_size": 240
61
+ },
62
+ "11": {
63
+ "level": 11,
64
+ "stimulation_size": 230
65
+ },
66
+ "12": {
67
+ "level": 12,
68
+ "stimulation_size": 225
69
+ },
70
+ "13": {
71
+ "level": 13,
72
+ "stimulation_size": 220
73
+ },
74
+ "14": {
75
+ "level": 14,
76
+ "stimulation_size": 210
77
+ },
78
+ "15": {
79
+ "level": 15,
80
+ "stimulation_size": 200
81
+ }
82
+ }
83
+ },
84
+ "debug": {
85
+ "condition": {
86
+ "config": {
87
+ "evaluation_interval": 2,
88
+ "difficulty_increase_threshold": 0.8,
89
+ "difficulty_decrease_threshold": 0.45,
90
+ "next_task": "gngsid_detect_stage"
91
+ }
92
+ },
93
+ "trial_config": {
94
+ "stimulus_duration": 1000,
95
+ "time_out": 10000,
96
+ "inter_trial_interval": 1000,
97
+ "reward_duration": 1000,
98
+ "reward_delay": 0,
99
+ "stimulus_freq": 2000,
100
+ "stimulus_freq_duration": 100,
101
+ "stimulus_interval": 100
102
+ },
103
+ "levels_table": {
104
+ "1": {
105
+ "level": 1,
106
+ "stimulation_size": 300
107
+ },
108
+ "2": {
109
+ "level": 2,
110
+ "stimulation_size": 295
111
+ },
112
+ "3": {
113
+ "level": 3,
114
+ "stimulation_size": 390
115
+ }
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,41 @@
1
+ from pathlib import Path
2
+
3
+ from mxbi.config import Configure
4
+ from mxbi.models.animal import ScheduleCondition
5
+ from mxbi.tasks.two_alternative_choice.models import LevelID, MonkeyName
6
+ from mxbi.tasks.two_alternative_choice.tasks.touch.touch_models import TrialConfig
7
+ from pydantic import BaseModel, ConfigDict, RootModel
8
+
9
+ CONFIG_PATH = Path(__file__).parent / "config.json"
10
+
11
+
12
+ class SizeReductionStageLeveledParameters(BaseModel):
13
+ model_config = ConfigDict(frozen=True)
14
+
15
+ level: int
16
+ stimulation_size: int
17
+
18
+
19
+ class SizeReductionStageConfig(BaseModel):
20
+ model_config = ConfigDict(frozen=True)
21
+
22
+ condition: ScheduleCondition
23
+ trial_config: TrialConfig
24
+
25
+ levels_table: dict[LevelID, SizeReductionStageLeveledParameters]
26
+
27
+
28
+ class SizeReductionStageConfigs(RootModel):
29
+ model_config = ConfigDict(frozen=True)
30
+
31
+ root: dict[MonkeyName, SizeReductionStageConfig]
32
+
33
+
34
+ def load_config() -> SizeReductionStageConfigs:
35
+ configs = Configure(CONFIG_PATH, SizeReductionStageConfigs).value
36
+ for config in configs.root.values():
37
+ config.condition.level_count = len(config.levels_table)
38
+ return configs
39
+
40
+
41
+ config = load_config()