Computer Programming
For Psychologists

Detecting key down events in PsychoPy


When handling keyboard events, most experiments only need to know the time a key was first pressed down. For example, in a typical classification task a participant can be instructed to use one key to indicate if an image on the screen contains an animal and another key to indicate it does not. PsychoPy allows you to interact with the keyboard using the waitKeys and the getKeys functions

 

waitKeys

The event.waitKeys() function is a blocking function. This means that at the moment this line is encountered, the code will halt at that line and only proceed until an actual key has been pressed. When we have a static stimulus that does not change over time, this approach works perfectly fine. We can simply draw the stimulus, execute the flip command and wait for a key press.

from psychopy import visual, event, core

# Create window
win = visual.Window(units = 'pix')

# Create shapes
rectangle = visual.Rect(win, width = 50, height = 50, fillColor = "red")

# Draw the stimulus
rectangle .draw()
window.flip()

# Wait for a response
response = event.waitKeys()

win.close()

 

getKeys

The getKeys function is a non-blocking function. This means that it will simply check if any keys have been pressed, but it will not wait for a key press if that is not the case. For a good mental picture, you can think of key presses arriving in a buffer in your operating system. The getKeys function will check and return any presses that are stored here, but if this buffer is empty it will not wait for key presses to arrive here. 

This approach is ideal when dealing with a dynamic stimulus, which is typically generated in an animation loop. Obviously we do not want to have a call to a blocking function here, as it would disrupt the animation. A non-blocking function allows us to check for keyboard input but without disrupting the animation. In the example below, the dynamic stimulus is a rectangle that moves in a circle. In addition to moving in a circle, the arrow keys can be used to move the stimulus to the left or to the right by manipulating the value of the x-variable.

from psychopy import visual, event, core

# Create window
win = visual.Window(units = 'pix')

# Create shapes
rectangle = visual.Rect(win, width = 50, height = 50, fillColor = "red")

# Animation loop
x = 0
timer = core.Clock()

while True:
   # Update 
   t = timer.getTime()
   rectangle.pos = [x + 100 * cos(0.1 * t), 100 * sin(0.1 * t]

   # Draw
   rectangle.draw()
   win.flip()

   # Check for a response
   response = event.getKeys()
   if 'escape' in response:
      break
   elif 'left' in response:
      x = x - 1
   elif 'right' in response:
      x = x + 1

There is one caveat with the procedure above. Ideally you want the object to move as long as the left or the right key is pressed down. But the getKeys function only returns you a key the first time it is pressed down, and not if it is currently pressed down. To move an object continuously to the left, you would therefore have to continuously tap the left key. 

 

Is a key currently pressed?

From the perspective of user interaction, it would be much more convenient if we have a mechanism that allows you to check if a key is currently pressed down and then act accordingly. One way to achieve this is to interact directly with the pyglet module, This is the module that PsychoPy uses to do its drawing. It also contains a few tools to interact with the keyboard in a way that allows us to check if a key is currently down.

The initialization code below imports the pyglet module and sets up the PsychoPy window as usual. We then set up a keyboard handler. A keyboard handler is a function that deals with incoming keyboard events. Keyboard events are first intercepted by your operating system, and when your PsychoPy window is active, the operating system will make sure that these events are transmitted to your window. It is then up to your window to decide how to handle these events. Some of this functionality is already implemented by the getKeys and waitKeys functions. However, now we also want to make use of the keyboard handler provided by pyglet.

Practically speaking, you can just think of the keyboard variable in the example below as a dictionary in which the keys represent the actual keys on your keyboard. The corresponding value will reflect if that key is currently being pressed down or not.

After doing this initial setup, we can implement the same behavior that we already had with the getKeys function in the example above. To check if a key is pressed, we look into the keyboard dictionary and inspect the values for the keys that we are interested in. Because the keyboard handler keeps track if a key is currently pressed down, we can now also continuously hold down a key to move an object.

from psychopy import visual, event, core
import pyglet

# Create window
win = visual.Window(units = 'pix')

# Set up the keyboard handler
key = pyglet.window.key               # Contains all possible key codes
keyboard = key.KeyStateHandler()      # Dictionary containing state of keys
win.winHandle.push_handlers(keyboard) # Connect dictionary with window

# Create shapes
rectangle = visual.Rect(win, width = 50, height = 50, fillColor = "red")

# Animation loop
x = 0
timer = core.Clock()

while True:
   # Update 
   t = timer.getTime()
   rectangle.pos = [x + 100 * cos(0.1 * t), 100 * sin(0.1 * t]

   # Draw
   rectangle.draw()
   win.flip()

   # Check for a response
   if keyboard[key.ESCAPE]:
      break
   elif keyboard[key.LEFT]:
      x = x - 1
   elif keyboard[key.RIGHT]:
      x = x + 1