PsychoPy Integration Notes¶
Overview¶
Reprostim package can be used with PsychoPy for creating and running fMRI experiments.
The MRI emulator plugin for PsychoPy provides a way to simulate MRI scanner triggers and
responses, allowing researchers to test and develop their experiments without needing access
to an actual MRI scanner.
For optimal experiment synchronization and data provenance, QR codes should be displayed at specific time points during your fMRI experiments:
At the start of the fMRI session ( marking the beginning of the entire scanning session with
EventType.SESSION_START)Right after receiving the initial MRI trigger for the sequence ( marking the start of data acquisition with
EventType.SERIES_START)Right after receiving each subsequent MRI trigger ( marking each volume acquisition with
EventType.MRI_TRIGGER_RECEIVED)At the end of each functional run (marking the completion of the scan with
EventType.SERIES_END)At the end of the fMRI session (marking the end of the entire scanning session with
EventType.SESSION_END)
Audio cues should be embedded alongside QR codes when conducting
audio-visual experiments. This provides redundant temporal markers that can be
captured both visually (by video recording) and acoustically (by audio recording),
improving synchronization accuracy and providing fallback options if one modality
fails to capture the markers properly. To enable audio codes,
set audio_enabled=True in your QrConfig (see example 04_fmri_audiocode.py below).
Examples¶
We created a simple PsychoPy example script
01_fmri_interval.py that
demonstrates how to use reprostim package and inject simple session start QR code
into experiment presentation:
from psychopy import core, event, logging, visual
from reprostim.qr.psychopy import EventType, QrCode, QrStim
# Create a window
win = visual.Window([800, 600], units="pix", fullscr=False)
# Display a QR code indicating the start of the session
qr = QrStim(win, QrCode(EventType.SESSION_START))
qr.draw()
win.flip()
# wait for its duration
qr.wait()
win.flip()
core.wait(2)

Example 02_fmri_series.py demonstrates how to use QR code with custom configuration, where QR background is transparent, QR code color is blue and QR size is scaled to be 30% of the original one:
from psychopy import core, event, logging, visual
from reprostim.qr.psychopy import EventType, QrCode, QrConfig, QrStim
# Create a window
win = visual.Window([800, 600], units="pix", fullscr=False)
# Optionally specify a QR code config
qr_config = QrConfig(
scale=0.3,
duration=1.0,
fill_color="blue",
back_color="transparent",
)
# Display a QR code indicating the start of the session
qr = QrStim(win, QrCode(EventType.SESSION_START), qr_config)
qr.draw()
win.flip()
qr.wait()
win.flip()

In addition, 03_fmri_event.py demonstrates how to use QR code in response to the fMRI scanner trigger/pulse event:
from psychopy import core, event, logging, visual
from psychopy_mri_emulator import launchScan
from reprostim.qr.psychopy import EventType, QrCode, QrConfig, QrStim
def show_qr(event, win, fix, qr_config, **kwargs):
# draw a QR code with given parameters
qr = QrStim(win, QrCode(event, **kwargs), qr_config)
fix.draw()
qr.draw()
win.flip()
# wait for its duration
qr.wait()
# restore fixation cross
fix.draw()
win.flip()
# Create a window and fixation cross
win = visual.Window([800, 600], units="pix", fullscr=False)
fix = visual.TextStim(win, text="+", pos=(0, 0))
fix.draw()
win.flip()
# Custom QR code config
qr_config = QrConfig(
padding=30,
align="right-bottom",
)
# show QR code on pulse received
show_qr(EventType.MRI_TRIGGER_RECEIVED, win, fix, qr_config, seq=seq, keys=keys)

Audio codes functionality is disabled by default in PsychoPy QrStim API, but it can be optionally turned on like in the 04_fmri_audiocode.py example below:
from psychopy import core, event, logging, visual
from psychopy_mri_emulator import launchScan
from reprostim.qr.psychopy import EventType, QrCode, QrConfig, QrStim
from reprostim.audio.audiocodes import AudioCodec
def show_qr(event, win, fix, qr_config, **kwargs):
# draw a QR code with given parameters
qr = QrStim(win, QrCode(event, **kwargs), qr_config)
fix.draw()
qr.draw()
win.flip()
# wait for its duration
qr.wait()
# restore fixation cross
fix.draw()
win.flip()
# Create a window and fixation cross
win = visual.Window([800, 600], units="pix", fullscr=False)
fix = visual.TextStim(win, text="+", pos=(0, 0))
fix.draw()
win.flip()
# Custom QR code config with audio codes enabled
qr_config = QrConfig(
audio_enabled=True,
audio_codec=AudioCodec.NFE,
audio_volume=0.61,
audio_data_field="seq",
audio_sample_rate=44100,
scale=0.5,
padding=30,
align="right-bottom",
)
# show QR code on pulse received
show_qr(EventType.MRI_TRIGGER_RECEIVED, win, fix, qr_config, seq=seq, keys=keys)

