Associate conditions to trials without adding loops

What are you trying to achieve?:
I’m trying to do a test where:

  1. one word among six is first shown on the screen, and the candidate needs to hit one of two buttons (only one correct), and then
  2. one image among six is shown, and the candidate needs to hit one of two buttons (only one correct).
  3. This loop is repeated six times, until each of the six words and images have been selected (random mode).

What did you try to make it work?:
I did the following code (corresponds to a builder of two sequential routines, one for the words, one for the images, and one loop with the conditions excel file associated):
#!/usr/bin/env python

-- coding: utf-8 --

“”"
This experiment was created using PsychoPy2 Experiment Builder (v1.90.1),
on Thu Apr 26 16:05:08 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 = ‘finalversion’ # from the Builder filename that created this script
expInfo = {‘session’: ‘001’, ‘participant’: ‘’}
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=(1024, 768), 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 “Words”

WordsClock = core.Clock()
WordStimuli = visual.TextStim(win=win, name=‘WordStimuli’,
text=‘default text’,
font=‘Arial’,
pos=(0, 0), height=0.1, wrapWidth=None, ori=0,
color=‘black’, colorSpace=‘rgb’, opacity=1,
depth=0.0);

Initialize components for Routine “Images”

ImagesClock = core.Clock()
ImageStimuli = visual.ImageStim(
win=win, name=‘ImageStimuli’,units=‘cm’,
image=‘sin’, mask=None,
ori=0, pos=(0, 0), size=(6.35, 8.89),
color=[1,1,1], colorSpace=‘rgb’, opacity=1,
flipHoriz=False, flipVert=False,
texRes=128, interpolate=True, depth=0.0)

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=2, method=‘random’,
extraInfo=expInfo, originPath=-1,
trialList=data.importConditions(‘PsychoPyProgram/ConditionsBlock1.xlsx’),
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 "Words"-------
t = 0
WordsClock.reset()  # clock
frameN = -1
continueRoutine = True
routineTimer.add(1.500000)
# update component parameters for each repeat
WordStimuli.setText(StimulusPresented)
WordResponse = event.BuilderKeyResponse()
# keep track of which components have finished
WordsComponents = [WordStimuli, WordResponse]
for thisComponent in WordsComponents:
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED

# -------Start Routine "Words"-------
while continueRoutine and routineTimer.getTime() > 0:
    # get current time
    t = WordsClock.getTime()
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame
    
    # *WordStimuli* updates
    if t >= 0.0 and WordStimuli.status == NOT_STARTED:
        # keep track of start time/frame for later
        WordStimuli.tStart = t
        WordStimuli.frameNStart = frameN  # exact frame index
        WordStimuli.setAutoDraw(True)
    frameRemains = 0.0 + 1.5- win.monitorFramePeriod * 0.75  # most of one frame period left
    if WordStimuli.status == STARTED and t >= frameRemains:
        WordStimuli.setAutoDraw(False)
    
    # *WordResponse* updates
    if t >= 0.0 and WordResponse.status == NOT_STARTED:
        # keep track of start time/frame for later
        WordResponse.tStart = t
        WordResponse.frameNStart = frameN  # exact frame index
        WordResponse.status = STARTED
        # keyboard checking is just starting
        win.callOnFlip(WordResponse.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 WordResponse.status == STARTED and t >= frameRemains:
        WordResponse.status = STOPPED
    if WordResponse.status == STARTED:
        theseKeys = event.getKeys(keyList=['e', 'i'])
        
        # check for quit:
        if "escape" in theseKeys:
            endExpNow = True
        if len(theseKeys) > 0:  # at least one key was pressed
            if WordResponse.keys == []:  # then this was the first keypress
                WordResponse.keys = theseKeys[0]  # just the first key pressed
                WordResponse.rt = WordResponse.clock.getTime()
                # was this 'correct'?
                if (WordResponse.keys == str(AnswerStimulus)) or (WordResponse.keys == AnswerStimulus):
                    WordResponse.corr = 1
                else:
                    WordResponse.corr = 0
    
    # 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 WordsComponents:
        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 "Words"-------
for thisComponent in WordsComponents:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)
# check responses
if WordResponse.keys in ['', [], None]:  # No response was made
    WordResponse.keys=None
    # was no response the correct answer?!
    if str(AnswerStimulus).lower() == 'none':
       WordResponse.corr = 1  # correct non-response
    else:
       WordResponse.corr = 0  # failed to respond (incorrectly)
