Psychopy 3.1.5: buffer issues with the new Keyboard component

Hi community,
I just downloaded psychopy 3.1.5 on my laptop (dell XPS 15) running windows 10 and was playing with the new keyboard functions from Psychtoolbox when I discovered something strange. It looks like the pressed keys remain in the buffer despite attempts to clear them via clear = True and clearEvents().

Consider the following piece of code that implements a lexical decision task:

from psychopy import core, data, event, visual, gui
import numpy as np
import os
from psychopy.hardware import keyboard

words_list = np.array(['brother', 'tennis', 'fishing'], dtype='str')
nonwords_list = np.array(['eotif', 'aptas', 'tubai'], dtype='str')
stimuli_list =  np.concatenate((words_list , nonwords_list))

win = visual.Window(fullscr=True, 
     color=[255, 255, 255], colorSpace='rgb255', units='cm')
police = 'Consolas'
win.setMouseVisible(False)

height_letters = 1.
#template stimulus
target = visual.TextStim(win=win, ori=0,
    text=u'template',
    font=police,
    pos=[0,0],
    color=[0,0,0], colorSpace=u'rgb255', height = height_letters)

instr = visual.TextStim(win=win, 
    ori=0.0,#orientation
    text =u"Press 'f' key if stimulus is a word, press 'j' key otherwise. Press spacebar key to start",
    font=police,
    pos=[0, 0],
    height=.7,
    color=[0,0,0],
    colorSpace='rgb255')

# initialize keyboards
resp = keyboard.Keyboard()
defaultKeyboard = keyboard.Keyboard()#for escaping only

ITI = 1. #intertrial interval

#present instructions
while True:  
    theseKeys = resp.getKeys(keyList=['space'], waitRelease=False)
    if len(theseKeys):
        break
    if defaultKeyboard.getKeys(keyList=["escape"], waitRelease=False):
        core.quit()    
    instr.draw()
    win.flip()
win.flip()#clear screen
core.wait(2)

#present stimuli
for i in stimuli_list:    
    if i in words_list:
        correct_resp  = 'f'
    if i in nonwords_list:
        correct_resp  = 'j'                  
    target.setText(text = i)
    win.callOnFlip(resp.clock.reset)
    
    kb_delay = 0 
    resp.clearEvents()
    while True:
        if kb_delay==1:#check keyboard buffer after first draw of stimulus and clock reset
            theseKeys = resp.getKeys(keyList=['f', 'j'], waitRelease=False)
            if len(theseKeys):
                theseKeys = theseKeys[0] 
                key_pressed = theseKeys.name
                RT = theseKeys.rt
                break
        if defaultKeyboard.getKeys(keyList=["escape"], waitRelease=False):
            core.quit()              
        target.draw()           
        win.flip()
        if kb_delay == 0:
            kb_delay = 1
        
    win.flip()#clear screen
    core.wait(ITI)
#Shutting down:
win.close()
core.quit()

When I press ‘f’ or ‘j’ in the intertrial interval, the next stimulus is skipped, suggesting that the key stays in the buffer despite the call to resp.clearEvents(). I wonder if it could due to an incompatibility between laptop’s built-in keyboard and the USB HID library in C. I would be very grateful if anyone could provide an explanation for this issue.

Below are details of my built-in keyboard (output of keyboard.getKeyboards())

[{'usagePageValue': 1.0, 'usageValue': 6.0, 'usageName': 'slave keyboard', 'index': 0.0, 'transport': 'Clavier', 'vendorID': None, 'productID': -1.0, 'version': None, 'manufacturer': None, 'product': 'Clavier', 'serialNumber': None, 'locationID': -1.0, 'interfaceID': -1.0, 'totalElements': 0.0, 'features': 0.0, 'inputs': 0.0, 'outputs': 0.0, 'collections': 0.0, 'axes': 0.0, 'buttons': 0.0, 'hats': 0.0, 'sliders': 0.0, 'dials': 0.0, 'wheels': 0.0, 'touchDeviceType': -1.0, 'maxTouchpoints': -1.0}]

EDIT: the problem remains when I use a USB-connected keyboard.

I don’t know the keyboard code very well, but the clearEvents function might need to flush the buffers.

If you replace your resp.clearEvents() with:

for buff in resp._buffers.values():
    buff.flush()
resp.clearEvents()

it seems to overcome the issue - might cause other issues though.

@djmannion Good catch, the buffers were not flushed in the keyboard.py script:

    def clearEvents(self, eventType=None):
        if havePTB:
            for buffer in self._buffers.values():
                buffer._evts.clear()
                buffer._keys.clear()
                buffer._keysStillDown.clear()
        else:
            event.clearEvents(eventType)

Adding buffer.flush() fixed the issue:

    def clearEvents(self, eventType=None):
        if havePTB:
            for buffer in self._buffers.values():
                buffer.flush()
                buffer._evts.clear()
                buffer._keys.clear()
                buffer._keysStillDown.clear()
        else:
            event.clearEvents(eventType)

@jon Could you validate the bug and the fix? Is there something else to modify in the script (in particular, I wonder if there is something to modify in the class _KeyBuffer(object) ).

Sounds good. Thanks for reporting @Servant_Mathieu and interrogating @djmannion :slight_smile: The new keyboard, while super-fast, is pretty complicated. We’ve now got multiple levels of buffer and check, which has caused this issue, but your fix looks right to me. In this case the keypress was still on the device buffer while we were only clearing our own software buffer.

Currently pushing for a 3.2.0 release that will contain this fix