psychopy.org | Reference | Downloads | Github

New button box implementation - Question about response times

Hello,

Our lab purchased a UPF button box developed by researchers from the University of Ferrara and the Italian Institute of Technology (website). I’ve developed python code using the ftd2xx python wrapper so that the device interfaces with Psychopy. The device has worked successfully with Psychopy thus far. We are in the process of testing the response times of the UPF box, and comparing them with those of a keyboard, and a Cedrus box (RB-730).

I developed two implementations of the UPF box in Psychopy Coder, and a single implementation in Psychopy Builder to test the generic keyboard and the Cedrus box. The general paradigm of an individual trial involves the presentation of a fixation cross at random interval between 1 & 3 seconds. The user hits a button in response to seeing the fixation cross and the response time is captured. For each input device, twenty trials are performed and the average response time is calculated.

Initially, as with any new equipment, we were concerned that the response time would be delayed, (most likely due to my amateurish programming), but in fact we are now wondering if the opposite problem is occurring. We are getting average response times of less than 300ms on each device, and are trying to determine if something is wrong.

Specifically I’m seeing .251 sec for the Cedrus box, .318 sec for the keyboard, .212 sec for the first implementation of the UPF box using frameN, and .241 sec for the second implementation of the UPF box not using frameN. Also I think I may be using frameN incorrectly, as it doesn’t operate the way I expect it to. Psychopy seems to treat it as a generic constant. Which explains the especially quick response time average, the fixation cross is presented in an interval that seems fixed instead of being randomized.

It’s my understanding that a response time of less that 300ms is unusually quick. However, I can’t think of any reason why a response time would be ‘too quick’, I can only see the potential for it being ‘too slow’ due to a problem with the code or the device. Is my expectation of response times reasonable, or is there a potential problem with the device implementation?

Code for frameN implementation:

#! /usr/bin/ev python2
# -*- coding: utf-8 -*-

"""
This psychopy experiment tests uses the UPF button box the response time to a basic stimuli being flashed
on the screen, in this case a fixation cross.  The response time captured by multiple trials is averaged.
This program uses the frameN constant from psychopy to structure the individual trials.
"""
from psychopy import visual, core, constants
from UPF_r1 import initialize_UPF_buttonbox, button_press_check, close_UPF_buttonbox
from random import randrange

# initalize window and fixation cross stimuli
win = visual.Window(size=(1920, 1080), fullscr=True, screen=0, allowGUI=False, allowStencil=False,
    monitor=u'Benq', color=[0,0,0], colorSpace='rgb',
    blendMode='avg', useFBO=True,
    units='deg')
fixationcross = visual.TextStim(win=win, ori=0, name='fixationcross',
    text='+',    font='Arial',
    units='deg', pos=[0,0], height=2, wrapWidth=None,
    color='white', colorSpace='rgb', opacity=1,
    depth=-2.0)
# initialize stimuli introduction
message1 = visual.TextStim(win, pos = [0, +3], text = "A stimuli will be presented shorty, press button in reaction to the stimuli")
message1.setAutoDraw(True)
# initalize button box
button_box = initialize_UPF_buttonbox()
# store frame rate of monitor if we can measure it successfully
frame_rate = int(win.getActualFrameRate())

trials = 10 # can set trials to whatever is desired

# display stimuli introduction message
win.update()
core.wait(2)
message1.setAutoDraw(False)
win.update()

# initialize response time clock, and the response time and key press lists
rt_clock = core.Clock()
response_times = []
keys_pressed = []

# for each trial, a fixation cross is presented after a delay 1 - 3 seconds
# each trial occurs in rapid succession, a list of keys pressed, and a correspoding
# list of response times are produced
for i in range(trials):
    stimulus_onset_frame = randrange(1, frame_rate * 3 - 40) # randomize stimulis presentation between 1 & 3 seconds


    core.wait(2) #not sure why this is here?
    for frameN in range(stimulus_onset_frame + frame_rate * 10):#for exactly 200 frames

        if stimulus_onset_frame <= frameN < stimulus_onset_frame + 40: # present fixation cross for 40 frames after onset
            if frameN == stimulus_onset_frame:
                rt_clock.reset() #response time clock reset at stimulus onset
            fixationcross.setAutoDraw(True)
            win.update()
        if stimulus_onset_frame <= frameN < stimulus_onset_frame + frame_rate * 10: # check for button press for up to 10 seconds

            button_pressed, response_time = button_press_check(rt_clock, button_box)
            if button_pressed != '': # if button_press_check returns a non-empty string (correspoding to a key being pressed)
                                     # the fixation cross is cleared from the screen, and the key pressed
                                     # and response time is appended to a relevant list
                fixationcross.setAutoDraw(False)
                win.update()
                keys_pressed.append(button_pressed)
                response_times.append(response_time)
                break

# the response time for each trial is printed, and the average response time
# across the trials is calculated

rt_sum = 0.0
for i in range(trials):
    print "the response time is: ", response_times[i]
    rt_sum += response_times[i]

avg_response_time = rt_sum / trials
print "The average response time is: ", avg_response_time


#cleanup
close_UPF_buttonbox(button_box)
win.close()
core.quit()

Code for implementation without frameN:

