$npx -y skills add neuromechanist/research-skills --skill experiment-designUse this skill for \"design experiment\", \"create PsychoPy experiment\", \"stimulus presentation\", \"experiment protocol\", \"timing validation\", \"trial structure\", \"block design\", \"event-related design\", \"PsychoPy builder\", \"create stimuli\", \"LSL markers\", \"Lab S
| 1 | # Experiment Design |
| 2 | |
| 3 | Design and implement neuroscience experiments with PsychoPy, including stimulus presentation, timing validation, event markers, and Lab Streaming Layer (LSL) integration. |
| 4 | |
| 5 | ## When to Use |
| 6 | |
| 7 | - Designing a new behavioral or neuroimaging experiment |
| 8 | - Creating PsychoPy scripts for stimulus presentation |
| 9 | - Setting up event markers via LSL or parallel port |
| 10 | - Validating timing accuracy |
| 11 | - Converting an experiment protocol to code |
| 12 | |
| 13 | ## Experiment Design Principles |
| 14 | |
| 15 | ### Trial Structure |
| 16 | |
| 17 | Every trial consists of: |
| 18 | |
| 19 | ``` |
| 20 | [Fixation] -> [Stimulus] -> [Response Window] -> [Inter-trial Interval] |
| 21 | | | | | |
| 22 | marker marker marker marker |
| 23 | ``` |
| 24 | |
| 25 | ### Design Types |
| 26 | |
| 27 | | Design | Best For | Example | |
| 28 | |--------|----------|---------| |
| 29 | | **Block** | fMRI, sustained attention | 30s blocks of condition A, B | |
| 30 | | **Event-related** | ERP/EEG, rapid events | Randomized single trials | |
| 31 | | **Mixed** | Both sustained and transient | Blocks with jittered events | |
| 32 | | **Resting state** | Baseline/connectivity | Eyes open/closed periods | |
| 33 | |
| 34 | ### Timing Considerations |
| 35 | |
| 36 | - **Frame-based timing** (preferred): Specify durations in frames, not seconds |
| 37 | - **Monitor refresh rate**: 60 Hz = 16.67 ms/frame; 120 Hz = 8.33 ms/frame |
| 38 | - **Stimulus onset**: Sync to vertical blank for precise timing |
| 39 | - **Jitter**: Add random ITI variation for event-related designs (avoid expectation effects) |
| 40 | - **Minimum stimulus duration**: 1 frame (16.67 ms at 60 Hz) |
| 41 | |
| 42 | ## PsychoPy Experiment Template |
| 43 | |
| 44 | ### Basic Structure |
| 45 | |
| 46 | ```python |
| 47 | from psychopy import visual, core, event, data, gui |
| 48 | import numpy as np |
| 49 | |
| 50 | # Experiment parameters |
| 51 | exp_info = { |
| 52 | "participant": "", |
| 53 | "session": "01", |
| 54 | "task": "experiment_name", |
| 55 | } |
| 56 | |
| 57 | # GUI dialog |
| 58 | dlg = gui.DlgFromDict(exp_info, title="Experiment") |
| 59 | if not dlg.OK: |
| 60 | core.quit() |
| 61 | |
| 62 | # Window setup |
| 63 | win = visual.Window( |
| 64 | size=[1920, 1080], |
| 65 | fullscr=True, |
| 66 | monitor="testMonitor", |
| 67 | units="deg", |
| 68 | color=[0, 0, 0], |
| 69 | ) |
| 70 | |
| 71 | # Stimuli |
| 72 | fixation = visual.TextStim(win, text="+", height=2) |
| 73 | stimulus = visual.ImageStim(win, image=None, size=[10, 10]) |
| 74 | feedback = visual.TextStim(win, text="", height=1.5) |
| 75 | |
| 76 | # Trial handler |
| 77 | conditions = data.importConditions("conditions.xlsx") |
| 78 | trials = data.TrialHandler( |
| 79 | conditions, |
| 80 | nReps=1, |
| 81 | method="random", |
| 82 | ) |
| 83 | |
| 84 | # Clock |
| 85 | clock = core.Clock() |
| 86 | |
| 87 | # Main experiment loop |
| 88 | for trial in trials: |
| 89 | # Fixation |
| 90 | fixation.draw() |
| 91 | win.flip() |
| 92 | core.wait(0.5) # 500 ms fixation |
| 93 | |
| 94 | # Stimulus |
| 95 | stimulus.image = trial["stimulus_file"] |
| 96 | stimulus.draw() |
| 97 | win.flip() |
| 98 | # Send marker here |
| 99 | |
| 100 | # Response |
| 101 | clock.reset() |
| 102 | keys = event.waitKeys( |
| 103 | maxWait=2.0, |
| 104 | keyList=["left", "right", "escape"], |
| 105 | timeStamped=clock, |
| 106 | ) |
| 107 | |
| 108 | if keys: |
| 109 | if keys[0][0] == "escape": |
| 110 | core.quit() |
| 111 | trials.addData("response", keys[0][0]) |
| 112 | trials.addData("rt", keys[0][1]) |
| 113 | |
| 114 | # ITI (jittered) |
| 115 | iti = np.random.uniform(0.8, 1.2) |
| 116 | core.wait(iti) |
| 117 | |
| 118 | # Save data |
| 119 | import os |
| 120 | os.makedirs("data", exist_ok=True) |
| 121 | trials.saveAsWideText(f"data/sub-{exp_info['participant']}_task-{exp_info['task']}.csv") |
| 122 | win.close() |
| 123 | core.quit() |
| 124 | ``` |
| 125 | |
| 126 | ### Conditions File Format |
| 127 | |
| 128 | ``` |
| 129 | # conditions.xlsx or conditions.csv |
| 130 | stimulus_file,condition,correct_response |
| 131 | stimuli/face01.png,face,left |
| 132 | stimuli/house01.png,house,right |
| 133 | stimuli/face02.png,face,left |
| 134 | ``` |
| 135 | |
| 136 | ## Lab Streaming Layer (LSL) Integration |
| 137 | |
| 138 | ### Sending Markers |
| 139 | |
| 140 | ```python |
| 141 | from pylsl import StreamInfo, StreamOutlet |
| 142 | |
| 143 | # Create marker stream |
| 144 | info = StreamInfo( |
| 145 | name="ExperimentMarkers", |
| 146 | type="Markers", |
| 147 | channel_count=1, |
| 148 | nominal_srate=0, # irregular rate |
| 149 | channel_format="string", |
| 150 | source_id="psychopy_markers", |
| 151 | ) |
| 152 | outlet = StreamOutlet(info) |
| 153 | |
| 154 | # Send marker at stimulus onset |
| 155 | stimulus.draw() |
| 156 | win.flip() |
| 157 | outlet.push_sample(["stimulus_onset"]) # Send immediately after flip |
| 158 | ``` |
| 159 | |
| 160 | ### Common Marker Scheme |
| 161 | |
| 162 | | Marker | Code | Description | |
| 163 | |--------|------|-------------| |
| 164 | | stimulus_onset | S1-S99 | Stimulus presentation | |
| 165 | | response | R1-R4 | Participant response | |
| 166 | | feedback | F1-F2 | Correct/incorrect feedback | |
| 167 | | block_start | B1-B10 | Block onset | |
| 168 | | block_end | BE | Block offset | |
| 169 | | trial_start | T | Trial onset | |
| 170 | | experiment_start | EXP_START | First trial | |
| 171 | | experiment_end | EXP_END | Last trial | |
| 172 | |
| 173 | ### HED Annotation for Markers |
| 174 | |
| 175 | Annotate events with Hierarchical Event Descriptors (HED) for standardized event description: |
| 176 | |
| 177 | ```tsv |
| 178 | onset duration trial_type value HED |
| 179 | 0.0 0.0 stimulus S1 Sensory-event, Visual-presenta |