Using Cedrus/Lumina devices in PsychoPy without the Cedrus plugin

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!