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,149 @@
|
|
|
1
|
+
from random import choice
|
|
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.size_reduction_stage.size_reduction_models import (
|
|
8
|
+
SizeReductionStageConfig,
|
|
9
|
+
config,
|
|
10
|
+
)
|
|
11
|
+
from mxbi.tasks.GNGSiD.tasks.touch.touch_models import TrialConfig
|
|
12
|
+
from mxbi.tasks.GNGSiD.tasks.touch.touch_scene import GNGSiDTouchScene
|
|
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 SizeReductionStage:
|
|
26
|
+
STAGE_NAME: Final[str] = "GNGSiD_SIZE_REDUCTION_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
|
+
master_amp, digital_amp = self._prepare_stimulus_intensity(
|
|
44
|
+
self._animal_state.name, _fixed_config.stimulus_freq
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
_config = TrialConfig(
|
|
48
|
+
level=_levels_config.level,
|
|
49
|
+
stimulation_size=_levels_config.stimulation_size,
|
|
50
|
+
stimulus_duration=_fixed_config.stimulus_duration,
|
|
51
|
+
time_out=_fixed_config.time_out,
|
|
52
|
+
inter_trial_interval=_fixed_config.inter_trial_interval,
|
|
53
|
+
reward_duration=_fixed_config.reward_duration,
|
|
54
|
+
reward_delay=_levels_config.reward_delay,
|
|
55
|
+
stimulus_freq=_fixed_config.stimulus_freq,
|
|
56
|
+
stimulus_freq_duration=_fixed_config.stimulus_freq_duration,
|
|
57
|
+
stimulus_freq_master_amp=master_amp,
|
|
58
|
+
stimulus_freq_digital_amp=digital_amp,
|
|
59
|
+
stimulus_interval=_fixed_config.stimulus_interval,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
self._data_logger = DataLogger(
|
|
63
|
+
self._session_state,
|
|
64
|
+
self._animal_state.name,
|
|
65
|
+
self.STAGE_NAME,
|
|
66
|
+
DataLoggerType.JSONL,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
self._presistent_data = _presistent_data.get(self._animal_state.name)
|
|
70
|
+
|
|
71
|
+
if self._presistent_data is None:
|
|
72
|
+
self._presistent_data = PersistentData(
|
|
73
|
+
rewards=0,
|
|
74
|
+
correct=0,
|
|
75
|
+
incorrect=0,
|
|
76
|
+
timeout=0,
|
|
77
|
+
)
|
|
78
|
+
_presistent_data[self._animal_state.name] = self._presistent_data
|
|
79
|
+
|
|
80
|
+
self._task = GNGSiDTouchScene(
|
|
81
|
+
theater,
|
|
82
|
+
session_state.session_config,
|
|
83
|
+
animal_state,
|
|
84
|
+
session_state.session_config.screen_type,
|
|
85
|
+
_config,
|
|
86
|
+
self._presistent_data,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def start(self) -> "Feedback":
|
|
90
|
+
trial_data = self._task.start()
|
|
91
|
+
self._data_logger.save(trial_data.model_dump())
|
|
92
|
+
|
|
93
|
+
feedback = self._handle_result(trial_data.result)
|
|
94
|
+
logger.debug(
|
|
95
|
+
f"{self.STAGE_NAME}: "
|
|
96
|
+
f"session_id={self._session_state.session_id}, "
|
|
97
|
+
f"animal_name={self._animal_state.name}, "
|
|
98
|
+
f"animal_level={self._animal_state.level}, "
|
|
99
|
+
f"state_name={self.STAGE_NAME}, "
|
|
100
|
+
f"result={trial_data}, "
|
|
101
|
+
f"feedback={feedback}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return feedback
|
|
105
|
+
|
|
106
|
+
def _load_stage_config(self, monkey: str) -> SizeReductionStageConfig:
|
|
107
|
+
stage_config = config.root.get(monkey) or config.root.get("default")
|
|
108
|
+
if stage_config is None:
|
|
109
|
+
raise ValueError("No default stage config found")
|
|
110
|
+
return stage_config
|
|
111
|
+
|
|
112
|
+
def _handle_result(self, result: "Result") -> "Feedback":
|
|
113
|
+
feedback = False
|
|
114
|
+
match result:
|
|
115
|
+
case Result.CORRECT:
|
|
116
|
+
_presistent_data[self._animal_state.name].correct += 1
|
|
117
|
+
feedback = True
|
|
118
|
+
case Result.INCORRECT:
|
|
119
|
+
_presistent_data[self._animal_state.name].incorrect += 1
|
|
120
|
+
feedback = False
|
|
121
|
+
case Result.TIMEOUT:
|
|
122
|
+
_presistent_data[self._animal_state.name].timeout += 1
|
|
123
|
+
feedback = False
|
|
124
|
+
case Result.CANCEL:
|
|
125
|
+
feedback = False
|
|
126
|
+
|
|
127
|
+
return feedback
|
|
128
|
+
|
|
129
|
+
def quit(self) -> None:
|
|
130
|
+
self._task.cancle()
|
|
131
|
+
|
|
132
|
+
def on_idle(self) -> None:
|
|
133
|
+
self._task.cancle()
|
|
134
|
+
|
|
135
|
+
def on_return(self) -> None:
|
|
136
|
+
self._task.cancle()
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def condition(self) -> "ScheduleCondition | None":
|
|
140
|
+
return self._stage_config.condition
|
|
141
|
+
|
|
142
|
+
def _prepare_stimulus_intensity(self, monkey: str, frequency: int):
|
|
143
|
+
bt = choice(([[10, 30], [50, 70]])) if monkey == "wolfgang" else []
|
|
144
|
+
at = [80, 80, 80] if monkey == "wolfgang" else [80, 80, 80]
|
|
145
|
+
intensity_options = at * 10 + bt
|
|
146
|
+
|
|
147
|
+
stimulus_intensity = choice(intensity_options)
|
|
148
|
+
|
|
149
|
+
return self._theater.acontroller.get_amp_value(frequency, stimulus_intensity)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from tkinter import Canvas
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BaseViews:
|
|
5
|
+
def _create_background(self, master, width, height) -> None:
|
|
6
|
+
self._backgroud = Canvas(
|
|
7
|
+
master,
|
|
8
|
+
bg="black",
|
|
9
|
+
width=width,
|
|
10
|
+
height=height,
|
|
11
|
+
highlightthickness=0,
|
|
12
|
+
)
|
|
13
|
+
self._backgroud.place(relx=0.5, rely=0.5, anchor="center")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from mxbi.tasks.GNGSiD.models import BaseDataToShow, BaseTrialConfig, BaseTrialData
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class TrialConfig(BaseTrialConfig):
|
|
5
|
+
go: bool
|
|
6
|
+
visual_stimulus_delay: int
|
|
7
|
+
|
|
8
|
+
stimulus_freq: int
|
|
9
|
+
stimulus_freq_duration: int
|
|
10
|
+
stimulus_freq_master_amp: int
|
|
11
|
+
stimulus_freq_digital_amp: int
|
|
12
|
+
|
|
13
|
+
stimulus_interval: int
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TrialData(BaseTrialData):
|
|
17
|
+
trial_config: TrialConfig
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DataToShow(BaseDataToShow):
|
|
21
|
+
stimulus: bool
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from math import ceil
|
|
3
|
+
from tkinter import CENTER, Canvas, Event
|
|
4
|
+
from typing import TYPE_CHECKING, Final
|
|
5
|
+
|
|
6
|
+
from mxbi.tasks.GNGSiD.models import Result, TouchEvent
|
|
7
|
+
from mxbi.tasks.GNGSiD.tasks.detect.models import DataToShow, TrialConfig, TrialData
|
|
8
|
+
from mxbi.tasks.GNGSiD.tasks.utils.targets import DetectTarget
|
|
9
|
+
from mxbi.utils.aplayer import ToneConfig
|
|
10
|
+
from mxbi.utils.tkinter.components.canvas_with_border import CanvasWithInnerBorder
|
|
11
|
+
from mxbi.utils.tkinter.components.showdata_widget import ShowDataWidget
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from concurrent.futures import Future
|
|
15
|
+
|
|
16
|
+
from mxbi.models.animal import AnimalState
|
|
17
|
+
from mxbi.models.session import ScreenConfig, SessionConfig
|
|
18
|
+
from mxbi.tasks.GNGSiD.models import PersistentData
|
|
19
|
+
from mxbi.theater import Theater
|
|
20
|
+
from numpy import int16
|
|
21
|
+
from numpy.typing import NDArray
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GNGSiDDetectScene:
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
theater: "Theater",
|
|
28
|
+
session_config: "SessionConfig",
|
|
29
|
+
animal_state: "AnimalState",
|
|
30
|
+
screen_type: "ScreenConfig",
|
|
31
|
+
trial_config: "TrialConfig",
|
|
32
|
+
persistent_data: "PersistentData",
|
|
33
|
+
) -> None:
|
|
34
|
+
self._theater: "Final[Theater]" = theater
|
|
35
|
+
self._animal_state: "Final[AnimalState]" = animal_state
|
|
36
|
+
self._screen_type: "Final[ScreenConfig]" = screen_type
|
|
37
|
+
self._trial_config: "Final[TrialConfig]" = trial_config
|
|
38
|
+
self._persistent_data: Final["PersistentData"] = persistent_data
|
|
39
|
+
|
|
40
|
+
self._tone: Final[NDArray[int16]] = self._prepare_stimulus()
|
|
41
|
+
self._standard_reward_stimulus = self._theater.new_standard_reward_stimulus(
|
|
42
|
+
self._trial_config.stimulus_duration
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
self._set_stimulus_intensity()
|
|
46
|
+
|
|
47
|
+
self._on_trial_start()
|
|
48
|
+
|
|
49
|
+
# region public api
|
|
50
|
+
def start(self) -> TrialData:
|
|
51
|
+
self._theater.mainloop()
|
|
52
|
+
return self._data
|
|
53
|
+
|
|
54
|
+
def cancle(self) -> None:
|
|
55
|
+
self._data.result = Result.CANCEL
|
|
56
|
+
self._theater.aplayer.stop()
|
|
57
|
+
self._on_trial_end()
|
|
58
|
+
|
|
59
|
+
# endregion
|
|
60
|
+
|
|
61
|
+
# region Lifecycle
|
|
62
|
+
def _on_trial_start(self) -> None:
|
|
63
|
+
self._create_view()
|
|
64
|
+
self._init_data()
|
|
65
|
+
self._bind_first_stage()
|
|
66
|
+
|
|
67
|
+
def _on_inter_trial(self) -> None:
|
|
68
|
+
self._background.after(
|
|
69
|
+
self._trial_config.inter_trial_interval, self._on_trial_end
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def _on_trial_end(self) -> None:
|
|
73
|
+
self._background.destroy()
|
|
74
|
+
self._theater.root.quit()
|
|
75
|
+
|
|
76
|
+
# endregion
|
|
77
|
+
|
|
78
|
+
# region Views
|
|
79
|
+
def _create_view(self) -> None:
|
|
80
|
+
self._create_background()
|
|
81
|
+
self._create_show_data_view()
|
|
82
|
+
self._create_target()
|
|
83
|
+
|
|
84
|
+
def _create_background(self) -> None:
|
|
85
|
+
self._background = CanvasWithInnerBorder(
|
|
86
|
+
master=self._theater.root,
|
|
87
|
+
bg="black",
|
|
88
|
+
width=self._screen_type.width,
|
|
89
|
+
height=self._screen_type.height,
|
|
90
|
+
border_width=40,
|
|
91
|
+
)
|
|
92
|
+
self._background.place(relx=0.5, rely=0.5, anchor="center")
|
|
93
|
+
|
|
94
|
+
def _create_show_data_view(self) -> None:
|
|
95
|
+
self._show_data_widget = ShowDataWidget(self._background)
|
|
96
|
+
self._show_data_widget.place(relx=0, rely=1, anchor="sw")
|
|
97
|
+
data = DataToShow(
|
|
98
|
+
name=self._animal_state.name,
|
|
99
|
+
id=self._animal_state.trial_id,
|
|
100
|
+
level_id=self._animal_state.current_level_trial_id,
|
|
101
|
+
level=self._trial_config.level,
|
|
102
|
+
rewards=self._persistent_data.rewards,
|
|
103
|
+
correct=self._animal_state.correct_trial,
|
|
104
|
+
incorrect=self._persistent_data.incorrect,
|
|
105
|
+
timeout=self._persistent_data.timeout,
|
|
106
|
+
stimulus=self._trial_config.go,
|
|
107
|
+
)
|
|
108
|
+
self._show_data_widget.show_data(data.model_dump())
|
|
109
|
+
|
|
110
|
+
def _create_target(self):
|
|
111
|
+
xshift = 240
|
|
112
|
+
xcenter = self._screen_type.width * 0.5 + xshift
|
|
113
|
+
ycenter = self._screen_type.height * 0.5
|
|
114
|
+
|
|
115
|
+
self._trigger_canvas = DetectTarget(
|
|
116
|
+
self._background, self._trial_config.stimulation_size
|
|
117
|
+
)
|
|
118
|
+
self._trigger_canvas.place(x=xcenter, y=ycenter, anchor="center")
|
|
119
|
+
|
|
120
|
+
def _create_wrong_view(self) -> None:
|
|
121
|
+
self._trigger_canvas = Canvas(
|
|
122
|
+
self._background,
|
|
123
|
+
bg="grey",
|
|
124
|
+
width=self._screen_type.width,
|
|
125
|
+
height=self._screen_type.height,
|
|
126
|
+
)
|
|
127
|
+
self._trigger_canvas.place(relx=0.5, rely=0.5, anchor=CENTER)
|
|
128
|
+
|
|
129
|
+
# endregion
|
|
130
|
+
|
|
131
|
+
# region event binding
|
|
132
|
+
def _bind_first_stage(self) -> None:
|
|
133
|
+
self._background.focus_set()
|
|
134
|
+
self._background.bind("<r>", lambda e: self._give_standard_stimulus())
|
|
135
|
+
self._trigger_canvas.bind("<ButtonPress>", self._on_first_touched)
|
|
136
|
+
self._trigger_canvas.after(self._trial_config.time_out, self._on_timeout)
|
|
137
|
+
|
|
138
|
+
def _bind_second_stage(self) -> None:
|
|
139
|
+
self._trigger_canvas.bind("<ButtonPress>", self._on_second_touched)
|
|
140
|
+
|
|
141
|
+
# TODO: Confirm the waiting time
|
|
142
|
+
if not self._trial_config.go:
|
|
143
|
+
self._trigger_canvas.after(
|
|
144
|
+
self._trial_config.stimulus_duration, self._on_incorrect
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# endregion
|
|
148
|
+
|
|
149
|
+
# region event handlers
|
|
150
|
+
def _on_first_touched(self, event: Event) -> None:
|
|
151
|
+
self._trigger_canvas.destroy()
|
|
152
|
+
self._record_touch(event)
|
|
153
|
+
|
|
154
|
+
if self._trial_config.go:
|
|
155
|
+
future = self._give_stimulus(self._tone)
|
|
156
|
+
future.add_done_callback(self._on_stimulus_complete)
|
|
157
|
+
|
|
158
|
+
self._background.after(
|
|
159
|
+
self._trial_config.visual_stimulus_delay,
|
|
160
|
+
lambda: (self._create_target(), self._bind_second_stage()),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _on_second_touched(self, event: Event) -> None:
|
|
164
|
+
self._trigger_canvas.destroy()
|
|
165
|
+
self._record_touch(event)
|
|
166
|
+
|
|
167
|
+
if self._trial_config.go:
|
|
168
|
+
self._on_incorrect()
|
|
169
|
+
else:
|
|
170
|
+
future = self._give_stimulus(self._tone)
|
|
171
|
+
future.add_done_callback(self._on_stimulus_complete)
|
|
172
|
+
|
|
173
|
+
self._background.after(2000, self._create_target)
|
|
174
|
+
|
|
175
|
+
def _record_touch(self, event: Event) -> None:
|
|
176
|
+
self._data.touch_events.append(
|
|
177
|
+
TouchEvent(time=datetime.now().timestamp(), x=event.x, y=event.y)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# endregion
|
|
181
|
+
|
|
182
|
+
# region result handlers
|
|
183
|
+
def _on_correct(self) -> None:
|
|
184
|
+
self._trigger_canvas.destroy()
|
|
185
|
+
|
|
186
|
+
self._give_reward()
|
|
187
|
+
self._data.result = Result.CORRECT
|
|
188
|
+
self._data.correct_rate = (self._animal_state.correct_trial + 1) / (
|
|
189
|
+
self._animal_state.current_level_trial_id + 1
|
|
190
|
+
)
|
|
191
|
+
self._on_inter_trial()
|
|
192
|
+
|
|
193
|
+
def _on_incorrect(self) -> None:
|
|
194
|
+
self._theater._aplayer.stop()
|
|
195
|
+
self._trigger_canvas.destroy()
|
|
196
|
+
|
|
197
|
+
self._data.result = Result.INCORRECT
|
|
198
|
+
self._data.correct_rate = self._animal_state.correct_trial / (
|
|
199
|
+
self._animal_state.current_level_trial_id + 1
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
self._create_wrong_view()
|
|
203
|
+
self._on_inter_trial()
|
|
204
|
+
|
|
205
|
+
def _on_timeout(self) -> None:
|
|
206
|
+
self._data.result = Result.TIMEOUT
|
|
207
|
+
self._data.correct_rate = self._animal_state.correct_trial / (
|
|
208
|
+
self._animal_state.current_level_trial_id + 1
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
self._create_wrong_view()
|
|
212
|
+
self._on_inter_trial()
|
|
213
|
+
|
|
214
|
+
# endregion
|
|
215
|
+
|
|
216
|
+
# region stimulus and reward
|
|
217
|
+
def _prepare_stimulus(self) -> "NDArray[int16]":
|
|
218
|
+
cycle = (
|
|
219
|
+
self._trial_config.stimulus_freq_duration
|
|
220
|
+
+ self._trial_config.stimulus_interval
|
|
221
|
+
)
|
|
222
|
+
repeat = ceil(self._trial_config.stimulus_duration / cycle)
|
|
223
|
+
repeat = max(repeat, 1)
|
|
224
|
+
|
|
225
|
+
freq_1 = ToneConfig(
|
|
226
|
+
frequency=self._trial_config.stimulus_freq,
|
|
227
|
+
duration=self._trial_config.stimulus_freq_duration,
|
|
228
|
+
)
|
|
229
|
+
freq_2 = ToneConfig(frequency=0, duration=self._trial_config.stimulus_interval)
|
|
230
|
+
|
|
231
|
+
return self._theater.aplayer.generate_stimulus([freq_1, freq_2], repeat)
|
|
232
|
+
|
|
233
|
+
def _give_stimulus(self, tone: "NDArray[int16]") -> "Future[bool]":
|
|
234
|
+
return self._theater.aplayer.play_stimulus(tone)
|
|
235
|
+
|
|
236
|
+
def _on_stimulus_complete(self, future: "Future[bool]") -> None:
|
|
237
|
+
if future.result():
|
|
238
|
+
self._background.after(self._trial_config.reward_delay, self._on_correct)
|
|
239
|
+
|
|
240
|
+
def _give_reward(self, _=None) -> None:
|
|
241
|
+
self._persistent_data.rewards += 1
|
|
242
|
+
self._theater.reward.give_reward(self._trial_config.reward_duration)
|
|
243
|
+
|
|
244
|
+
def _set_stimulus_intensity(self) -> None:
|
|
245
|
+
self._theater.acontroller.set_master_volume(
|
|
246
|
+
self._trial_config.stimulus_freq_master_amp
|
|
247
|
+
)
|
|
248
|
+
self._theater.acontroller.set_digital_volume(
|
|
249
|
+
self._trial_config.stimulus_freq_digital_amp
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def _give_standard_stimulus(self) -> None:
|
|
253
|
+
self._standard_reward_stimulus.play(self._trial_config.reward_duration)
|
|
254
|
+
|
|
255
|
+
# endregion
|
|
256
|
+
|
|
257
|
+
# region data
|
|
258
|
+
def _init_data(self):
|
|
259
|
+
self._data = TrialData(
|
|
260
|
+
animal=self._animal_state.name,
|
|
261
|
+
trial_id=self._animal_state.trial_id,
|
|
262
|
+
current_level_trial_id=self._animal_state.current_level_trial_id,
|
|
263
|
+
trial_config=self._trial_config,
|
|
264
|
+
trial_start_time=datetime.now().timestamp(),
|
|
265
|
+
trial_end_time=0,
|
|
266
|
+
result=Result.TIMEOUT,
|
|
267
|
+
correct_rate=0,
|
|
268
|
+
touch_events=[],
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# endregion
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from mxbi.tasks.GNGSiD.models import BaseDataToShow, BaseTrialConfig, BaseTrialData
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class TrialConfig(BaseTrialConfig):
|
|
5
|
+
is_stimulus_trial: bool
|
|
6
|
+
visual_stimulus_delay: int
|
|
7
|
+
|
|
8
|
+
medium_reward_duration: int
|
|
9
|
+
medium_reward_threshold: int
|
|
10
|
+
low_reward_duration: int
|
|
11
|
+
|
|
12
|
+
attention_duration: int
|
|
13
|
+
extra_response_time: int
|
|
14
|
+
|
|
15
|
+
stimulus_freq_high: int
|
|
16
|
+
stimulus_freq_high_duration: int
|
|
17
|
+
stimulus_freq_high_master_amp: int
|
|
18
|
+
stimulus_freq_high_digital_amp: int
|
|
19
|
+
stimulus_freq_low: int
|
|
20
|
+
stimulus_freq_low_duration: int
|
|
21
|
+
stimulus_freq_low_master_amp: int
|
|
22
|
+
stimulus_freq_low_digital_amp: int
|
|
23
|
+
stimulus_interval: int
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TrialData(BaseTrialData):
|
|
27
|
+
trial_config: TrialConfig
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DataToShow(BaseDataToShow):
|
|
31
|
+
stimulus: bool
|