Psychopy Assignment: The stroop effect#

In this exercise we’ll put some of what we’ve learned so far together into an simple experiment demonstrating the Stroop Effect. We’ll gradually build this codebase out into a full experiment.

General instructions

  • To work on this assignment, please create a GitHub folder called stroop-experiment and create a .py file called stroop.py using the starter code below.

  • Then, for each task, please create (at least) one separate GitHub commit so that I can see your history of updating your code base.

  • Solution code is provided beneath each exercise, but I encourage you to first try to figure out the code and experiment a bit at each step yourself, knowing that you can always “check” the correct answer at any point. The more you practice actually trying to generate (and type out) the code, the more effective the assignment will be at building your psychopy skills.

import time
import sys
import os
import random
from psychopy import visual,event,core,gui

stimuli = ['red', 'orange', 'yellow', 'green', 'blue']

win = visual.Window([800,600],color="gray", units='pix',checkTiming=False)
placeholder = visual.Rect(win,width=180,height=80, fillColor="lightgray",lineColor="black", lineWidth=6,pos=[0,0])
word_stim = visual.TextStim(win,text="", height=40, color="black",pos=[0,0])
instruction = visual.TextStim(win,text="Press the first letter of the ink color", height=20, color="black",pos=[0,-200])
while True:
    cur_stim = random.choice(stimuli)
    word_stim.setText(cur_stim)
    word_stim.setColor(cur_stim)
    placeholder.draw()
    instruction.draw()
    word_stim.draw()
    win.flip()
    core.wait(1.0)
    placeholder.draw()
    instruction.draw()    
    win.flip()
    core.wait(.15)

    if event.getKeys(['q']):
        win.close()
        core.quit()

Once you’ve successfully run this code and understand what all the lines do, proceed to the following exercises.

PART 1: BUILD THE BASIC STROOP TRIALS#

  1. Fixation Cross Create a fixation cross using a TextStim object visual.TextStim set text to “+” and color to “black” and height to 15. Make the fixation cross appear for 500 ms before each color word, and disappear right before the color word appears, with another 500 ms inter-stimulus interval between the fixation cross and the word.

Hide code cell source
#add fixation cross to the top of the script (before the while loop)
fixation = visual.TextStim(win,height=40,color="black",text="+")
Hide code cell source
#new while loop
#add fixation cross
while True:
    cur_stim = random.choice(stimuli)
    word_stim.setText(cur_stim)
    word_stim.setColor(cur_stim)
    placeholder.draw()
    instruction.draw()
    fixation.draw()
    win.flip()
    core.wait(.5)
    placeholder.draw()
    instruction.draw()
    win.flip()
    core.wait(.5)
    placeholder.draw()
    instruction.draw()
    word_stim.draw()
    win.flip()
    core.wait(1.0)
    placeholder.draw()
    instruction.draw()    
    win.flip()
    core.wait(.15)

    if event.getKeys(['q']):
        win.close()
        core.quit()
  1. AutoDraw

Use the autoDraw functionality to automatically draw the instruction for every window flip. This simplifies the code above quite a bit!

Hide code cell source
#add autoDraw to instruction
instruction = visual.TextStim(win,text="Press the first letter of the ink color", height=20, color="black",pos=[0,-200],autoDraw=True)
Hide code cell source
#new while loop
#remove instruction.draw() since this is now automatically drawn!
while True:
    cur_stim = random.choice(stimuli)
    word_stim.setText(cur_stim)
    word_stim.setColor(cur_stim)
    placeholder.draw()
    fixation.draw()
    win.flip()
    core.wait(.5)
    placeholder.draw()
    win.flip()
    core.wait(.5)
    placeholder.draw()
    word_stim.draw()
    win.flip()
    core.wait(1.0)
    placeholder.draw()   
    win.flip()
    core.wait(.15)

    if event.getKeys(['q']):
        win.close()
        core.quit()
  1. Wait for a response

Rather than cycling throught the colors, use event.waitKeys() to wait for a response (e.g., “o” for “orange”). Your script should only accept ‘r’, ‘o’, ‘y’, ‘g’, ‘b’ (the first letter of each color) and – to make testing easier for you – ‘q’ for quit.

Tip

Make sure your code has just the functionality it needs, e.g., for part 2, you don’t need event.getKeys(['q'])

