Using Cedrus/Lumina devices in PsychoPy without the Cedrus plugin
Hey everyone,
If you’re using a Cedrus Lumina controller (or any XID-compatible device) for fMRI paradigms in PsychoPy, you might have noticed that the built-in Cedrus Builder component is broken in recent versions — which makes older experiments incompatible with newer PsychoPy releases.
I found a workaround that bypasses the plugin entirely and works reliably. Here’s the idea:
The fix: use pyxid2 directly via Code components
Instead of relying on the Cedrus Builder element, you talk to the device directly using pyxid2 (already bundled with PsychoPy). You add a Code component to your routines and paste the relevant snippets into the appropriate tabs (Before Experiment, Begin Routine, Each Frame).
Before Experiment — connect to the device once:
import pyxid2
devices = pyxid2.get_xid_devices()
if not devices:
raise RuntimeError("No Cedrus device found. Check USB/drivers.")
cedrus = devices[0]
cedrus.reset_timer()
cedrus.clear_response_queue()
Three ready-to-use examples:
1. Wait for any button press → save → end routine
# Begin Routine
cedrus.clear_response_queue()
cedrus_pressed = False
# Each Frame
cedrus.poll_for_response()
if not cedrus_pressed:
r = cedrus.get_next_response()
if r:
cedrus_pressed = True
thisExp.addData('cedrus.key', str(r.get('key', '')))
thisExp.addData('cedrus.time', r.get('time', None))
continueRoutine = False
2. Collect all presses during a fixed time window (e.g. 5s)
# Begin Routine
cedrus.clear_response_queue()
cedrus_keys, cedrus_times = [], []
t0 = globalClock.getTime()
# Each Frame
cedrus.poll_for_response()
while True:
r = cedrus.get_next_response()
if not r: break
cedrus_keys.append(str(r.get('key', '')))
cedrus_times.append(r.get('time', None))
if (globalClock.getTime() - t0) >= 5.0:
continueRoutine = False
# End Routine
thisExp.addData('cedrus.keys', cedrus_keys)
thisExp.addData('cedrus.times', cedrus_times)
3. Wait for a specific trigger (e.g. button '4' = scanner trigger)
# Each Frame
cedrus.poll_for_response()
if not trigger_received:
r = cedrus.get_next_response()
if r and str(r.get('key', '')) == '4':
trigger_received = True
thisExp.addData('cedrus.trigger_time', r.get('time', None))
continueRoutine = False
Tested on PsychoPy 2025.2.4 (Builder mode). Should also work on earlier versions that include pyxid2.
A few tips:
- Always
clear_response_queue()at Begin Routine to avoid stale presses - Use
poll_for_response()+get_next_response()— non-blocking, safe for screen refresh - Never use
sleep()or blocking loops in Each Frame
Happy to answer questions if anyone tries it!