Message should show up if no key was hit in 800 ms

Dear users,

In my code, the ‘TOO SLOW’ message should show up if there is no key hit until 800ms in a trial lasting for 1500 ms. In my code below (the lines 185 - 191 are the important ones), the message shows up after 800 ms, but blinks weirdly, as well as its shows up even if a key was hit. Can you help me?

Instant_Errormessage.py (9.8 KB)

#!/usr/bin/env python

-- coding: utf-8 --

“”"
This experiment was created using PsychoPy2 Experiment Builder (v1.90.1),
on Sun Apr 29 13:13:45 2018
If you publish work using this script please cite the PsychoPy publications:
Peirce, JW (2007) PsychoPy - Psychophysics software in Python.
Journal of Neuroscience Methods, 162(1-2), 8-13.
Peirce, JW (2009) Generating stimuli for neuroscience using PsychoPy.
Frontiers in Neuroinformatics, 2:10. doi: 10.3389/neuro.11.010.2008
“”"

from future import absolute_import, division
from psychopy import locale_setup, sound, gui, visual, core, data, event, logging, clock
from psychopy.constants import (NOT_STARTED, STARTED, PLAYING, PAUSED,
STOPPED, FINISHED, PRESSED, RELEASED, FOREVER)
import numpy as np # whole numpy lib is available, prepend ‘np.’
from numpy import (sin, cos, tan, log, log10, pi, average,
sqrt, std, deg2rad, rad2deg, linspace, asarray)
from numpy.random import random, randint, normal, shuffle
import os # handy system and path functions
import sys # to get file system encoding

Ensure that relative paths start from the same directory as this script

_thisDir = os.path.dirname(os.path.abspath(file)).decode(sys.getfilesystemencoding())
os.chdir(_thisDir)

Store info about the experiment session

expName = u’Instant_Errormessage’ # from the Builder filename that created this script
expInfo = {u’session’: u’001’, u’participant’: u’’}
dlg = gui.DlgFromDict(dictionary=expInfo, title=expName)
if dlg.OK == False:
core.quit() # user pressed cancel
expInfo[‘date’] = data.getDateStr() # add a simple timestamp
expInfo[‘expName’] = expName

Data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc

filename = thisDir + os.sep + u’data/%s%s_%s’ % (expInfo[‘participant’], expName, expInfo[‘date’])

An ExperimentHandler isn’t essential but helps with data saving

thisExp = data.ExperimentHandler(name=expName, version=’’,
extraInfo=expInfo, runtimeInfo=None,
originPath=None,
savePickle=True, saveWideText=True,
dataFileName=filename)

save a log file for detail verbose info

logFile = logging.LogFile(filename+’.log’, level=logging.EXP)
logging.console.setLevel(logging.WARNING) # this outputs to the screen, not a file

endExpNow = False # flag for ‘escape’ or other condition => quit the exp

Start Code - component code to be run before the window creation

Setup the Window

win = visual.Window(
size=[2560, 1440], fullscr=True, screen=0,
allowGUI=False, allowStencil=False,
monitor=‘testMonitor’, color=[0,0,0], colorSpace=‘rgb’,
blendMode=‘avg’, useFBO=True)

store frame rate of monitor if we can measure it

expInfo[‘frameRate’] = win.getActualFrameRate()
if expInfo[‘frameRate’] != None:
frameDur = 1.0 / round(expInfo[‘frameRate’])
else:
frameDur = 1.0 / 60.0 # could not measure, so guess

Initialize components for Routine “trial”

trialClock = core.Clock()
text = visual.TextStim(win=win, name=‘text’,
text=‘default text’,
font=u’Arial’,
pos=(0, 0), height=0.1, wrapWidth=None, ori=0,
color=u’white’, colorSpace=‘rgb’, opacity=1,
depth=0.0);
textA = [[“a”, “space”], [“b”, “space”], [“c”, “space”]]

shuffle(textA)

bg_Question = visual.TextStim(win,
text = “IS THIS RELATED TO YOUR STIMULI?”, pos = (0,0.85),
color = u’black’)

bg_YES = visual.TextStim(win,
text = “e=YES”, pos = (-0.85,0.75), color = u’green’)