Hide code cell source
# define valid response keys to the top of the script (before the while loop)
valid_response_keys = ['r', 'o', 'y', 'g', 'b','q']
Hide code cell source
#add event.waitKeys to the while loop
#replace the event.getKeys call with a check of whether the key pressed is a 'q'
while True:
    cur_stim = random.choice(stimuli)
    word_stim.setText(cur_stim)
    word_stim.setColor(cur_stim)
    placeholder.draw()
    fixation.draw()
    win.flip()
    core.wait(.5)
    placeholder.draw()
    win.flip()
    core.wait(.5)
    placeholder.draw()
    word_stim.draw()
    win.flip()
    key_pressed = event.waitKeys(keyList=valid_response_keys)
    print(key_pressed[0])

    if key_pressed[0] == 'q':
        break
  1. Reaction times

Compute the reaction times – the time it takes to respond from when the color word appears, to when the user presses a key, in milleconds (e.g., .8 secs should show up as 800). Store these in a list called RTs. (Use psychopy timers)

Tip

Append each reaction time to the RTs list after every response.

Print the list to verify that the reaction times are correct (e.g., if you take approx 1 second to respond, is it recording 1000?). Your submitted code should have this print statement.

Hide code cell source
#update to while loop portion
RTs=[] #set RT list
response_timer = core.Clock() # set response timer clock
key_pressed=False #initializing this for later
while True:
    cur_stim = random.choice(stimuli)
    word_stim.setText(cur_stim)
    word_stim.setColor(cur_stim)
    placeholder.draw()
    fixation.draw()
    win.flip()
    core.wait(.5)
    placeholder.draw()
    win.flip()
    core.wait(.5)
    placeholder.draw()
    word_stim.draw()
    win.flip()
    response_timer.reset() # immediately after win.flip(), reset clock to measure RT
    key_pressed = event.waitKeys(keyList=valid_response_keys)
    RTs.append(round(response_timer.getTime()*1000,0)) #add an RT to the list, rounded to the nearest millisecond

    if key_pressed[0] == 'q':
        break

print(RTs)
  1. Feedback

Now let’s implement some feedback. If the user responded correctly, do nothing. If the user responded incorrectly, show “Incorrect” in black letters and add a 1s time delay before going on to the next trial.

Hide code cell source
# add a new feedback TextStim before the while loop
feedback_incorrect = visual.TextStim(win,text="INCORRECT", height=40, color="black",pos=[0,0])
Hide code cell source
#add feedback after collecting the response and RT
if key_pressed[0] == cur_stim[0]:
    #correct response
    pass
elif key_pressed[0] == 'q':
    break
else:
    feedback_incorrect.draw()
    win.flip()
    core.wait(1)
  1. Timeout

Now, instead of waiting for a response forever, let’s implement a timeout. Show accuracy feedback as before, show “Too slow” if the user takes more than 2 secs to respond.

Hide code cell source
#add 'too slow' feedback before the while loop
feedback_too_slow = visual.TextStim(win,text="TOO SLOW", height=40, color="black",pos=[0,0])
Hide code cell source
#full while loop
RTs=[] #set RT list
response_timer = core.Clock() # set response timer clock
key_pressed=False #need to initialize this for later
while True:
    cur_stim = random.choice(stimuli)
    word_stim.setText(cur_stim)
    word_stim.setColor(cur_stim)
    placeholder.draw()
    fixation.draw()
    win.flip()
    core.wait(.5)
    placeholder.draw()
    win.flip()
    core.wait(.5)
    placeholder.draw()
    word_stim.draw()  
    win.flip()
    response_timer.reset() # immediately after win.flip(), reset clock to measure RT
    key_pressed = event.waitKeys(keyList=valid_response_keys,maxWait=2) # maximum wait time of 2 s
    # add feedback
    # if key_pressed is still FALSE/ no response was registered, present too slow feedback
    if not key_pressed:
        feedback_too_slow.draw()
        win.flip()
        core.wait(1)
    elif key_pressed[0] == cur_stim[0]: #elif statement
        #correct response
        pass
    elif key_pressed[0] == 'q':
        break
    else:
        feedback_incorrect.draw()
        win.flip()
        core.wait(1)
    RTs.append(round(response_timer.getTime()*1000,0)) #add an RT to the list, rounded to the nearest millisecond
  1. Stroop it up! Add incongruent trials

Introduce incongruent trials by showing words in the “wrong” color, e.g., “yellow” printed in green. To do this, define a function called make_incongruent() which takes in a color as an argument and returns one of the other colors in stimuli that’s different from the one being passed in. Then set the color word’s color to this new value, thereby creating an incongruent trial.