# store data for trials (TrialHandler)
trials.addData('WordResponse.keys',WordResponse.keys)
trials.addData('WordResponse.corr', WordResponse.corr)
if WordResponse.keys != None:  # we had a response
    trials.addData('WordResponse.rt', WordResponse.rt)

# ------Prepare to start Routine "Images"-------
t = 0
ImagesClock.reset()  # clock
frameN = -1
continueRoutine = True
routineTimer.add(1.500000)
# update component parameters for each repeat
ImageStimuli.setImage(ImagePresented)
ImageResponse = event.BuilderKeyResponse()
# keep track of which components have finished
ImagesComponents = [ImageStimuli, ImageResponse]
for thisComponent in ImagesComponents:
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED

# -------Start Routine "Images"-------
while continueRoutine and routineTimer.getTime() > 0:
    # get current time
    t = ImagesClock.getTime()
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame
    
    # *ImageStimuli* updates
    if t >= 0.0 and ImageStimuli.status == NOT_STARTED:
        # keep track of start time/frame for later
        ImageStimuli.tStart = t
        ImageStimuli.frameNStart = frameN  # exact frame index
        ImageStimuli.setAutoDraw(True)
    frameRemains = 0.0 + 1.5- win.monitorFramePeriod * 0.75  # most of one frame period left
    if ImageStimuli.status == STARTED and t >= frameRemains:
        ImageStimuli.setAutoDraw(False)
    
    # *ImageResponse* updates
    if t >= 0.0 and ImageResponse.status == NOT_STARTED:
        # keep track of start time/frame for later
        ImageResponse.tStart = t
        ImageResponse.frameNStart = frameN  # exact frame index
        ImageResponse.status = STARTED
        # keyboard checking is just starting
        win.callOnFlip(ImageResponse.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 ImageResponse.status == STARTED and t >= frameRemains:
        ImageResponse.status = STOPPED
    if ImageResponse.status == STARTED:
        theseKeys = event.getKeys(keyList=['e', 'i'])
        
        # check for quit:
        if "escape" in theseKeys:
            endExpNow = True
        if len(theseKeys) > 0:  # at least one key was pressed
            if ImageResponse.keys == []:  # then this was the first keypress
                ImageResponse.keys = theseKeys[0]  # just the first key pressed
                ImageResponse.rt = ImageResponse.clock.getTime()
                # was this 'correct'?
                if (ImageResponse.keys == str(AnswerImage)) or (ImageResponse.keys == AnswerImage):
                    ImageResponse.corr = 1
                else:
                    ImageResponse.corr = 0
    
    # 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 ImagesComponents:
        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 "Images"-------
for thisComponent in ImagesComponents:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)
# check responses
if ImageResponse.keys in ['', [], None]:  # No response was made
    ImageResponse.keys=None
    # was no response the correct answer?!
    if str(AnswerImage).lower() == 'none':
       ImageResponse.corr = 1  # correct non-response
    else:
       ImageResponse.corr = 0  # failed to respond (incorrectly)
# store data for trials (TrialHandler)
trials.addData('ImageResponse.keys',ImageResponse.keys)
trials.addData('ImageResponse.corr', ImageResponse.corr)
if ImageResponse.keys != None:  # we had a response
    trials.addData('ImageResponse.rt', ImageResponse.rt)
thisExp.nextEntry()

completed 2 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()

What specifically went wrong when you tried that?:
Everything worked perfectly, except that: given that there is only one condition file, it always selects the word and then the Image of the same row of the Excel file associated to the conidtions. But I need that this choice is made completely randomly, i.e. that one random row is selected for the words routine, and then one random row is selected for the image routine. In other words, I would need to indicate specfic selection conditions to each of the two trials, without creating new loops. Is that possible?

Hi,

If you can live with your stimuli not being defined in the input file, the attached experiment might be one possible approach (the example is for three different stimuli per condition only, and both trials use text components).

I’d be curious to see other approaches though.

Jan

two_trials_with_random_stimuli.psyexp (9.4 KB)

Wow that’s just amazing, didn’t think that it would be so easy!! Thank you so much!

However, still a question: How do I implement correct key responses now that differ in one trial? For example, in your program, how do I assign the ‘e’-key as correct response to the text ‘a’ and ‘b’, and the ‘i’-key as correct response to the text ‘c’?

I thought you might ask that question. :slight_smile: One possible solution is in the attached experiment.

Note that currently the stimulus/response information is written to the output file as a list, e.g. ['c', 'left']. If you would like to have them separately, you can use the same approach that I used for presenting stimuli/determining the correct response, i.e. do something along these lines:

trials.addData("textA_stim", textA[trials.thisN][0])
trials.addData("textA_resp", textA[trials.thisN][1])

