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.
- mxbiflow/__init__.py +3 -0
- mxbiflow/assets/__init__.py +5 -0
- mxbiflow/assets/clicker.wav +0 -0
- mxbiflow/config_store.py +68 -0
- mxbiflow/data_logger.py +114 -0
- mxbiflow/default/__init__.py +4 -0
- mxbiflow/default/idle/assets/apple_v1.png +0 -0
- mxbiflow/default/idle/idle.py +57 -0
- mxbiflow/detector_bridge.py +87 -0
- mxbiflow/game.py +84 -0
- mxbiflow/infra/eventbus.py +31 -0
- mxbiflow/main.py +106 -0
- mxbiflow/models/animal.py +130 -0
- mxbiflow/models/reward.py +7 -0
- mxbiflow/models/session.py +145 -0
- mxbiflow/mxbiflow.py +43 -0
- mxbiflow/path.py +41 -0
- mxbiflow/scene/__init__.py +8 -0
- mxbiflow/scene/scene_manager.py +64 -0
- mxbiflow/scene/scene_protocol.py +22 -0
- mxbiflow/scheduler.py +90 -0
- mxbiflow/tasks/GNGSiD/models.py +70 -0
- mxbiflow/tasks/GNGSiD/stages/detect_stage/config.json +116 -0
- mxbiflow/tasks/GNGSiD/stages/detect_stage/detect_stage.py +161 -0
- mxbiflow/tasks/GNGSiD/stages/detect_stage/detect_stage_models.py +65 -0
- mxbiflow/tasks/GNGSiD/stages/discriminate_stage/config.json +70 -0
- mxbiflow/tasks/GNGSiD/stages/discriminate_stage/discriminate_stage.py +173 -0
- mxbiflow/tasks/GNGSiD/stages/discriminate_stage/discriminate_stage_models.py +80 -0
- mxbiflow/tasks/GNGSiD/stages/size_reduction_stage/config.json +83 -0
- mxbiflow/tasks/GNGSiD/stages/size_reduction_stage/size_reduction_models.py +58 -0
- mxbiflow/tasks/GNGSiD/stages/size_reduction_stage/size_reduction_stage.py +149 -0
- mxbiflow/tasks/GNGSiD/tasks/artifacts.py +13 -0
- mxbiflow/tasks/GNGSiD/tasks/detect/models.py +21 -0
- mxbiflow/tasks/GNGSiD/tasks/detect/scene.py +271 -0
- mxbiflow/tasks/GNGSiD/tasks/discriminate/discriminate_models.py +31 -0
- mxbiflow/tasks/GNGSiD/tasks/discriminate/discriminate_scene.py +336 -0
- mxbiflow/tasks/GNGSiD/tasks/touch/touch_models.py +17 -0
- mxbiflow/tasks/GNGSiD/tasks/touch/touch_scene.py +256 -0
- mxbiflow/tasks/GNGSiD/tasks/utils/targets.py +57 -0
- mxbiflow/tasks/cross_modal/bundle_dir.py +553 -0
- mxbiflow/tasks/cross_modal/config.py +41 -0
- mxbiflow/tasks/cross_modal/media.py +61 -0
- mxbiflow/tasks/cross_modal/models.py +57 -0
- mxbiflow/tasks/cross_modal/scene.py +252 -0
- mxbiflow/tasks/cross_modal/stage.py +218 -0
- mxbiflow/tasks/cross_modal/trial_io.py +23 -0
- mxbiflow/tasks/cross_modal/trial_schema.py +113 -0
- mxbiflow/tasks/default/error_task/error_scene.py +53 -0
- mxbiflow/tasks/default/idle_task/assets/apple_v1.png +0 -0
- mxbiflow/tasks/default/idle_task/idle_scene.py +85 -0
- mxbiflow/tasks/default/initial_habituation_training/README.md +188 -0
- mxbiflow/tasks/default/initial_habituation_training/stages/config.csv +7 -0
- mxbiflow/tasks/default/initial_habituation_training/stages/config.json +67 -0
- mxbiflow/tasks/default/initial_habituation_training/stages/initial_habituation_training_stage.py +172 -0
- mxbiflow/tasks/default/initial_habituation_training/stages/models.py +56 -0
- mxbiflow/tasks/default/initial_habituation_training/tasks/stay_to_reward/stay_to_reward.py +244 -0
- mxbiflow/tasks/default/initial_habituation_training/tasks/stay_to_reward/stay_to_reward_models.py +50 -0
- mxbiflow/tasks/task_protocol.py +26 -0
- mxbiflow/tasks/task_table.py +29 -0
- mxbiflow/tasks/two_alternative_choice/assets/starter.py +27 -0
- mxbiflow/tasks/two_alternative_choice/models.py +68 -0
- mxbiflow/tasks/two_alternative_choice/stages/size_reduction_stage/config.json +118 -0
- mxbiflow/tasks/two_alternative_choice/stages/size_reduction_stage/size_reduction_models.py +41 -0
- mxbiflow/tasks/two_alternative_choice/stages/size_reduction_stage/size_reduction_stage.py +122 -0
- mxbiflow/tasks/two_alternative_choice/tasks/touch/touch_models.py +19 -0
- mxbiflow/tasks/two_alternative_choice/tasks/touch/touch_scene.py +249 -0
- mxbiflow/timer/__init__.py +3 -0
- mxbiflow/timer/frame_timer.py +47 -0
- mxbiflow/timer/realtime_timer.py +0 -0
- mxbiflow/tmp_email.py +13 -0
- mxbiflow/ui/components/animal.py +87 -0
- mxbiflow/ui/components/baseconfig.py +68 -0
- mxbiflow/ui/components/card.py +18 -0
- mxbiflow/ui/components/device_card/__init__.py +17 -0
- mxbiflow/ui/components/device_card/detector/beambreak_detector_card.py +29 -0
- mxbiflow/ui/components/device_card/detector/fusion_detector.py +45 -0
- mxbiflow/ui/components/device_card/detector/mock_detector_card.py +20 -0
- mxbiflow/ui/components/device_card/detector/rfid_detector.py +40 -0
- mxbiflow/ui/components/device_card/device_card.py +67 -0
- mxbiflow/ui/components/device_card/rewarder/mock_rewarder_card.py +20 -0
- mxbiflow/ui/components/device_card/rewarder/rpi_gpio_rewarder.py +33 -0
- mxbiflow/ui/components/devices.py +183 -0
- mxbiflow/ui/components/dialog/__init__.py +3 -0
- mxbiflow/ui/components/dialog/add_devices_dialog.py +64 -0
- mxbiflow/ui/components/experiment_groups.py +122 -0
- mxbiflow/ui/experiment_panel.py +91 -0
- mxbiflow/ui/mxbi_panel.py +152 -0
- mxbiflow/utils/logger.py +19 -0
- mxbiflow/utils/serial.py +10 -0
- mxbiflow-0.1.1.dist-info/METADATA +168 -0
- mxbiflow-0.1.1.dist-info/RECORD +93 -0
- mxbiflow-0.1.1.dist-info/WHEEL +4 -0
- mxbiflow-0.1.1.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from random import choice, choices, randint
|
|
2
|
+
from typing import TYPE_CHECKING, Final
|
|
3
|
+
|
|
4
|
+
from mxbi.data_logger import DataLogger, DataLoggerType
|
|
5
|
+
from mxbi.models.animal import ScheduleCondition
|
|
6
|
+
from mxbi.tasks.GNGSiD.models import PersistentData, Result
|
|
7
|
+
from mxbi.tasks.GNGSiD.stages.detect_stage.detect_stage_models import (
|
|
8
|
+
DetectStageConfig,
|
|
9
|
+
config,
|
|
10
|
+
)
|
|
11
|
+
from mxbi.tasks.GNGSiD.tasks.detect.models import TrialConfig
|
|
12
|
+
from mxbi.tasks.GNGSiD.tasks.detect.scene import GNGSiDDetectScene
|
|
13
|
+
from mxbi.utils.logger import logger
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from mxbi.models.animal import AnimalState
|
|
17
|
+
from mxbi.models.session import SessionState
|
|
18
|
+
from mxbi.models.task import Feedback
|
|
19
|
+
from mxbi.theater import Theater
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_presistent_data: dict[str, PersistentData] = {}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GNGSiDDetectStage:
|
|
26
|
+
STAGE_NAME: Final[str] = "GNGSiD_DETECT_STAGE"
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
theater: "Theater",
|
|
31
|
+
session_state: "SessionState",
|
|
32
|
+
animal_state: "AnimalState",
|
|
33
|
+
) -> None:
|
|
34
|
+
self._theater = theater
|
|
35
|
+
self._session_state = session_state
|
|
36
|
+
self._animal_state = animal_state
|
|
37
|
+
|
|
38
|
+
self._stage_config = self._load_stage_config(animal_state.name)
|
|
39
|
+
|
|
40
|
+
_fixed_config = self._stage_config.params
|
|
41
|
+
_levels_config = self._stage_config.levels_table[animal_state.level]
|
|
42
|
+
|
|
43
|
+
_is_go = choices(
|
|
44
|
+
[True, False],
|
|
45
|
+
weights=[_levels_config.go_task_prob, _levels_config.nogo_task_prob],
|
|
46
|
+
)[0]
|
|
47
|
+
|
|
48
|
+
_stimulus_duration = randint(
|
|
49
|
+
_levels_config.min_stimulus_duration,
|
|
50
|
+
_levels_config.max_stimulus_duration,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
_master_amp, _digital_amp = self._prepare_stimulus_intensity(
|
|
54
|
+
self._animal_state.name, _fixed_config.stimulus_freq
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
_config = TrialConfig(
|
|
58
|
+
level=_levels_config.level,
|
|
59
|
+
stimulation_size=_fixed_config.stimulation_size,
|
|
60
|
+
stimulus_duration=_stimulus_duration,
|
|
61
|
+
time_out=_fixed_config.time_out,
|
|
62
|
+
inter_trial_interval=_fixed_config.inter_trial_interval,
|
|
63
|
+
reward_duration=_fixed_config.reward_duration,
|
|
64
|
+
reward_delay=_fixed_config.reward_delay,
|
|
65
|
+
go=_is_go,
|
|
66
|
+
visual_stimulus_delay=_fixed_config.visual_stimulus_delay,
|
|
67
|
+
stimulus_freq=_fixed_config.stimulus_freq,
|
|
68
|
+
stimulus_freq_duration=_fixed_config.stimulus_freq_duration,
|
|
69
|
+
stimulus_freq_master_amp=_master_amp,
|
|
70
|
+
stimulus_freq_digital_amp=_digital_amp,
|
|
71
|
+
stimulus_interval=_fixed_config.stimulus_interval,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
self._presistent_data = _presistent_data.get(self._animal_state.name)
|
|
75
|
+
|
|
76
|
+
if self._presistent_data is None:
|
|
77
|
+
self._presistent_data = PersistentData(
|
|
78
|
+
rewards=0,
|
|
79
|
+
correct=0,
|
|
80
|
+
incorrect=0,
|
|
81
|
+
timeout=0,
|
|
82
|
+
)
|
|
83
|
+
_presistent_data[self._animal_state.name] = self._presistent_data
|
|
84
|
+
|
|
85
|
+
self._task = GNGSiDDetectScene(
|
|
86
|
+
theater,
|
|
87
|
+
session_state.session_config,
|
|
88
|
+
animal_state,
|
|
89
|
+
session_state.session_config.screen_type,
|
|
90
|
+
_config,
|
|
91
|
+
self._presistent_data,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
self._data_logger = DataLogger(
|
|
95
|
+
self._session_state,
|
|
96
|
+
self._animal_state.name,
|
|
97
|
+
self.STAGE_NAME,
|
|
98
|
+
DataLoggerType.JSONL,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def start(self) -> "Feedback":
|
|
102
|
+
trial_data = self._task.start()
|
|
103
|
+
self._data_logger.save(trial_data.model_dump())
|
|
104
|
+
|
|
105
|
+
feedback = self._handle_result(trial_data.result)
|
|
106
|
+
logger.debug(
|
|
107
|
+
f"{self.STAGE_NAME}: "
|
|
108
|
+
f"session_id={self._session_state.session_id}, "
|
|
109
|
+
f"animal_name={self._animal_state.name}, "
|
|
110
|
+
f"animal_level={self._animal_state.level}, "
|
|
111
|
+
f"state_name={self.STAGE_NAME}, "
|
|
112
|
+
f"result={trial_data}, "
|
|
113
|
+
f"feedback={feedback}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return feedback
|
|
117
|
+
|
|
118
|
+
def _load_stage_config(self, monkey: str) -> DetectStageConfig:
|
|
119
|
+
stage_config = config.root.get(monkey) or config.root.get("default")
|
|
120
|
+
if stage_config is None:
|
|
121
|
+
raise ValueError("No default stage config found")
|
|
122
|
+
return stage_config
|
|
123
|
+
|
|
124
|
+
def _handle_result(self, result: "Result") -> "Feedback":
|
|
125
|
+
feedback = False
|
|
126
|
+
match result:
|
|
127
|
+
case Result.CORRECT:
|
|
128
|
+
_presistent_data[self._animal_state.name].correct += 1
|
|
129
|
+
feedback = True
|
|
130
|
+
case Result.INCORRECT:
|
|
131
|
+
_presistent_data[self._animal_state.name].incorrect += 1
|
|
132
|
+
feedback = False
|
|
133
|
+
case Result.TIMEOUT:
|
|
134
|
+
_presistent_data[self._animal_state.name].timeout += 1
|
|
135
|
+
feedback = False
|
|
136
|
+
case Result.CANCEL:
|
|
137
|
+
feedback = False
|
|
138
|
+
|
|
139
|
+
return feedback
|
|
140
|
+
|
|
141
|
+
def quit(self) -> None:
|
|
142
|
+
self._task.cancle()
|
|
143
|
+
|
|
144
|
+
def on_idle(self) -> None:
|
|
145
|
+
self._task.cancle()
|
|
146
|
+
|
|
147
|
+
def on_return(self) -> None:
|
|
148
|
+
self._task.cancle()
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def condition(self) -> "ScheduleCondition | None":
|
|
152
|
+
return self._stage_config.condition
|
|
153
|
+
|
|
154
|
+
def _prepare_stimulus_intensity(self, monkey: str, frequency: int):
|
|
155
|
+
bt = choice(([[10, 30], [50, 70]])) if monkey == "wolfgang" else []
|
|
156
|
+
at = [80, 80, 80] if monkey == "wolfgang" else [80, 80, 80]
|
|
157
|
+
intensity_options = at * 10 + bt
|
|
158
|
+
|
|
159
|
+
stimulus_intensity = choice(intensity_options)
|
|
160
|
+
|
|
161
|
+
return self._theater.acontroller.get_amp_value(frequency, stimulus_intensity)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from mxbi.config import Configure
|
|
4
|
+
from mxbi.models.animal import ScheduleCondition
|
|
5
|
+
from mxbi.tasks.GNGSiD.models import LevelID, MonkeyName
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, RootModel
|
|
7
|
+
|
|
8
|
+
CONFIG_PATH = Path(__file__).parent / "config.json"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DetectStageParams(BaseModel):
|
|
12
|
+
model_config = ConfigDict(frozen=True)
|
|
13
|
+
|
|
14
|
+
# visual stimulation config
|
|
15
|
+
stimulation_size: int
|
|
16
|
+
visual_stimulus_delay: int
|
|
17
|
+
|
|
18
|
+
# audio stimulation config
|
|
19
|
+
stimulus_freq: int
|
|
20
|
+
stimulus_freq_duration: int
|
|
21
|
+
stimulus_interval: int
|
|
22
|
+
|
|
23
|
+
# trial lifecycle
|
|
24
|
+
time_out: int
|
|
25
|
+
inter_trial_interval: int
|
|
26
|
+
|
|
27
|
+
# reward config
|
|
28
|
+
reward_duration: int
|
|
29
|
+
reward_delay: int
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DetectStageLeveledParams(BaseModel):
|
|
33
|
+
model_config = ConfigDict(frozen=True)
|
|
34
|
+
|
|
35
|
+
level: int
|
|
36
|
+
min_stimulus_duration: int
|
|
37
|
+
max_stimulus_duration: int
|
|
38
|
+
|
|
39
|
+
go_task_prob: float
|
|
40
|
+
nogo_task_prob: float
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DetectStageConfig(BaseModel):
|
|
44
|
+
model_config = ConfigDict(frozen=True)
|
|
45
|
+
|
|
46
|
+
condition: ScheduleCondition
|
|
47
|
+
|
|
48
|
+
params: DetectStageParams
|
|
49
|
+
levels_table: dict[LevelID, DetectStageLeveledParams]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class DetectStageConfigs(RootModel):
|
|
53
|
+
model_config = ConfigDict(frozen=True)
|
|
54
|
+
|
|
55
|
+
root: dict[MonkeyName, DetectStageConfig]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def load_config() -> DetectStageConfigs:
|
|
59
|
+
configs = Configure(CONFIG_PATH, DetectStageConfigs).value
|
|
60
|
+
for config in configs.root.values():
|
|
61
|
+
config.condition.level_count = len(config.levels_table)
|
|
62
|
+
return configs
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
config = load_config()
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"default": {
|
|
3
|
+
"condition": {
|
|
4
|
+
"config": {
|
|
5
|
+
"evaluation_interval": 20,
|
|
6
|
+
"difficulty_increase_threshold": 0.8,
|
|
7
|
+
"difficulty_decrease_threshold": 0.45
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"params": {
|
|
11
|
+
"level": 0,
|
|
12
|
+
"stimulation_size": 250,
|
|
13
|
+
"visual_stimulus_delay": 500,
|
|
14
|
+
"min_stimulus_duration": 2500,
|
|
15
|
+
"max_stimulus_duration": 6500,
|
|
16
|
+
"extra_response_time": 1000,
|
|
17
|
+
"stimulus_configs": [
|
|
18
|
+
{
|
|
19
|
+
"stimulus_freq_high": 2640,
|
|
20
|
+
"stimulus_freq_high_duration": 100,
|
|
21
|
+
"stimulus_freq_low": 1760,
|
|
22
|
+
"stimulus_freq_low_duration": 100
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"stimulus_freq_high": 5920,
|
|
26
|
+
"stimulus_freq_high_duration": 100,
|
|
27
|
+
"stimulus_freq_low": 3950,
|
|
28
|
+
"stimulus_freq_low_duration": 100
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"stimulus_freq_high": 7000,
|
|
32
|
+
"stimulus_freq_high_duration": 100,
|
|
33
|
+
"stimulus_freq_low": 4700,
|
|
34
|
+
"stimulus_freq_low_duration": 100
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"stimulus_interval": 200,
|
|
38
|
+
"time_out": 10000,
|
|
39
|
+
"inter_trial_interval": 2000,
|
|
40
|
+
"reward_duration": 1000,
|
|
41
|
+
"medium_reward_duration": 800,
|
|
42
|
+
"medium_reward_threshold": 2000,
|
|
43
|
+
"low_reward_duration": 500,
|
|
44
|
+
"reward_delay": 500,
|
|
45
|
+
"attention_duration": 2800
|
|
46
|
+
},
|
|
47
|
+
"levels_table": {
|
|
48
|
+
"0": {
|
|
49
|
+
"level": 0,
|
|
50
|
+
"stimulus_trial_prob": 0.2,
|
|
51
|
+
"nostimulus_trial_prob": 0.8
|
|
52
|
+
},
|
|
53
|
+
"1": {
|
|
54
|
+
"level": 1,
|
|
55
|
+
"stimulus_trial_prob": 0.3,
|
|
56
|
+
"nostimulus_trial_prob": 0.7
|
|
57
|
+
},
|
|
58
|
+
"2": {
|
|
59
|
+
"level": 2,
|
|
60
|
+
"stimulus_trial_prob": 0.4,
|
|
61
|
+
"nostimulus_trial_prob": 0.6
|
|
62
|
+
},
|
|
63
|
+
"3": {
|
|
64
|
+
"level": 3,
|
|
65
|
+
"stimulus_trial_prob": 0.5,
|
|
66
|
+
"nostimulus_trial_prob": 0.5
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from random import choice, choices, randint
|
|
2
|
+
from typing import TYPE_CHECKING, Final
|
|
3
|
+
|
|
4
|
+
from mxbi.data_logger import DataLogger, DataLoggerType
|
|
5
|
+
from mxbi.models.animal import ScheduleCondition
|
|
6
|
+
from mxbi.tasks.GNGSiD.models import PersistentData, Result
|
|
7
|
+
from mxbi.tasks.GNGSiD.stages.discriminate_stage.discriminate_stage_models import (
|
|
8
|
+
DiscriminateStageConfig,
|
|
9
|
+
config,
|
|
10
|
+
)
|
|
11
|
+
from mxbi.tasks.GNGSiD.tasks.discriminate.discriminate_models import TrialConfig
|
|
12
|
+
from mxbi.tasks.GNGSiD.tasks.discriminate.discriminate_scene import (
|
|
13
|
+
GNGSiDDiscriminateScene,
|
|
14
|
+
)
|
|
15
|
+
from mxbi.utils.logger import logger
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from mxbi.models.animal import AnimalState
|
|
19
|
+
from mxbi.models.session import SessionState
|
|
20
|
+
from mxbi.models.task import Feedback
|
|
21
|
+
from mxbi.theater import Theater
|
|
22
|
+
|
|
23
|
+
_presistent_data: dict[str, PersistentData] = {}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GNGSiDDiscriminateStage:
|
|
27
|
+
STAGE_NAME: Final[str] = "GNGSiD_DISCRIMINATE_STAGE"
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
theater: "Theater",
|
|
32
|
+
session_state: "SessionState",
|
|
33
|
+
animal_state: "AnimalState",
|
|
34
|
+
) -> None:
|
|
35
|
+
self._theater = theater
|
|
36
|
+
self._session_state = session_state
|
|
37
|
+
self._animal_state = animal_state
|
|
38
|
+
|
|
39
|
+
self._stage_config = self._load_stage_config(animal_state.name)
|
|
40
|
+
|
|
41
|
+
_fixed_config = self._stage_config.params
|
|
42
|
+
_levels_config = self._stage_config.levels_table[animal_state.level]
|
|
43
|
+
|
|
44
|
+
_stimulus_config = choice(_fixed_config.stimulus_configs)
|
|
45
|
+
_stimulus_duration = randint(
|
|
46
|
+
_fixed_config.min_stimulus_duration, _fixed_config.max_stimulus_duration
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
_is_stimulus_trial = choices(
|
|
50
|
+
[True, False],
|
|
51
|
+
weights=[
|
|
52
|
+
_levels_config.stimulus_trial_prob,
|
|
53
|
+
_levels_config.nostimulus_trial_prob,
|
|
54
|
+
],
|
|
55
|
+
)[0]
|
|
56
|
+
|
|
57
|
+
_high_master_amp, _high_digital_amp = self._prepare_stimulus_intensity(
|
|
58
|
+
animal_state.name, _stimulus_config.stimulus_freq_high
|
|
59
|
+
)
|
|
60
|
+
_low_master_amp, _low_digital_amp = self._prepare_stimulus_intensity(
|
|
61
|
+
animal_state.name, _stimulus_config.stimulus_freq_low
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
_config = TrialConfig(
|
|
65
|
+
level=_levels_config.level,
|
|
66
|
+
stimulation_size=_fixed_config.stimulation_size,
|
|
67
|
+
stimulus_duration=_stimulus_duration,
|
|
68
|
+
time_out=_fixed_config.time_out,
|
|
69
|
+
inter_trial_interval=_fixed_config.inter_trial_interval,
|
|
70
|
+
reward_duration=_fixed_config.reward_duration,
|
|
71
|
+
reward_delay=_fixed_config.reward_delay,
|
|
72
|
+
is_stimulus_trial=_is_stimulus_trial,
|
|
73
|
+
visual_stimulus_delay=_fixed_config.visual_stimulus_delay,
|
|
74
|
+
medium_reward_duration=_fixed_config.medium_reward_duration,
|
|
75
|
+
medium_reward_threshold=_fixed_config.medium_reward_threshold,
|
|
76
|
+
low_reward_duration=_fixed_config.low_reward_duration,
|
|
77
|
+
attention_duration=_fixed_config.attention_duration,
|
|
78
|
+
stimulus_freq_low=_stimulus_config.stimulus_freq_low,
|
|
79
|
+
stimulus_freq_low_duration=_stimulus_config.stimulus_freq_low_duration,
|
|
80
|
+
stimulus_freq_low_master_amp=_low_master_amp,
|
|
81
|
+
stimulus_freq_low_digital_amp=_low_digital_amp,
|
|
82
|
+
stimulus_freq_high=_stimulus_config.stimulus_freq_high,
|
|
83
|
+
stimulus_freq_high_duration=_stimulus_config.stimulus_freq_high_duration,
|
|
84
|
+
stimulus_freq_high_master_amp=_high_master_amp,
|
|
85
|
+
stimulus_freq_high_digital_amp=_high_digital_amp,
|
|
86
|
+
stimulus_interval=_fixed_config.stimulus_interval,
|
|
87
|
+
extra_response_time=_fixed_config.extra_response_time,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
self._presistent_data = _presistent_data.get(self._animal_state.name)
|
|
91
|
+
|
|
92
|
+
if self._presistent_data is None:
|
|
93
|
+
self._presistent_data = PersistentData(
|
|
94
|
+
rewards=0,
|
|
95
|
+
correct=0,
|
|
96
|
+
incorrect=0,
|
|
97
|
+
timeout=0,
|
|
98
|
+
)
|
|
99
|
+
_presistent_data[self._animal_state.name] = self._presistent_data
|
|
100
|
+
|
|
101
|
+
self._task = GNGSiDDiscriminateScene(
|
|
102
|
+
theater,
|
|
103
|
+
session_state.session_config,
|
|
104
|
+
animal_state,
|
|
105
|
+
session_state.session_config.screen_type,
|
|
106
|
+
_config,
|
|
107
|
+
self._presistent_data,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
self._data_logger = DataLogger(
|
|
111
|
+
self._session_state,
|
|
112
|
+
self._animal_state.name,
|
|
113
|
+
self.STAGE_NAME,
|
|
114
|
+
DataLoggerType.JSONL,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def start(self) -> "Feedback":
|
|
118
|
+
trial_data = self._task.start()
|
|
119
|
+
self._data_logger.save(trial_data.model_dump())
|
|
120
|
+
|
|
121
|
+
feedback = self._handle_result(trial_data.result)
|
|
122
|
+
logger.debug(
|
|
123
|
+
f"{self.STAGE_NAME}: "
|
|
124
|
+
f"session_id={self._session_state.session_id}, "
|
|
125
|
+
f"animal_name={self._animal_state.name}, "
|
|
126
|
+
f"animal_level={self._animal_state.level}, "
|
|
127
|
+
f"state_name={self.STAGE_NAME}, "
|
|
128
|
+
f"result={trial_data}, "
|
|
129
|
+
f"feedback={feedback}"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return feedback
|
|
133
|
+
|
|
134
|
+
def _load_stage_config(self, monkey: str) -> DiscriminateStageConfig:
|
|
135
|
+
stage_config = config.root.get(monkey) or config.root.get("default")
|
|
136
|
+
if stage_config is None:
|
|
137
|
+
raise ValueError("No default stage config found")
|
|
138
|
+
return stage_config
|
|
139
|
+
|
|
140
|
+
def _handle_result(self, result: "Result") -> "Feedback":
|
|
141
|
+
feedback = False
|
|
142
|
+
match result:
|
|
143
|
+
case Result.CORRECT:
|
|
144
|
+
_presistent_data[self._animal_state.name].correct += 1
|
|
145
|
+
feedback = True
|
|
146
|
+
case Result.INCORRECT:
|
|
147
|
+
_presistent_data[self._animal_state.name].incorrect += 1
|
|
148
|
+
feedback = False
|
|
149
|
+
case Result.TIMEOUT:
|
|
150
|
+
_presistent_data[self._animal_state.name].timeout += 1
|
|
151
|
+
feedback = False
|
|
152
|
+
case Result.CANCEL:
|
|
153
|
+
feedback = False
|
|
154
|
+
|
|
155
|
+
return feedback
|
|
156
|
+
|
|
157
|
+
def quit(self) -> None:
|
|
158
|
+
self._task.cancle()
|
|
159
|
+
|
|
160
|
+
def on_idle(self) -> None:
|
|
161
|
+
self._task.cancle()
|
|
162
|
+
|
|
163
|
+
def on_return(self) -> None:
|
|
164
|
+
self._task.cancle()
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def condition(self) -> "ScheduleCondition | None":
|
|
168
|
+
return self._stage_config.condition
|
|
169
|
+
|
|
170
|
+
def _prepare_stimulus_intensity(self, monkey: str, frequency: int):
|
|
171
|
+
stimulus_intensity = choice([55, 60, 65, 70, 75])
|
|
172
|
+
|
|
173
|
+
return self._theater.acontroller.get_amp_value(frequency, stimulus_intensity)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
from mxbi.config import Configure
|
|
5
|
+
from mxbi.models.animal import ScheduleCondition
|
|
6
|
+
from mxbi.tasks.GNGSiD.models import LevelID, MonkeyName
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, RootModel
|
|
8
|
+
|
|
9
|
+
CONFIG_PATH: Path = Path(__file__).parent / "config.json"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StimulusConfig(BaseModel):
|
|
13
|
+
stimulus_freq_high: int
|
|
14
|
+
stimulus_freq_high_duration: int
|
|
15
|
+
stimulus_freq_low: int
|
|
16
|
+
stimulus_freq_low_duration: int
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DiscriminateStageParams(BaseModel):
|
|
20
|
+
model_config = ConfigDict(frozen=True)
|
|
21
|
+
|
|
22
|
+
# visual stimulation config
|
|
23
|
+
stimulation_size: int
|
|
24
|
+
visual_stimulus_delay: int
|
|
25
|
+
|
|
26
|
+
# dynamic reward config
|
|
27
|
+
medium_reward_duration: int
|
|
28
|
+
medium_reward_threshold: int
|
|
29
|
+
low_reward_duration: int
|
|
30
|
+
|
|
31
|
+
# audio stimulation config
|
|
32
|
+
min_stimulus_duration: int
|
|
33
|
+
max_stimulus_duration: int
|
|
34
|
+
stimulus_configs: List[StimulusConfig]
|
|
35
|
+
stimulus_interval: int
|
|
36
|
+
extra_response_time: int
|
|
37
|
+
|
|
38
|
+
# trial lifecycle
|
|
39
|
+
time_out: int
|
|
40
|
+
inter_trial_interval: int
|
|
41
|
+
|
|
42
|
+
# reward config
|
|
43
|
+
reward_duration: int
|
|
44
|
+
reward_delay: int
|
|
45
|
+
|
|
46
|
+
attention_duration: int
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class DiscriminateStageLeveledParams(BaseModel):
|
|
50
|
+
model_config = ConfigDict(frozen=True)
|
|
51
|
+
|
|
52
|
+
level: int
|
|
53
|
+
|
|
54
|
+
stimulus_trial_prob: float
|
|
55
|
+
nostimulus_trial_prob: float
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DiscriminateStageConfig(BaseModel):
|
|
59
|
+
model_config = ConfigDict(frozen=True)
|
|
60
|
+
|
|
61
|
+
condition: ScheduleCondition
|
|
62
|
+
|
|
63
|
+
params: DiscriminateStageParams
|
|
64
|
+
levels_table: Dict[LevelID, DiscriminateStageLeveledParams]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DiscriminateStageConfigs(RootModel):
|
|
68
|
+
model_config = ConfigDict(frozen=True)
|
|
69
|
+
|
|
70
|
+
root: Dict[MonkeyName, DiscriminateStageConfig]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def load_config() -> DiscriminateStageConfigs:
|
|
74
|
+
configs = Configure(CONFIG_PATH, DiscriminateStageConfigs).value
|
|
75
|
+
for config in configs.root.values():
|
|
76
|
+
config.condition.level_count = len(config.levels_table)
|
|
77
|
+
return configs
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
config = load_config()
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
"params": {
|
|
12
|
+
"stimulus_duration": 1000,
|
|
13
|
+
"stimulus_freq": 2000,
|
|
14
|
+
"stimulus_freq_duration": 100,
|
|
15
|
+
"stimulus_interval": 130,
|
|
16
|
+
"time_out": 10000,
|
|
17
|
+
"inter_trial_interval": 2000,
|
|
18
|
+
"reward_duration": 1000
|
|
19
|
+
},
|
|
20
|
+
"levels_table": {
|
|
21
|
+
"0": {
|
|
22
|
+
"level": 0,
|
|
23
|
+
"stimulation_size": 430,
|
|
24
|
+
"reward_delay": 0
|
|
25
|
+
},
|
|
26
|
+
"1": {
|
|
27
|
+
"level": 1,
|
|
28
|
+
"stimulation_size": 420,
|
|
29
|
+
"reward_delay": 0
|
|
30
|
+
},
|
|
31
|
+
"2": {
|
|
32
|
+
"level": 2,
|
|
33
|
+
"stimulation_size": 400,
|
|
34
|
+
"reward_delay": 100
|
|
35
|
+
},
|
|
36
|
+
"3": {
|
|
37
|
+
"level": 3,
|
|
38
|
+
"stimulation_size": 380,
|
|
39
|
+
"reward_delay": 200
|
|
40
|
+
},
|
|
41
|
+
"4": {
|
|
42
|
+
"level": 4,
|
|
43
|
+
"stimulation_size": 360,
|
|
44
|
+
"reward_delay": 300
|
|
45
|
+
},
|
|
46
|
+
"5": {
|
|
47
|
+
"level": 5,
|
|
48
|
+
"stimulation_size": 345,
|
|
49
|
+
"reward_delay": 400
|
|
50
|
+
},
|
|
51
|
+
"6": {
|
|
52
|
+
"level": 6,
|
|
53
|
+
"stimulation_size": 330,
|
|
54
|
+
"reward_delay": 500
|
|
55
|
+
},
|
|
56
|
+
"7": {
|
|
57
|
+
"level": 7,
|
|
58
|
+
"stimulation_size": 315,
|
|
59
|
+
"reward_delay": 600
|
|
60
|
+
},
|
|
61
|
+
"8": {
|
|
62
|
+
"level": 8,
|
|
63
|
+
"stimulation_size": 300,
|
|
64
|
+
"reward_delay": 700
|
|
65
|
+
},
|
|
66
|
+
"9": {
|
|
67
|
+
"level": 9,
|
|
68
|
+
"stimulation_size": 285,
|
|
69
|
+
"reward_delay": 800
|
|
70
|
+
},
|
|
71
|
+
"10": {
|
|
72
|
+
"level": 10,
|
|
73
|
+
"stimulation_size": 270,
|
|
74
|
+
"reward_delay": 900
|
|
75
|
+
},
|
|
76
|
+
"11": {
|
|
77
|
+
"level": 11,
|
|
78
|
+
"stimulation_size": 260,
|
|
79
|
+
"reward_delay": 1000
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from mxbi.config import Configure
|
|
4
|
+
from mxbi.models.animal import ScheduleCondition
|
|
5
|
+
from mxbi.tasks.GNGSiD.models import LevelID, MonkeyName
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, RootModel
|
|
7
|
+
|
|
8
|
+
CONFIG_PATH = Path(__file__).parent / "config.json"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SizeReductionStageParameters(BaseModel):
|
|
12
|
+
model_config = ConfigDict(frozen=True)
|
|
13
|
+
|
|
14
|
+
# audio stimulation config
|
|
15
|
+
stimulus_duration: int
|
|
16
|
+
stimulus_freq: int
|
|
17
|
+
stimulus_freq_duration: int
|
|
18
|
+
stimulus_interval: int
|
|
19
|
+
|
|
20
|
+
# trial lifecycle
|
|
21
|
+
time_out: int
|
|
22
|
+
inter_trial_interval: int
|
|
23
|
+
|
|
24
|
+
# reward config
|
|
25
|
+
reward_duration: int
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SizeReductionStageLeveledParameters(BaseModel):
|
|
29
|
+
model_config = ConfigDict(frozen=True)
|
|
30
|
+
|
|
31
|
+
level: int
|
|
32
|
+
stimulation_size: int
|
|
33
|
+
reward_delay: int
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SizeReductionStageConfig(BaseModel):
|
|
37
|
+
model_config = ConfigDict(frozen=True)
|
|
38
|
+
|
|
39
|
+
condition: ScheduleCondition
|
|
40
|
+
|
|
41
|
+
params: SizeReductionStageParameters
|
|
42
|
+
levels_table: dict[LevelID, SizeReductionStageLeveledParameters]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SizeReductionStageConfigs(RootModel):
|
|
46
|
+
model_config = ConfigDict(frozen=True)
|
|
47
|
+
|
|
48
|
+
root: dict[MonkeyName, SizeReductionStageConfig]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def load_config() -> SizeReductionStageConfigs:
|
|
52
|
+
configs = Configure(CONFIG_PATH, SizeReductionStageConfigs).value
|
|
53
|
+
for config in configs.root.values():
|
|
54
|
+
config.condition.level_count = len(config.levels_table)
|
|
55
|
+
return configs
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
config = load_config()
|