Hide code cell source
#define a function to make colors incongurent
def make_incongruent(color):
    possible_incongruent_colors = [stimulus for stimulus in stimuli if stimulus != color]
    incongruent_color = random.choice(possible_incongruent_colors)
    return incongruent_color
Hide code cell source
#update how the text and color are set for the key stimulus
cur_word = random.choice(stimuli) #notice the change in variable name now that we have congruent and incongruent trials
trial_type = random.choice(trial_types) #we're just going to randomly pick the trial type (so it's 50/50 congruent/incongruent)

word_stim.setText(cur_word) #set text
if trial_type == 'incongruent':
    cur_color = make_incongruent(cur_word)
else:
    cur_color = cur_word
#notice that at this point cur_color is the color we're gonna set the word to. 
#It's taking into account the trial type 
word_stim.setColor(cur_color) #set color
  1. CHECKPOINT

The full code should now look like the code below.

Hide code cell source
import time
import sys
import os
import random
from psychopy import visual,event,core,gui

stimuli = ['red', 'orange', 'yellow', 'green', 'blue']
valid_response_keys = ['r', 'o', 'y', 'g', 'b','q']
trial_types = ['congruent','incongruent']

def make_incongruent(color):
    possible_incongruent_colors = [stimulus for stimulus in stimuli if stimulus != color]
    incongruent_color = random.choice(possible_incongruent_colors)
    return incongruent_color

win = visual.Window([800,600],color="gray", units='pix',checkTiming=False)
placeholder = visual.Rect(win,width=180,height=80, fillColor="lightgray",lineColor="black", lineWidth=6,pos=[0,0])
word_stim = visual.TextStim(win,text="", height=40, color="black",pos=[0,0])
instruction = visual.TextStim(win,text="Press the first letter of the ink color", height=20, color="black",pos=[0,-200],autoDraw=True)
#add fixation cross
fixation = visual.TextStim(win,height=40,color="black",text="+")
# add a new feedback TextStim before the while loop
feedback_incorrect = visual.TextStim(win,text="INCORRECT", height=40, color="black",pos=[0,0])
feedback_too_slow = visual.TextStim(win,text="TOO SLOW", height=40, color="black",pos=[0,0])

RTs=[] #set RT list
response_timer = core.Clock() # set response timer clock
key_pressed=False #need to initialize this for later
while True:

    cur_word = random.choice(stimuli) #notice the change in variable name now that we have congruent and incongruent trials
    trial_type = random.choice(trial_types) #we're just going to randomly pick the trial type (so it's 50/50 congruent/incongruent)

    word_stim.setText(cur_word) #set text
    if trial_type == 'incongruent':
        cur_color = make_incongruent(cur_word)
    else:
        cur_color = cur_word
    #notice that at this point cur_color is the color we're gonna set the word to. 
    #It's taking into account the trial type 
    word_stim.setColor(cur_color) #set color

    #show fixation
    placeholder.draw()
    fixation.draw()
    win.flip()
    core.wait(.5)

    #short inter stimulus interval
    placeholder.draw()
    win.flip()
    core.wait(.5)

    #draw word stimulus
    placeholder.draw()
    word_stim.draw()
    win.flip()

    #get response
    response_timer.reset() # immediately after win.flip(), reset clock to measure RT
    key_pressed = event.waitKeys(keyList=valid_response_keys,maxWait=2) # maximum wait time of 2 s
    RTs.append(round(response_timer.getTime()*1000,0)) #add an RT to the list, rounded to the nearest millisecond

    # add feedback
    # if key_pressed is still FALSE/ no response was registered, present too slow feedback
    if not key_pressed:
        feedback_too_slow.draw()
        win.flip()
        core.wait(1)
    elif key_pressed[0] == cur_color[0]:
        #correct response
        pass
    elif key_pressed[0] == 'q':
        break
    else:
        feedback_incorrect.draw()
        win.flip()
        core.wait(1)

print(RTs)

PART 2: EXTEND THE BASIC STROOP TASK INTO A FULL EXPERIMENT#

In Part 2, we’ll be extending the Stroop effect task you just coded into a more complete experiment that (1) generates stimulus lists that are then read in by the main script and (2) accepts runtime variables to assign participants to conditions and record participant codes.

We’ll use the all same stimuli, but introduce several variations:

a. We’ll introduce an orientation manipulation so that on 50% of trials the word is presented upside down (what effect do you think this will have on response times?)

b. The output will now be written to a results file.

We’ll also start modularizing the code so that one part of it is responsible for generating the trials, another for reading in the trial list, another for showing the stimuli, and another for writing the participant’s responses to a file.

  1. Create a generate trials file Complete the generate_trials() function below so that it writes a file with all the trials to be presented to the participant.