#! /usr/bin/ev python2
# -*- coding: utf-8 -*-
"""
This psychopy experiment tests uses the UPF button box the response time to a basic stimuli being flashed
on the screen, in this case a fixation cross.  The response time captured by multiple trials is averaged.
This test program does not use the frameN constant from psychopy in its implementation.
"""
from psychopy import visual, core
from UPF_r1 import initialize_UPF_buttonbox, button_press_check, close_UPF_buttonbox
from random import randrange

# initalize window and fixation cross
win = visual.Window(size=(1920, 1080), fullscr=True, screen=0, allowGUI=False, allowStencil=False,
    monitor=u'Benq', color=[0,0,0], colorSpace='rgb',
    blendMode='avg', useFBO=True,
    units='deg') # copied window properties from Laura's program
fixationcross = visual.TextStim(win=win, ori=0, name='fixationcross',
        text='+',    font='Arial',
        units='deg', pos=[0,0], height=2, wrapWidth=None,
        color='white', colorSpace='rgb', opacity=1,
        depth=-2.0)

def display_stimulus():
    core.wait(randrange(1, 4)) # delay of 1 - 3 seconds before displaying fixation cross
    fixationcross.setAutoDraw(True)
    win.update()

# intialize and display stimuli intro message
message1 = visual.TextStim(win, pos = [0, +3], text = "A stimuli will be presented shorty, press button in reaction to the stimuli")
message1.setAutoDraw(True)
win.update()
button_box = initialize_UPF_buttonbox()

# initalize response time clock, set the number of trials, and initialize the key press and
# response time lists
rt_clock = core.Clock()
trials = 20
response_times = []
keys_pressed = []

core.wait(2)
message1.setAutoDraw(False)

# for each trial, a fixation cross is presented after a delay 1 - 3 seconds
# each trial occurs in rapid succession, a list of keys pressed, and a correspoding
# list of response times are produced

for i in range(trials):
    display_stimulus()
    rt_clock.reset()
    button_pressed, response_time = button_press_check(rt_clock, button_box)

    if button_pressed != '': # if button_press_check returns a non-empty string (correspoding to a key being pressed)
                             # the fixation cross is cleared from the screen, and the key pressed
                             # and response time is appended to a relevant list
        fixationcross.setAutoDraw(False)
        win.update()
        keys_pressed.append(button_pressed)
        response_times.append(response_time)

# the response time for each trial is printed, and the average response time
# across the trials is calculated

rt_sum = 0.0
for i in range(trials):
    rt_sum += response_times[i]
    print "The response time is: ", response_times[i]

avg_response_time = rt_sum / trials
print "The average response time is: ", avg_response_time

#cleanup
close_UPF_buttonbox(button_box)
win.close()
core.quit()

API code (UPF_r1) that utilizes python wrapper:

#! /usr/bin/ev python2
# -*- coding: utf-8 -*-

import ftd2xx
import ftd2xx._ftd2xx as _ftd2xx
from psychopy import core
def initialize_UPF_buttonbox():
    # initializing the device using the ftd2xx python wrapper
    # first, search for an existing device, grab the serial number
    # then using the serial number, an instance of the button box will be
    # created, followed by a device reset, and setting of the Baud rate

    serial_number = ftd2xx.listDevices()
    button_box = ftd2xx.openEx(serial_number[0])
    button_box.resetDevice()
    button_box.setBaudRate(921600)
    button_box.setBitMode(0xff, 1) #sets I/O pins of ftd chipset to output, & asynchronous bitbang mode
    button_box.write('?')  #the write() function sets the I/O pins to 5V or 0V
                           #the ascii value of '?' in binary is '111111' (6 pins are set to 5V)
    return button_box


def button_press_check(rt_clock, button_box):
    button_pressed = ''
    response_time = 0
    button_box.setBitMode(0xff, 1) #sets I/O pins of ftd chipset to output, & asynchronous bitbang mode
    button_box.write('?')  #the write() function sets the I/O pins to 5V or 0V
                       #the ascii value of '?' in binary is '111111' (6 pins are set to 5V)
    button_state = '{0:b}'.format(button_box.getBitMode()) #function returns the state of the pins in binary (111111)
    while button_state == '111111':
        button_state = '{0:b}'.format(button_box.getBitMode())
    response_time = rt_clock.getTime() # use psychopy clock module to track response time
    if button_state == '111110':
        button_pressed = 'BUTTON_ONE'
    elif button_state == '111101':
        button_pressed = 'BUTTON_TWO'
    elif button_state == '111011':
        button_pressed = 'BUTTON_THREE'
    elif button_state == '110111':
        button_pressed = 'BUTTON_FOUR'
    elif button_state == '101111':
        button_pressed = 'BUTTON_FIVE'
    #core.wait(1) #added a brief pause to prevent checking for button press in rapid sucession,
                 # if that happens, the function does not work. It unclear as to why this is the case.
    return button_pressed, response_time

def close_UPF_buttonbox(button_box):
    # resets device and closes
    button_box.setBitMode(0xff, 0)
    button_box.close()

I apologize if I’ve provided too much code, I figured it was better to err on the side of more rather than less.

I would appreciate any general feedback / suggestions about my implementation of the UPF box that you may have

Thank you!
Dave