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 calledstroop.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#
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.
Show 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="+")
Show 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()
AutoDraw
Use the autoDraw functionality to automatically draw the instruction
for every window flip. This simplifies the code above quite a bit!
Show 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)
Show 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()
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'])
Show 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']
Show 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
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.
Show 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)
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.
Show 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])
Show 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)
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.
Show 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])
Show 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
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.
Show 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
Show 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
CHECKPOINT
The full code should now look like the code below.
Show 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.
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.
Show 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()
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
Show 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)
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!
Show code cell source
#import the generate files trial at the top of the main script
from generate_trials import generate_trials
Show 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
Show 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)
Show 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)
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.
Show 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()
FINAL MAIN CODE
The full main experiment file can should now look something like the code below.
Show 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:
Show 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()