def generate_trials(subj_code, seed,num_repetitions=25):
    '''
    Writes a file named {subj_code_}trials.csv, one line per trial. Creates a trials subdirectory if one does not exist
    subj_code: a string corresponding to a participant's unique subject code
    seed: an integer specifying the random seed
    num_repetitions: integer specifying total times that combinations of trial type (congruent vs. incongruent) and orientation (upright vs. upside_down) should repeat (total number of trials = 4 * num_repetitions)
    '''
    import os
    import random

    # define general parameters and functions here
    
    # create a trials folder if it doesn't already exist
    try:
        os.mkdir('trials')
    except FileExistsError:
        print('Trials directory exists; proceeding to open file')
    f= open(f"trials/{subj_code}_trials.csv","w")

    #write header
    header = separator.join(["subj_code","seed","word", 'color','trial_type','orientation'])
    f.write(header+'\n')
    
    # write code to loop through creating and adding trials to the file here


    #close the file
    f.close()

Tip

The text at the start of the function explaining what it does and its arguments is called a “docstring”. This is the proper way to document what a function does. Unlike comments, which are completely ignored by the Python interpreter, docstrings are part of the function and can be accessed like so: print(generateTrials.__doc__).

The produced file should be a CSV and have the following format:

Col 1: subject code

Col 2: the word to be shown

Col 3: the color of the word

Col 4: whether the trial is ‘congruent’ or ‘incongruent’. 50% of the trials should be congruent and 50% incongruent (half/half).

Col 5: The orientation of the word, “upright”, or “upside_down”. Please ensure that 50% of the congruent trials are upright (and the remaining are upside-down). Same thing for incongruent trials – 50% should be upright and the remainder upside-down.

Make sure the trial order is randomized (i.e., shuffle the order) so that you don’t have all the congruent trials together, all the trials containg the same word together, etc.

Hide code cell source
def generate_trials(subj_code, seed, num_repetitions = 25):
    '''
    Writes a file named {subj_code_}trials.csv, one line per trial. Creates a trials subdirectory if one does not exist
    subj_code: a string corresponding to a participant's unique subject code
    seed: an integer specifying the random seed
    num_repetitions: integer (or string) specifying total times that combinations of trial type (congruent vs. incongruent) and orientation (upright vs. upside_down) should repeat (total number of trials = 4 * num_repetitions)
    '''
    import os
    import random

    # define general parameters and functions here
    separator = ","
    colors = ['red', 'orange', 'yellow', 'green', 'blue']
    trial_types = ["congruent","incongruent"]
    orientations = ["upright","upside_down"]
    num_repetitions = int(num_repetitions)

    #set seed
    random.seed(int(seed))

    #define a function to make colors incongurent
    def make_incongruent(color, stimuli):
        possible_incongruent_colors = [stimulus for stimulus in stimuli if stimulus != color]
        incongruent_color = random.choice(possible_incongruent_colors)
        return incongruent_color
    
    # create a trials folder if it doesn't already exist
    try:
        os.mkdir('trials')
    except FileExistsError:
        print('Trials directory exists; proceeding to open file')
    f= open(f"trials/{subj_code}_trials.csv","w")

    #write header
    header = separator.join(["subj_code","seed","word", 'color','trial_type','orientation'])
    f.write(header+'\n')
    
    # write code to loop through creating trials here
    trial_data = []
    for i in range(num_repetitions):
        for cur_trial_type in trial_types:
            for cur_orientation in orientations:
                cur_word = random.choice(colors)
                if cur_trial_type == 'incongruent':
                    cur_color = make_incongruent(cur_word,colors)
                else:
                    cur_color = cur_word
                trial_data.append([subj_code, seed, cur_word, cur_color, cur_trial_type, cur_orientation])
                
    #shuffle the list
    random.shuffle(trial_data)

    #write the trials to the trials file
    for cur_trial in trial_data:
        f.write(separator.join(map(str,cur_trial))+'\n')

    #close the file
    f.close()
  1. Runtime variables

Extend the script you wrote for Part 1 to accept these 2 runtime variables using a GUI box:

  • subject code (any string, but conventionally something like stroop_101, stroop_102, etc.)

  • seed (any integer)

  • number of repetitions (any integer)

The entered values should be stored in a dictionary called runtime_vars. After the values are collected, the dictionary might look like this:

runtime_vars= {'subj_code':'stroop_101','seed': 101, 'num_reps': 25}

Note