bg_NO = visual.TextStim(win,
text = “e=YES”, pos = (0.85,0.75), color = u’green’)

bg_Question.autoDraw = True
bg_YES.autoDraw = True
bg_NO.autoDraw = True

errormsg = visual.TextStim(win, text = ‘WRONG!’,color = u’red’, pos = (0,-0.5))
tsmsg = visual.TextStim(win, text = ‘TOO SLOW!’, color = u’red’, pos = (0,0.4))

Create some handy timers

globalClock = core.Clock() # to track the time since experiment started
routineTimer = core.CountdownTimer() # to track time remaining of each (non-slip) routine

set up handler to look after randomisation of conditions etc

trials = data.TrialHandler(nReps=3, method=‘sequential’,
extraInfo=expInfo, originPath=-1,
trialList=[None],
seed=None, name=‘trials’)
thisExp.addLoop(trials) # add the loop to the experiment
thisTrial = trials.trialList[0] # so we can initialise stimuli with some values

abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)

if thisTrial != None:
for paramName in thisTrial:
exec(’{} = thisTrial[paramName]’.format(paramName))

for thisTrial in trials:
currentLoop = trials
# abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)
if thisTrial != None:
for paramName in thisTrial:
exec(’{} = thisTrial[paramName]’.format(paramName))

# ------Prepare to start Routine "trial"-------
t = 0
trialClock.reset()  # clock
frameN = -1
continueRoutine = True
routineTimer.add(1.500000)
# update component parameters for each repeat
text.setText(textA[trials.thisN][0])
key_resp_2 = event.BuilderKeyResponse()
trials.addData("textA", textA[trials.thisN])
# keep track of which components have finished
trialComponents = [text, key_resp_2]
for thisComponent in trialComponents:
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED

# -------Start Routine "trial"-------
while continueRoutine and routineTimer.getTime() > 0:
    # get current time
    t = trialClock.getTime()
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame
    
    # *text* updates
    if t >= 0.0 and text.status == NOT_STARTED:
        # keep track of start time/frame for later
        text.tStart = t
        text.frameNStart = frameN  # exact frame index
        text.setAutoDraw(True)
    frameRemains = 0.0 + 1.5- win.monitorFramePeriod * 0.75  # most of one frame period left
    if text.status == STARTED and t >= frameRemains:
        text.setAutoDraw(False)
    
    # *key_resp_2* updates
    if t >= 0.0 and key_resp_2.status == NOT_STARTED:
        # keep track of start time/frame for later
        key_resp_2.tStart = t
        key_resp_2.frameNStart = frameN  # exact frame index
        key_resp_2.status = STARTED
        # keyboard checking is just starting
        win.callOnFlip(key_resp_2.clock.reset)  # t=0 on next screen flip
        event.clearEvents(eventType='keyboard')
    frameRemains = 0.0 + 1.5- win.monitorFramePeriod * 0.75  # most of one frame period left
    if key_resp_2.status == STARTED and t >= frameRemains:
        key_resp_2.status = STOPPED
    if key_resp_2.status == STARTED:
        theseKeys = event.getKeys(keyList=['e', 'i', 'space'])
        
        # check for quit:
        if "escape" in theseKeys:
            endExpNow = True
        if len(theseKeys) > 0:  # at least one key was pressed
            if key_resp_2.keys == []:  # then this was the first keypress
                key_resp_2.keys = theseKeys[0]  # just the first key pressed
                key_resp_2.rt = key_resp_2.clock.getTime()
                # was this 'correct'?
                if (key_resp_2.keys == str(textA[trials.thisN][1])) or (key_resp_2.keys == textA[trials.thisN][1]):
                    key_resp_2.corr = 1
                else:
                    key_resp_2.corr = 0
                    ErrorTimer = core.CountdownTimer(0.4)
                    while ErrorTimer.getTime() > 0:
                        errormsg.setAutoDraw(True)
                        win.flip()
                    errormsg.setAutoDraw(False)
                    win.flip()
        if len(theseKeys) == 0 and t > 0.8 :
            TooSlowTimer = core.CountdownTimer(0.4)
            while TooSlowTimer.getTime() > 0:
                tsmsg.setAutoDraw(True)
                win.flip()
            tsmsg.setAutoDraw(False)
            win.flip()
    # check if all components have finished
    if not continueRoutine:  # a component has requested a forced-end of Routine
        break
    continueRoutine = False  # will revert to True if at least one component still running
    for thisComponent in trialComponents:
        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
            continueRoutine = True
            break  # at least one component has not yet finished
    
    # check for quit (the Esc key)
    if endExpNow or event.getKeys(keyList=["escape"]):
        core.quit()
    
    # refresh the screen
    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
        win.flip()