Installation¶
On Linux used reprostim dev venv and hatch with PsychoPy v2024.2.5:
hatch run psychopy
To install MRI emulator plugin, you can install it via PsychoPy Coder GUI:
In PsychoPy Coder select “Tools” -> “Plugin/packages manager…” -> “MRI emulator” -> “Install” and then restart PsychoPy.
or install it manually:
pip install psychopy-mri-emulator
Appendix¶
A: Environments tested¶
Env[1]¶
Ubuntu 24.04.2 LTS PsychoPy v2024.2.4 Problem:
Welcome to PsychoPy3!
v2024.2.4
## Running: /home/vmelnik/Projects/Dartmouth/branches/reprostim/tools/psychopy/01_fmri_interval.py ##
pygame 2.6.1 (SDL 2.28.4, Python 3.10.18)
Hello from the pygame community. https://www.pygame.org/contribute.html
Traceback (most recent call last):
9.0616 WARNING Monitor specification not found. Creating a temporary one...
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/tools/psychopy/01_fmri_interval.py", line 7, in <module>
win = visual.Window([800, 600], fullscr=False)
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/venv/lib/python3.10/site-packages/psychopy/visual/window.py", line 480, in __init__
self.backend = backends.getBackend(win=self, backendConf=backendConf)
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/venv/lib/python3.10/site-packages/psychopy/visual/backends/__init__.py", line 48, in getBackend
Backend = plugins.resolveObjectFromName(useBackend, __name__)
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/venv/lib/python3.10/site-packages/psychopy/plugins/__init__.py", line 202, in resolveObjectFromName
importlib.import_module(path)
File "/usr/lib/python3.10/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 883, in exec_module
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/venv/lib/python3.10/site-packages/psychopy/visual/backends/pygletbackend.py", line 44, in <module>
if pyglet.version < '1.4':
AttributeError: module 'pyglet' has no attribute 'version'
Exception ignored in: <function Window.__del__ at 0x779800419120>
Traceback (most recent call last):
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/venv/lib/python3.10/site-packages/psychopy/visual/window.py", line 665, in __del__
self.close()
File "/home/vmelnik/Projects/Dartmouth/branches/reprostim/venv/lib/python3.10/site-packages/psychopy/visual/window.py", line 2657, in close
self.backend.close() # moved here, dereferencing the window prevents
AttributeError: 'NoneType' object has no attribute 'close'
################# Experiment ended with exit code 1 [pid:6335] #################
Env[2]¶
Ubuntu 24.04.2 LTS
PsychoPy v2024.2.5
Works fine under hatch run psychopy
Env[3]¶
MacOS 12.7.6 PsychoPy v2024.2.5
In case of standalone PsychoPy e.g. on MacOS, you can install the package via
OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES open -a PsychoPy
Open PsychoPy coder.
Go to the Tools menu → Plugin/packages manager…
In the dialog, type: psychopy-mri-emulator
Failed with error:
Welcome to PsychoPy3!
v2024.2.5
87.6741 INFO Investigating repo at /Users/vmelnik/Projects/Dartmouth/branches/reprostim
87.7475 WARNING We found a repository at /Users/vmelnik/Projects/Dartmouth/branches/reprostim but it doesn't point to gitlab.pavlovia.org. You could create that as a remote to sync from PsychoPy.
## Running: /Users/vmelnik/Projects/Dartmouth/branches/reprostim/tools/psychopy/01_fmri_interval.py ##
2025-09-04 17:02:35.163 python[56653:2052484] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'nextEventMatchingMask should only be called from the Main Thread!'
*** First throw call stack:
(
0 CoreFoundation 0x00007ff814afa6e3 __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007ff81485a8bb objc_exception_throw + 48
2 AppKit 0x00007ff8174bae0d -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 4633
3 libpython3.10.dylib 0x00000001051c6782 ffi_call_unix64 + 82
4 ??? 0x000070000a15b010 0x0 + 123145471504400
)
libc++abi: terminating with uncaught exception of type NSException
################ Experiment ended with exit code -6 [pid:56653] ################