This dictionary must be populated dynamically. You should not be hard-coding any of these values

Hide code cell source
#add the following code to the main stroop task

#function for collecting runtime variables
def get_runtime_vars(vars_to_get,order,exp_version="Stroop"):
    #Get run time variables, see http://www.psychopy.org/api/gui.html for explanation
    infoDlg = gui.DlgFromDict(dictionary=vars_to_get, title=exp_version, order=order)
    if infoDlg.OK:
        return vars_to_get
    else: 
        print('User Cancelled')

# get the runtime variables
order =  ['subj_code','seed','num_reps']
runtime_vars = get_runtime_vars({'subj_code':'stroop_101','seed': 101, 'num_reps': 25}, order)
  1. Read in a trials file

Extend the script further to read in the trials data-file. Once its read in, step through the file, one trial (i.e., line) at a time & show the appropriate stimuli. For example, if trial 3 in the trial file shows the word “green” in red font in an upside-down orientation, then that’s what your experiments should be doing on trial 3. Make sure to also now set the stimulus to the appropriate orientation on each trial!

Hide code cell source
#import the generate files trial at the top of the main script
from generate_trials import generate_trials
Hide code cell source
#add the import_trials function we've used in previous assignments
def import_trials (trial_filename, col_names=None, separator=','):
    trial_file = open(trial_filename, 'r')
 
    if col_names is None:
        # Assume the first row contains the column names
        col_names = trial_file.readline().rstrip().split(separator)
    trials_list = []
    for cur_trial in trial_file:
        cur_trial = cur_trial.rstrip().split(separator)
        assert len(cur_trial) == len(col_names) # make sure the number of column names = number of columns
        trial_dict = dict(zip(col_names, cur_trial))
        trials_list.append(trial_dict)
    return trials_list
Hide code cell source
# generate a trial list
generate_trials(runtime_vars['subj_code'],runtime_vars['seed'],runtime_vars['num_reps'])

#read in trials
trial_path = os.path.join(os.getcwd(),'trials',runtime_vars['subj_code']+'_trials.csv')
trial_list = import_trials(trial_path)
print(trial_list)
Hide code cell source
#new trial loop
for cur_trial in trial_list:

    cur_word = cur_trial['word']
    cur_color = cur_trial['color']
    trial_type = cur_trial['trial_type']
    cur_ori = cur_trial['orientation']

    word_stim.setText(cur_word) #set text
    word_stim.setColor(cur_color) #set color

    if cur_ori=='upside_down':
        word_stim.setOri(180)
    else:
        word_stim.setOri(0)

    #show fixation
    placeholder.draw()
    fixation.draw()
    win.flip()
    core.wait(.5)

    #short inter stimulus interval
    placeholder.draw()
    win.flip()
    core.wait(.5)

    #draw word stimulus
    placeholder.draw()
    word_stim.draw()
    win.flip()

    #get response
    response_timer.reset() # immediately after win.flip(), reset clock to measure RT
    key_pressed = event.waitKeys(keyList=valid_response_keys,maxWait=2) # maximum wait time of 2 s
    rt = round(response_timer.getTime()*1000,0)

    # add feedback
    # if key_pressed is still FALSE/ no response was registered, present too slow feedback
    if not key_pressed:
        feedback_too_slow.draw()
        win.flip()
        core.wait(1)
    elif key_pressed[0] == cur_color[0]:
        #correct response
        pass
    elif key_pressed[0] == 'q':
        break
    else:
        feedback_incorrect.draw()
        win.flip()
        core.wait(1)
  1. Write the data

Now, open a data file writes all the trial information and responses to a file after every response.

You should be writing to the file after every trial (not at the very end). Make sure your data is written to data/subject-code_data.csv where subject-code is the subject code entered at runtime and data/ is a subdirectory that will contain all the data files. Your code should record one line per response and record responses as they come in. If a user quits after trial 50, the data file should have recorded those 50 responses. Here’s an example of what your output file should look like (note that the first line contains column name).

subj_code

seed

word

color

trial_type

orientation

trial_num

response

is_correct

rt

stroop_101

101

green

blue

incongruent

upright

1

b

1

898

stroop_101

101

green

green

congruent

upright

2

g

1

754

stroop_101

101

yellow

red

incongruent

upside_down

3

y

0

902

Note

Use a CSV format as before: fields are separated by commas

Challenge!

Ensure that you can’t run the same participant code twice. If you enter a participant code that’s already been entered before, PsychoPy should pop-up a warning box saying “Participant code already exists”. This should prevent you from overwriting an existing data-file.