# -------Ending Routine "trial"-------
for thisComponent in trialComponents:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)
# check responses
if key_resp_2.keys in ['', [], None]:  # No response was made
    key_resp_2.keys=None
    # was no response the correct answer?!
    if str(textA[trials.thisN][1]).lower() == 'none':
       key_resp_2.corr = 1  # correct non-response
    else:
       key_resp_2.corr = 0  # failed to respond (incorrectly)
# store data for trials (TrialHandler)
trials.addData('key_resp_2.keys',key_resp_2.keys)
trials.addData('key_resp_2.corr', key_resp_2.corr)
if key_resp_2.keys != None:  # we had a response
    trials.addData('key_resp_2.rt', key_resp_2.rt)

thisExp.nextEntry()

completed 3 repeats of ‘trials’

these shouldn’t be strictly necessary (should auto-save)

thisExp.saveAsWideText(filename+’.csv’)
thisExp.saveAsPickle(filename)
logging.flush()

make sure everything is closed down

thisExp.abort() # or data files will save again on exit
win.close()
core.quit()

  if len(theseKeys) == 0 and t > 0.8 :
            TooSlowTimer = core.CountdownTimer(0.4)
            while TooSlowTimer.getTime() > 0:
                tsmsg.setAutoDraw(True)
                win.flip()
            tsmsg.setAutoDraw(False)
            win.flip()

OK, let’s modify a few bits of this:

  1. instead of changing “setAutoDraw”, just call tsmsg.draw() on each loop. That will make sure it is consistently drawn on the screen.

  2. replace the “if” with an “elif”. That means it only gets tripped if the first “if” statement (the one that trips if they did press a key) is false.

End result:

  elif len(theseKeys) == 0 and t > 0.8 :
            TooSlowTimer = core.CountdownTimer(0.4)
            while TooSlowTimer.getTime() > 0:
                tsmsg.draw()
                win.flip()
            win.flip()

Try that.

Hi Jonathan,

I meanwhile figured it out. If the solution may interests you:

            if t > 0.8 and key_resp_2.keys in ['', [], None]:
                while routineTimer.getTime() > 0:
                    tsmsg.setAutoDraw(True)
                    win.flip()
                tsmsg.setAutoDraw(False)
                win.flip()

Regards

(I adapted the duration to the routine timer to avoid to use a new core.clock timer which could hamper the timing of the experiment)

It is using your own calls to win.flip() which can break Builder’s timing. Builder is fundamentally structured around a cycle of drawing to the screen on every screen refresh, calling just one win.flip() on every cycle. You’re highjacking that here by inserting a loop that will do its own win.flip(), effectively pausing all other Builder-created code. This could have very large and unexpected effects. You should try to restructure your code to avoid using any win.flip() of your own.

All of this is much clearer if you work within a Builder code component, where you can have code that executes on every Builder screen refresh cycle (it seems to me like you are manually editing the .py file that Builder generates, rather than staying within the graphical Builder environment).

Hi Michael!

Oh that’s bad news!

Yes that was precisely what I was doing; editing the code of the experiment created in the Builder by just inserting the mentioned part above. Can you provide me with a solution to do exactly this thing in the code component of the Builder? I’m absolutely clueless in that sense.

Make a text stimulus containing a variable like $message, set to update every frame.

Insert a code component:

Begin routine tab:

message = ''  # initial blank text
message_shown = False

Each frame tab:

if not message_shown and t > 0.8: # only do this once
    # if the key list is empty:
    if not your_keyboard_component_name.keys:
        message = 'Too slow'
        message_shown = True

See, no drawing needed. Builder does it for you.

Put the code component above the text component so that the text component gets access to the updated message variable immediately.

Wow works perfectly, cheers!