Jan

two_trials_with_random_stimuli_new.psyexp (9.6 KB)

Hahahha amaziiing! Could you may tell me the following (sorry but the explanations for the builder online are not as good as for code commands, and you seem to be experienced). In the experiment I need to measure reaction times. If I insert a limit into the stimulus presentation and the key hit, the label color of the corresponding trial turns into green. This means that the reaction time is stored using the non-slip timing method, where the clock of the trial is compared to a global clock at each trial, to assure precise millisecond storage, is that correct? I just need to make sure that my exported reaction time results are not biaised by any errors associated to time calculations of my computer. Non-slip timing avoids that, is that correct or am I wrong?

This means that the reaction time is stored using the non-slip timing method, where the clock of the trial is compared to a global clock at each trial, to assure precise millisecond storage, is that correct?

If I’m not mistaken, not really. The green colour means that the trial has a known endpoint in time. As a result of this, non-slip timing can be used. Non-slip timing ensures that your experiment timing doesn’t drift (see here for more information on this). This is unrelated to how the RT is measured. With regard to this issue, you might want to have a look at the Other questions about timing here.

Jan

Thanks again for your answer.

Alright, so this means that if my trials turn into green, I should calculate the number of frames present for my machine and its frame rate (60 Hz -> 16.67 ms / frame flip) and indicate the stimulus presentation times in frames rather than seconds to be heavily precise and to assure that each stimuli is presented for the exact same amount of time, namely for the exact same amount of frames (I verified with the demo that there were no frames dropping in my machine). In other words, stimuli onset in the builder will be set to 0 frames. stimuli stop (I want it to be as close possible to 1500 ms) is thus 1500 ms / 16.6667 = about 90 -> stimuli stop at frame number 90. Like this, it will be assured that the stimuli are all presented for the exact same period, true? Or does the builder do exact presentation timing automatically when a trial gets green / does the program automatically implement non-slip timing for trials with an endpoint?

In terms of RT measurement, I’m aware of errors associated to external factors as keyboard response latencies, human behaviour, etc., as stated in the link you send me. I only need to be able to compare the response latencies in between them, and thus need to assure that the only variations of the response latencies are associated to hardware or other external factors, rather than to variations in reaction time measurements by the program. Not sure if you know what I mean here. In other words, is Psychopy measuring the RT always in the same way over all trials in one experiment over several blocks, or is there a certain shift present in RT measurements, for example if you compare trial #1 with trial #1000? Is it maybe more precise to return the RT with frame numbers, converting it back to milliseconds for the output?

Thanks for your help, really!

A few comments (I would suggest that you also spend some time searching the forum as I’m pretty sure that similar questions have been asked in the past; if not, it might be preferable to start a new topic):

  • Yes, you will get the most precise stimulus timing with frames. (But does one frame more or less really matter for a stimulus that is presented for 1500 ms?)
  • Yes, the Builder automatically implements non-slip timing for trials with a known endpoint - but I suspect you misinterpret what exactly non-slip timing does. Put simply, non-slip timing corrects overshoots when stimulus presentation is time-based (to avoid an accrual of these overshoots over the course of the experiment - a major issue for fMRI). It does not actually play a role when stimulus presentation is frame-based. And, as mentioned before, it has nothing to do with your RT measurement.
  • Yes, PsychoPy should always measure the RT in the same way, even after many trials.

Jan

Thank you so much, that’s all I need to know! Sorry for taking so much of your time!

Sorry to bother you again, but I just can’t find out the following:

  • show a ‘Wrong’ Message on the screen, below the shown stimulus, if the wrong response key was hitten (for 200ms). (Instant feedback). I tried to do a conditional TextStimuli, but I don’t get how I need to set the conditions. I want to avoid to make an additional trial just for the feedback as in the demo provided, becuase this just adds another trial in my experiment already full of trials.
  • show a ‘Too Slow’ Message on the screen, on top of the stimulus, if no key has been pushed in the first 800ms. Same problem as on top.
  • Repeat an entire practice trial if more than 50% of the provided key responses during that same practice trial were wrong, or if more than 50% of the provided key responses were too slow.

Could you may help me out here again?

Okay so I figured out how to implement the instant error message, that’s done.
The Too Slow Message is also implemented in the code below, but here’s the problem: The Too Slow Message shows up at right timing, but it shows up even if a key was hit previously, The message should however only show up if no key was hitten until 800ms of each trial.

Code: (Basically, you only need to focus only on lines 185-191, it must be adapted somewhere there I guess).

#!/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()