Hide code cell source
#open data file and write header
try:
    os.mkdir('data')
    print('Data directory did not exist. Created data/')
except FileExistsError:
    pass 
separator=","
data_file = open(os.path.join(os.getcwd(),'data',runtime_vars['subj_code']+'_data.csv'),'w')
header = separator.join(['subj_code','seed', 'word','color','trial_type','orientation','trial_num','response','is_correct','rt'])
data_file.write(header+'\n')

response_timer = core.Clock() # set response timer clock
# trial loop
# add a trial number
trial_num = 1
for cur_trial in trial_list:

    cur_word = cur_trial['word']
    cur_color = cur_trial['color']
    trial_type = cur_trial['trial_type']
    cur_ori = cur_trial['orientation']

    word_stim.setText(cur_word) #set text
    word_stim.setColor(cur_color) #set color

    if cur_ori=='upside_down':
        word_stim.setOri(180)
    else:
        word_stim.setOri(0)

    #show fixation
    placeholder.draw()
    fixation.draw()
    win.flip()
    core.wait(.5)

    #short inter stimulus interval
    placeholder.draw()
    win.flip()
    core.wait(.5)

    #draw word stimulus
    placeholder.draw()
    word_stim.draw()
    win.flip()

    #get response
    response_timer.reset() # immediately after win.flip(), reset clock to measure RT
    key_pressed = event.waitKeys(keyList=valid_response_keys,maxWait=2) # maximum wait time of 2 s
    rt = round(response_timer.getTime()*1000,0)

    # add feedback
    # if key_pressed is still FALSE/ no response was registered, present too slow feedback
    if not key_pressed:
        is_correct = 0
        response = "NA"
        feedback_too_slow.draw()
        win.flip()
        core.wait(1)
    elif key_pressed[0] == cur_color[0]: 
        is_correct = 1
        response = key_pressed[0]
        #correct response
        pass
    else:
        is_correct = 0
        response = key_pressed[0]
        feedback_incorrect.draw()
        win.flip()
        core.wait(1)
    
    #writing a response
    response_list=[cur_trial[_] for _ in cur_trial]
    print(response_list)
	#write dependent variables
    response_list.extend([trial_num,response,is_correct,rt])
    responses = map(str,response_list)
    print(response_list)
    line = separator.join([str(i) for i in response_list])
    data_file.write(line+'\n')

    # increment trial number
    trial_num += 1

#close the data file at the end of the experiment
data_file.close()
  1. FINAL MAIN CODE

The full main experiment file can should now look something like the code below.

Hide code cell source
import time
import sys
import os
import random
from psychopy import visual,event,core,gui
from generate_trials import generate_trials

stimuli = ['red', 'orange', 'yellow', 'green', 'blue']
valid_response_keys = ['r', 'o', 'y', 'g', 'b']
trial_types = ['congruent','incongruent']

#function for collecting runtime variables
def get_runtime_vars(vars_to_get,order,exp_version="Stroop"):
    #Get run time variables, see http://www.psychopy.org/api/gui.html for explanation
    infoDlg = gui.DlgFromDict(dictionary=vars_to_get, title=exp_version, order=order)
    if infoDlg.OK:
        return vars_to_get
    else: 
        print('User Cancelled')

#function for reading in trials
def import_trials(trial_filename, col_names=None, separator=','):
    trial_file = open(trial_filename, 'r')
 
    if col_names is None:
        # Assume the first row contains the column names
        col_names = trial_file.readline().rstrip().split(separator)
    trials_list = []
    for cur_trial in trial_file:
        cur_trial = cur_trial.rstrip().split(separator)
        assert len(cur_trial) == len(col_names) # make sure the number of column names = number of columns
        trial_dict = dict(zip(col_names, cur_trial))
        trials_list.append(trial_dict)
    return trials_list

win = visual.Window([800,600],color="gray", units='pix',checkTiming=False)
placeholder = visual.Rect(win,width=180,height=80, fillColor="lightgray",lineColor="black", lineWidth=6,pos=[0,0])
word_stim = visual.TextStim(win,text="", height=40, color="black",pos=[0,0])
instruction = visual.TextStim(win,text="Press the first letter of the ink color", height=20, color="black",pos=[0,-200],autoDraw=True)
#add fixation cross
fixation = visual.TextStim(win,height=40,color="black",text="+")
# add a new feedback TextStim before the while loop
feedback_incorrect = visual.TextStim(win,text="INCORRECT", height=40, color="black",pos=[0,0])
feedback_too_slow = visual.TextStim(win,text="TOO SLOW", height=40, color="black",pos=[0,0])

