|
19 | 19 | core.checkPygletDuringWait = False |
20 | 20 |
|
21 | 21 | from psychopy import visual |
22 | | -from psychopy.hardware import keyboard |
23 | 22 | from psyexp_core import rundir, screen |
24 | 23 | from psyexp_core.keyboard import ( |
25 | 24 | KEYBOARD_BACKEND, |
|
33 | 32 | from mid_det.io import recording |
34 | 33 | from mid_det.ratings import core as rcore |
35 | 34 | from mid_det.ratings import display as rdisplay |
| 35 | +from mid_det.ratings import flow |
36 | 36 | from mid_det.ratings.setup_wizard import run_ratings_wizard |
37 | 37 |
|
38 | 38 | _PACKAGE_DIR = Path(__file__).resolve().parent # src/mid_det/ratings/ |
39 | 39 | _PROJECT_ROOT = _PACKAGE_DIR.parent.parent.parent # project root |
40 | 40 | _TEXT_DIR = _PROJECT_ROOT / "text" |
41 | 41 |
|
42 | | -# Slider control keys (MATLAB parity): 1 = left, 2 = right, 3 = select/advance. |
43 | | -_KEY_LEFT = "1" |
44 | | -_KEY_RIGHT = "2" |
45 | | -_KEY_SELECT = "3" |
46 | | -_ADVANCE_KEYS = [_KEY_LEFT, _KEY_RIGHT, _KEY_SELECT] |
47 | | -# Instruction/text pages advance on button 1 only (forward-only, no going back). |
48 | | -_PAGE_ADVANCE_KEYS = [_KEY_LEFT] |
49 | | - |
50 | | - |
51 | | -def _load_instruction_pages() -> list[str]: |
52 | | - path = _TEXT_DIR / "instructions_ratings.txt" |
53 | | - pages: list[str] = [] |
54 | | - with open(path) as f: |
55 | | - for line in f: |
56 | | - stripped = line.rstrip() |
57 | | - if stripped: |
58 | | - pages.append(stripped) |
59 | | - return pages |
60 | | - |
61 | | - |
62 | | -def _show_text_page( |
63 | | - win: visual.Window, |
64 | | - kb: keyboard.Keyboard | None, |
65 | | - text_stim: visual.TextStim, |
66 | | - hint_stim: visual.TextStim, |
67 | | - text: str, |
68 | | -) -> None: |
69 | | - text_stim.text = text |
70 | | - text_stim.draw() |
71 | | - hint_stim.draw() |
72 | | - win.flip() |
73 | | - wait_for_key(kb, _PAGE_ADVANCE_KEYS, quit_keys=config.QUIT_KEYS) |
74 | | - |
75 | | - |
76 | | -def _run_slider( |
77 | | - win: visual.Window, |
78 | | - kb: keyboard.Keyboard | None, |
79 | | - stim: rdisplay.RatingStimuli, |
80 | | - scale: str, |
81 | | - cue: rcore.RatingCue | None, |
82 | | -) -> int: |
83 | | - """Run one slider interaction; return the selected position (1..N_ELS).""" |
84 | | - pos = rcore.START_SLIDEPOS[scale] |
85 | | - rdisplay.draw_scale(stim, scale, pos, cue) |
86 | | - win.flip() |
87 | | - while True: |
88 | | - key = wait_for_key(kb, _ADVANCE_KEYS, quit_keys=config.QUIT_KEYS) |
89 | | - if key == _KEY_LEFT: |
90 | | - pos = rcore.clamp_slider(pos, -1) |
91 | | - elif key == _KEY_RIGHT: |
92 | | - pos = rcore.clamp_slider(pos, +1) |
93 | | - elif key == _KEY_SELECT: |
94 | | - return pos |
95 | | - rdisplay.draw_scale(stim, scale, pos, cue) |
96 | | - win.flip() |
97 | | - |
98 | | - |
99 | | -def _show_fixation(win: visual.Window, stim: rdisplay.RatingStimuli) -> None: |
100 | | - rdisplay.draw_fixation(stim) |
101 | | - win.flip() |
102 | | - core.wait(0.5) |
103 | | - |
104 | 42 |
|
105 | 43 | def run() -> None: |
106 | 44 | # Select the keyboard backend before any Keyboard is built. |
@@ -159,32 +97,32 @@ def run() -> None: |
159 | 97 | pos=(0, -0.38), height=1.0 / 28, color="white", autoLog=False, |
160 | 98 | ) |
161 | 99 |
|
162 | | - pages = _load_instruction_pages() |
| 100 | + pages = flow.load_instruction_pages(_TEXT_DIR) |
163 | 101 | # pages: 0=intro, 1=valence, 2=arousal, 3=independence, 4=final |
164 | 102 |
|
165 | 103 | # ── INSTRUCTIONS + PRACTICE DEMOS ──────────────────────────────────────── |
166 | 104 | if show_instructions: |
167 | | - _show_text_page(win, kb, instr_text, instr_hint, pages[0]) |
168 | | - _show_text_page(win, kb, instr_text, instr_hint, pages[1]) |
169 | | - _run_slider(win, kb, stim, "valence", cue=None) # valence practice demo |
170 | | - _show_text_page(win, kb, instr_text, instr_hint, pages[2]) |
171 | | - _run_slider(win, kb, stim, "arousal", cue=None) # arousal practice demo |
172 | | - _show_text_page(win, kb, instr_text, instr_hint, pages[3]) |
| 105 | + flow.show_text_page(win, kb, instr_text, instr_hint, pages[0]) |
| 106 | + flow.show_text_page(win, kb, instr_text, instr_hint, pages[1]) |
| 107 | + flow.run_slider(win, kb, stim, "valence", cue=None) # valence practice demo |
| 108 | + flow.show_text_page(win, kb, instr_text, instr_hint, pages[2]) |
| 109 | + flow.run_slider(win, kb, stim, "arousal", cue=None) # arousal practice demo |
| 110 | + flow.show_text_page(win, kb, instr_text, instr_hint, pages[3]) |
173 | 111 |
|
174 | 112 | # Final "press 3 to select" page is always shown (MATLAB inst5). |
175 | | - _show_text_page(win, kb, instr_text, instr_hint, pages[4]) |
| 113 | + flow.show_text_page(win, kb, instr_text, instr_hint, pages[4]) |
176 | 114 |
|
177 | 115 | # ── RATING TRIALS ──────────────────────────────────────────────────────── |
178 | | - _show_fixation(win, stim) |
| 116 | + flow.show_fixation(win, stim) |
179 | 117 | results: list[dict] = [] |
180 | 118 | for cue in rcore.RATING_CUES: |
181 | | - valence = _run_slider(win, kb, stim, "valence", cue) |
182 | | - arousal = _run_slider(win, kb, stim, "arousal", cue) |
| 119 | + valence = flow.run_slider(win, kb, stim, "valence", cue) |
| 120 | + arousal = flow.run_slider(win, kb, stim, "arousal", cue) |
183 | 121 | results.append({ |
184 | 122 | "polarity": cue.polarity, "magnitude": cue.magnitude, |
185 | 123 | "valence": valence, "arousal": arousal, |
186 | 124 | }) |
187 | | - _show_fixation(win, stim) |
| 125 | + flow.show_fixation(win, stim) |
188 | 126 |
|
189 | 127 | # ── WRITE CSV ──────────────────────────────────────────────────────────── |
190 | 128 | # (manifest.json was already written to run_dir at startup) |
|
0 commit comments