Use this Skill for PsychoPy experiment design: stimulus presentation, response collection, TTL synchronization, BIDS event files, and reaction time analysis.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Use this Skill for PsychoPy experiment design: stimulus presentation, response collection, TTL synchronization, BIDS event files, and reaction time analysis.
One-line summary: Design and run neuroscience experiments with PsychoPy: visual/auditory stimuli, TTL triggers for EEG/fMRI, BIDS event file generation, and RT analysis.
When to Use This Skill
When designing visual or auditory cognitive paradigms (oddball, N-back, Stroop, etc.)
When synchronizing stimulus delivery with EEG/fMRI acquisition via TTL pulses
When generating BIDS-compatible events.tsv files for neuroimaging data
When collecting keyboard/response-box reaction times with millisecond precision
When deploying experiments online via Pavlovia
When analyzing timing data from PsychoPy log files
Coder: Full Python scripting via psychopy.visual, psychopy.core, psychopy.event
For neuroscience experiments, Coder provides fine-grained timing control.
Timing Precision
Stimulus timing in PsychoPy can be specified in:
Frames: Most precise (e.g., 60 Hz monitor → 16.67 ms/frame)
Seconds: Convenient but limited by monitor refresh
Critical timing: win.flip() synchronizes to vertical retrace (VBL).
BIDS Events Format
BIDS (Brain Imaging Data Structure) events.tsv requires:
Column
Description
onset
Stimulus onset time relative to first acquisition (s)
duration
Stimulus duration (s)
trial_type
Condition label
response_time
Reaction time (s), NaN if no response
response
Key pressed or NaN
TTL Synchronization
Parallel port pulse (8-bit trigger code) sent to EEG amplifier:
Code 1-255 marks event type
Duration typically 5-10 ms
Environment Setup
Install Dependencies
# PsychoPy is best installed as standalone or via pip in a fresh environment
pip install psychopy>=2023.2 pandas>=2.0 numpy>=1.24 matplotlib>=3.7 scipy>=1.11
# For parallel port triggers (EEG)
pip install pyparallel # Linux# pip install pywin32 # Windows (parallel port)
Verify Installation
import psychopy
from psychopy import core, visual, event
print(f"PsychoPy version: {psychopy.__version__}")
# Do NOT open a window in headless environments
Core Workflow
Step 1: Experiment Design and Trial List
import numpy as np
import pandas as pd
from pathlib import Path
defcreate_oddball_trial_list(n_standards=120, n_oddballs=30, seed=42):
"""
Create a randomized auditory oddball trial list.
Standard tone: 1000 Hz (80% probability)
Oddball tone: 2000 Hz (20% probability)
Returns
-------
pd.DataFrame with columns: trial_num, trial_type, frequency_Hz, isi_s
"""
rng = np.random.default_rng(seed)
# Create trial types
trials = (["standard"] * n_standards + ["oddball"] * n_oddballs)
# Constraint: no more than 2 consecutive oddballs; at least 2 standards between oddballswhileTrue:
rng.shuffle(trials)
# Check constraint: no two consecutive oddballs
valid = Truefor i inrange(len(trials) - 1):
if trials[i] == "oddball"and trials[i+1] == "oddball":
valid = Falsebreakif valid:
break# Inter-stimulus intervals (jittered 0.8–1.2 s)
isi_list = rng.uniform(0.8, 1.2, len(trials))
trial_df = pd.DataFrame({
"trial_num": range(1, len(trials) + 1),
"trial_type": trials,
"frequency_Hz": [1000if t == "standard"else2000for t in trials],
"isi_s": isi_list,
"trigger_code": [10if t == "standard"else20for t in trials],
})
trial_df["onset_s"] = trial_df["isi_s"].cumsum().shift(1).fillna(0)
print(f"Trial list: {len(trial_df)} trials")
print(f" Standards: {(trial_df['trial_type']=='standard').sum()}")
print(f" Oddballs: {(trial_df['trial_type']=='oddball').sum()}")
print(f" Total duration: {trial_df['onset_s'].max():.0f}s ({trial_df['onset_s'].max()/60:.1f} min)")
# Save trial list
trial_df.to_csv("oddball_trials.csv", index=False)
return trial_df
trials = create_oddball_trial_list()
print("\nFirst 10 trials:")
print(trials.head(10).to_string(index=False))
Step 2: PsychoPy Experiment Script
"""
Auditory Oddball Experiment — PsychoPy Coder Mode
Run in a PsychoPy environment (not headless CI).
Replace 'SIMULATE = True' with 'SIMULATE = False' for real experiments.
"""
SIMULATE = True# Set False for actual experimentimport numpy as np
import pandas as pd
from pathlib import Path
ifnot SIMULATE:
from psychopy import core, visual, sound, event, prefs
prefs.hardware["audioLib"] = ["ptb", "sounddevice"]
defrun_oddball_experiment(trial_df, participant="P01", session=1, simulate=True):
"""
Run auditory oddball experiment.
Parameters
----------
trial_df : pd.DataFrame
Trial list from create_oddball_trial_list()
participant : str
session : int
simulate : bool
If True, simulate timing without opening windows
"""if simulate:
print("SIMULATION MODE: No window will open")
results = []
for _, trial in trial_df.iterrows():
rt = np.random.exponential(0.35) + 0.15if np.random.rand() < 0.90else np.nan
results.append({
"trial_num": trial["trial_num"],
"trial_type": trial["trial_type"],
"frequency_Hz": trial["frequency_Hz"],
"onset_s": trial["onset_s"],
"response_time": rt,
"response": "space"ifnot np.isnan(rt) else"none",
"correct": not np.isnan(rt) if trial["trial_type"] == "oddball"else np.isnan(rt),
})
results_df = pd.DataFrame(results)
save_results(results_df, participant, session)
return results_df
else:
# Real experiment codefrom psychopy import core, visual, sound, event
win = visual.Window([1280, 720], fullscr=True, units="norm")
fixation = visual.TextStim(win, text="+", height=0.1)
clock = core.Clock()
results = []
for _, trial in trial_df.iterrows():
# Present fixation
fixation.draw()
win.flip()
# Play tone (duration 100ms)
tone = sound.Sound(trial["frequency_Hz"], secs=0.1, stereo=True)
tone.play()
onset_time = clock.getTime()
# Send TTL trigger# parallel_port.setData(trial["trigger_code"])# core.wait(0.005)# parallel_port.setData(0)# Collect response
event.clearEvents()
response, rt = None, np.nan
core.wait(trial["isi_s"] - 0.1)
keys = event.getKeys(keyList=["space", "escape"], timeStamped=clock)
if keys:
key, key_time = keys[0]
if key == "escape":
break
response = key
rt = key_time - onset_time
results.append({
"trial_num": trial["trial_num"],
"trial_type": trial["trial_type"],
"onset_s": onset_time,
"response_time": rt,
"response": response or"none",
})
win.close()
results_df = pd.DataFrame(results)
save_results(results_df, participant, session)
return results_df
defsave_results(results_df, participant, session):
"""Save results in BIDS-compatible events.tsv format."""
output_dir = Path(f"sub-{participant}/ses-{session:02d}/beh")
output_dir.mkdir(parents=True, exist_ok=True)
# BIDS events file
bids_events = results_df.rename(columns={
"onset_s": "onset",
"response_time": "response_time",
})
bids_events["duration"] = 0.1# stimulus duration
bids_events["trial_type"] = bids_events["trial_type"]
bids_cols = ["onset", "duration", "trial_type", "response_time", "response"]
bids_events[bids_cols].to_csv(
output_dir / f"sub-{participant}_ses-{session:02d}_task-oddball_events.tsv",
sep="\t", index=False, float_format="%.4f"
)
print(f"Saved BIDS events to {output_dir}")
results = run_oddball_experiment(trials, simulate=SIMULATE)
print(f"\nHit rate (oddball): {results[results['trial_type']=='oddball']['response_time'].notna().mean():.2%}")
print(f"FA rate (standard): {results[results['trial_type']=='standard']['response_time'].notna().mean():.2%}")
# Pavlovia deployment checklist (not executable — reference guide)
checklist = {
"1. Python → JavaScript": "PsychoPy Builder auto-converts; avoid raw Python in Coder",
"2. Stimuli files": "Upload to Pavlovia GitLab repo; use relative paths",
"3. Timing accuracy": "Online timing ±10ms (vs. ±1ms local) — use frames not seconds",
"4. Response keys": "Browser keyboard events; test all target keys",
"5. Data download": "Pavlovia auto-saves CSV to project; download via GUI or API",
"6. Ethical compliance": "Add consent form as first component; store no PII",
}
for step, note in checklist.items():
print(f"{step}: {note}")
Troubleshooting
Error: psychopy.visual.Window fails to open (in CI/headless)
from psychopy import core
# Use globalClock for all timing
global_clock = core.Clock()
# Reset at experiment start, use global_clock.getTime() for all onsets
Version Compatibility
Package
Tested versions
Known issues
psychopy
2023.2, 2024.1
Sound backend varies by OS; test ptb > sounddevice > pygame