# get the runtime variables
order =  ['subj_code','seed','num_reps']
runtime_vars = get_runtime_vars({'subj_code':'stroop_101','seed': 101, 'num_reps': 25}, order)

# generate a trial list
generate_trials(runtime_vars['subj_code'],runtime_vars['seed'],runtime_vars['num_reps'])

#read in trials
trial_path = os.path.join(os.getcwd(),'trials',runtime_vars['subj_code']+'_trials.csv')
trial_list = import_trials(trial_path)
print(trial_list)

#open data file and write header
try:
    os.mkdir('data')
    print('Data directory did not exist. Created data/')
except FileExistsError:
    pass 
separator=","
data_file = open(os.path.join(os.getcwd(),'data',runtime_vars['subj_code']+'_data.csv'),'w')
header = separator.join(['subj_code','seed', 'word','color','trial_type','orientation','trial_num','response','is_correct','rt'])
data_file.write(header+'\n')

response_timer = core.Clock() # set response timer clock
# trial loop
# add a trial number
trial_num = 1
for cur_trial in trial_list:

    cur_word = cur_trial['word']
    cur_color = cur_trial['color']
    trial_type = cur_trial['trial_type']
    cur_ori = cur_trial['orientation']

    word_stim.setText(cur_word) #set text
    word_stim.setColor(cur_color) #set color

    if cur_ori=='upside_down':
        word_stim.setOri(180)
    else:
        word_stim.setOri(0)

    #show fixation
    placeholder.draw()
    fixation.draw()
    win.flip()
    core.wait(.5)

    #short inter stimulus interval
    placeholder.draw()
    win.flip()
    core.wait(.5)

    #draw word stimulus
    placeholder.draw()
    word_stim.draw()
    win.flip()

    #get response
    response_timer.reset() # immediately after win.flip(), reset clock to measure RT
    key_pressed = event.waitKeys(keyList=valid_response_keys,maxWait=2) # maximum wait time of 2 s
    rt = round(response_timer.getTime()*1000,0)

    # add feedback
    # if key_pressed is still FALSE/ no response was registered, present too slow feedback
    if not key_pressed:
        is_correct = 0
        response = "NA"
        feedback_too_slow.draw()
        win.flip()
        core.wait(1)
    elif key_pressed[0] == cur_color[0]: 
        is_correct = 1
        response = key_pressed[0]
        #correct response
        pass
    else:
        is_correct = 0
        response = key_pressed[0]
        feedback_incorrect.draw()
        win.flip()
        core.wait(1)
    
    #writing a response
    response_list=[cur_trial[_] for _ in cur_trial]
    print(response_list)
	#write dependent variables
    response_list.extend([trial_num,response,is_correct,rt])
    responses = map(str,response_list)
    print(response_list)
    line = separator.join([str(i) for i in response_list])
    data_file.write(line+'\n')

    # increment trial number
    trial_num += 1

#close the data file at the end of the experiment
data_file.close()

Final main code after completing the challenge:

Hide code cell source
import time
import sys
import os
import random
from psychopy import visual,event,core,gui
from generate_trials import generate_trials

stimuli = ['red', 'orange', 'yellow', 'green', 'blue']
valid_response_keys = ['r', 'o', 'y', 'g', 'b']
trial_types = ['congruent','incongruent']

def popupError(text):
    errorDlg = gui.Dlg(title="Error", pos=(200,400))
    errorDlg.addText('Error: '+text, color='Red')
    errorDlg.show()

def open_data_file(filename):
    """
    Open data file, creating data/ directory as necesasry
    """
    if os.path.isfile(filename):
        popupError(f'Error {filename} already exists')
        return False
    else:
        try:
            data_file = open(filename,'w')
        except FileNotFoundError:
            print(f'Could not open {filename} for writing')
    return data_file

#function for collecting runtime variables
#now throws an error if the data file already exists
def get_runtime_vars(vars_to_get,order,exp_version="Exercise 3"):
    """
    Get run time variables, see http://www.psychopy.org/api/gui.html for explanation
    Return filled in runtime variables and an opened data file
    """
    while True:
        infoDlg = gui.DlgFromDict(dictionary=vars_to_get, title=exp_version, order=order,copyDict=True) 
        populated_runtime_vars = infoDlg.dictionary 
        data_file = open_data_file(os.path.join(os.getcwd(),'data',populated_runtime_vars['subj_code']+'_data.csv'))
        if 'Choose' in list(populated_runtime_vars.values()):
            popupError('Need to choose a value from each dropdown box')
        elif infoDlg.OK and data_file:
            return populated_runtime_vars
        elif not infoDlg.OK:
            print('User Cancelled')
            sys.exit()

#function for reading in trials
def import_trials(trial_filename, col_names=None, separator=','):
    trial_file = open(trial_filename, 'r')
 
    if col_names is None:
        # Assume the first row contains the column names
        col_names = trial_file.readline().rstrip().split(separator)
    trials_list = []
    for cur_trial in trial_file:
        cur_trial = cur_trial.rstrip().split(separator)
        assert len(cur_trial) == len(col_names) # make sure the number of column names = number of columns
        trial_dict = dict(zip(col_names, cur_trial))
        trials_list.append(trial_dict)
    return trials_list

win = visual.Window([800,600],color="gray", units='pix',checkTiming=False)
placeholder = visual.Rect(win,width=180,height=80, fillColor="lightgray",lineColor="black", lineWidth=6,pos=[0,0])
word_stim = visual.TextStim(win,text="", height=40, color="black",pos=[0,0])
instruction = visual.TextStim(win,text="Press the first letter of the ink color", height=20, color="black",pos=[0,-200],autoDraw=True)
#add fixation cross
fixation = visual.TextStim(win,height=40,color="black",text="+")
# add a new feedback TextStim before the while loop
feedback_incorrect = visual.TextStim(win,text="INCORRECT", height=40, color="black",pos=[0,0])
feedback_too_slow = visual.TextStim(win,text="TOO SLOW", height=40, color="black",pos=[0,0])

# get the runtime variables
order =  ['subj_code','seed','num_reps']
runtime_vars = get_runtime_vars({'subj_code':'stroop_101','seed': 101, 'num_reps': 25}, order)
print(runtime_vars)

# generate a trial list
generate_trials(runtime_vars['subj_code'],runtime_vars['seed'],runtime_vars['num_reps'])

#read in trials
trial_path = os.path.join(os.getcwd(),'trials',runtime_vars['subj_code']+'_trials.csv')
trial_list = import_trials(trial_path)
print(trial_list)

#open data file and write header
try:
    os.mkdir('data')
    print('Data directory did not exist. Created data/')
except FileExistsError:
    pass 
separator=","
data_file = open(os.path.join(os.getcwd(),'data',runtime_vars['subj_code']+'_data.csv'),'w')
header = separator.join(['subj_code','seed', 'word','color','trial_type','orientation','trial_num','response','is_correct','rt'])
data_file.write(header+'\n')

response_timer = core.Clock() # set response timer clock
# trial loop
# add a trial number
trial_num = 1
for cur_trial in trial_list:

    cur_word = cur_trial['word']
    cur_color = cur_trial['color']
    trial_type = cur_trial['trial_type']
    cur_ori = cur_trial['orientation']

    word_stim.setText(cur_word) #set text
    word_stim.setColor(cur_color) #set color

    if cur_ori=='upside_down':
        word_stim.setOri(180)
    else:
        word_stim.setOri(0)

    #show fixation
    placeholder.draw()
    fixation.draw()
    win.flip()
    core.wait(.5)

    #short inter stimulus interval
    placeholder.draw()
    win.flip()
    core.wait(.5)

    #draw word stimulus
    placeholder.draw()
    word_stim.draw()
    win.flip()

    #get response
    response_timer.reset() # immediately after win.flip(), reset clock to measure RT
    key_pressed = event.waitKeys(keyList=valid_response_keys,maxWait=2) # maximum wait time of 2 s
    rt = round(response_timer.getTime()*1000,0)

    # add feedback
    # if key_pressed is still FALSE/ no response was registered, present too slow feedback
    if not key_pressed:
        is_correct = 0
        response = "NA"
        feedback_too_slow.draw()
        win.flip()
        core.wait(1)
    elif key_pressed[0] == cur_color[0]: 
        is_correct = 1
        response = key_pressed[0]
        #correct response
        pass
    else:
        is_correct = 0
        response = key_pressed[0]
        feedback_incorrect.draw()
        win.flip()
        core.wait(1)
    
    #writing a response
    response_list=[cur_trial[_] for _ in cur_trial]
    print(response_list)
	#write dependent variables
    response_list.extend([trial_num,response,is_correct,rt])
    responses = map(str,response_list)
    print(response_list)
    line = separator.join([str(i) for i in response_list])
    data_file.write(line+'\n')

    # increment trial number
    trial_num += 1

#close the data file at the end of the experiment
data_file.close()