I am using PsychoPy version 1.82.01 in the Builder View. My knowledge of Python is still very limited. I've run into a roadblock with a task and would appreciate any advice from the community!
The task goes like this:
Subject is first presented with two target triangles: one pointing to the left, one pointing to the right. The subject is instructed to press the corresponding left or right arrow keys when they spot either one of these targets.
A sample of the Excel conditions file looks like this:
angle_01 angle_02 angle_03 angle_04 corrAns
0 45 135 180 none
315 45 135 180 none
315 225 135 180 none
270 225 135 180 left
0 225 135 180 none
**The subject is then presented with an array of 4 triangles. I created these 4 triangles in Builder. Each triangle is set to have a 1 second duration. The orientation of each triangle is determined by the conditions file--triangle 1 orientation field is set to "angle_01," triangle 2 orientation is set to "angle_02" and so forth. So, if you can see in the snippet above, I created each row in the conditions file such that only one triangle changes its orientation every second. When one of the triangle changes like this, it is called a "distractor shift."
After a number of these distractor shifts, one of the triangles shifts into the "target" position (either pointing left or right, 90 or 270 degrees), at which point the person is meant to respond with a keypress. This is the "target shift." I have the orientations for all triangles specified in an Excel conditions file. Right now I have this set as a sequential loop, with fixed numbers of distractor shifts, for the sake of simplicity.
However, my ultimate goal is to have a variable number of distractor shifts before the target shift in each trial, and for each trial to end with a fixation cross before moving onto the next trial. I am having a hard time figuring out what code to use to make this possible. I started to make the loop proceed through the conditions file randomly, but the result is that every triangle changes its orientation at every rep (rather than just one at a time). Have I gone about this in the wrong way? The full code pasted below.
Thank you!
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
This experiment was created using PsychoPy2 Experiment Builder (v1.82.01), Thu Aug 27 10:56:53 2015
If you publish work using this script please cite the relevant 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 division # so that 1/3=0.333 instead of 1/3=0
from psychopy import visual, core, data, event, logging, sound, gui
from psychopy.constants import * # things like STARTED, FINISHED
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
# Ensure that relative paths start from the same directory as this script
_thisDir = os.path.dirname(os.path.abspath(__file__))
os.chdir(_thisDir)
# Store info about the experiment session
expName = 'simultaneous_04' # 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 + '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=(1366, 768), fullscr=True, screen=0, allowGUI=False, allowStencil=False,
monitor='testMonitor', color=[-1,-1,-1], colorSpace='rgb',
blendMode='avg', useFBO=True,
)
# store frame rate of monitor if we can measure it successfully
expInfo['frameRate']=win.getActualFrameRate()
if expInfo['frameRate']!=None:
frameDur = 1.0/round(expInfo['frameRate'])
else:
frameDur = 1.0/60.0 # couldn't get a reliable measure so guess
# Initialize components for Routine "Target_Presentation"
Target_PresentationClock = core.Clock()
target_triangle = visual.ShapeStim(win=win, name='target_triangle',units='cm',
vertices = [[-[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [+[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [0,[5.5,5.89][1]/2.0]],
ori=1.0, pos=[-7, 3],
lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
fillColor=[1,1,1], fillColorSpace='rgb',
opacity=1,depth=0.0,
interpolate=True)
text_4 = visual.TextStim(win=win, ori=0, name='text_4',
text='Press the LEFT ARROW key when you spot the triangle pointing to the left.\n\nPress the RIGHT ARROW key when you spot the triangle pointing to the right.\n\nPress the spacebar when you are ready to begin.', font='Arial',
pos=[0,-0.5], height=0.08, wrapWidth=None,
color='white', colorSpace='rgb', opacity=1,
depth=-1.0)
target_triangle_02 = visual.ShapeStim(win=win, name='target_triangle_02',units='cm',
vertices = [[-[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [+[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [0,[5.5,5.89][1]/2.0]],
ori=90, pos=[7,3],
lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
fillColor=[1,1,1], fillColorSpace='rgb',
opacity=1,depth=-2.0,
interpolate=True)
text_5 = visual.TextStim(win=win, ori=0, name='text_5',
text='or', font='Arial',
pos=[0, 0.25], height=0.1, wrapWidth=None,
color='white', colorSpace='rgb', opacity=1,
depth=-3.0)
# Initialize components for Routine "Start_Fixation_Cross"
Start_Fixation_CrossClock = core.Clock()
text_2 = visual.TextStim(win=win, ori=0, name='text_2',
text='+', font='Arial',
pos=[0, 0], height=0.1, wrapWidth=None,
color='white', colorSpace='rgb', opacity=1,
depth=0.0)
# Initialize components for Routine "Trial"
TrialClock = core.Clock()
triangle_01 = visual.ShapeStim(win=win, name='triangle_01',units='cm',
vertices = [[-[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [+[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [0,[5.5,5.89][1]/2.0]],
ori=1.0, pos=[-8.2,6.2],
lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
fillColor=[1,1,1], fillColorSpace='rgb',
opacity=1,depth=-1.0,
interpolate=True)
triangle_02 = visual.ShapeStim(win=win, name='triangle_02',units='cm',
vertices = [[-[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [+[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [0,[5.5,5.89][1]/2.0]],
ori=1.0, pos=[8.2,6.2],
lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
fillColor=[1,1,1], fillColorSpace='rgb',
opacity=1,depth=-2.0,
interpolate=True)
triangle_03 = visual.ShapeStim(win=win, name='triangle_03',units='cm',
vertices = [[-[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [+[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [0,[5.5,5.89][1]/2.0]],
ori=1.0, pos=[8.2,-6.2],
lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
fillColor=[1,1,1], fillColorSpace='rgb',
opacity=1,depth=-3.0,
interpolate=True)
triangle_04 = visual.ShapeStim(win=win, name='triangle_04',units='cm',
vertices = [[-[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [+[5.5,5.89][0]/2.0,-[5.5,5.89][1]/2.0], [0,[5.5,5.89][1]/2.0]],
ori=1.0, pos=[-8.2,-6.2],
lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
fillColor=[1,1,1], fillColorSpace='rgb',
opacity=1,depth=-4.0,
interpolate=True)
# Initialize components for Routine "Ending"
EndingClock = core.Clock()
text_3 = visual.TextStim(win=win, ori=0, name='text_3',
text='This concludes the experiment. \n\nThank you for participating!', font='Arial',
pos=[0, 0], height=0.1, wrapWidth=None,
color='white', colorSpace='rgb', opacity=1,
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
#------Prepare to start Routine "Target_Presentation"-------
t = 0
Target_PresentationClock.reset() # clock
frameN = -1
# update component parameters for each repeat
target_triangle.setOri(270)
key_resp_3 = event.BuilderKeyResponse() # create an object of type KeyResponse
key_resp_3.status = NOT_STARTED
# keep track of which components have finished
Target_PresentationComponents = []
Target_PresentationComponents.append(target_triangle)
Target_PresentationComponents.append(text_4)
Target_PresentationComponents.append(target_triangle_02)
Target_PresentationComponents.append(text_5)
Target_PresentationComponents.append(key_resp_3)
for thisComponent in Target_PresentationComponents:
if hasattr(thisComponent, 'status'):
thisComponent.status = NOT_STARTED
#-------Start Routine "Target_Presentation"-------
continueRoutine = True
while continueRoutine:
# get current time
t = Target_PresentationClock.getTime()
frameN = frameN + 1 # number of completed frames (so 0 is the first frame)
# update/draw components on each frame
# *target_triangle* updates
if t >= 0.0 and target_triangle.status == NOT_STARTED:
# keep track of start time/frame for later
target_triangle.tStart = t # underestimates by a little under one frame
target_triangle.frameNStart = frameN # exact frame index
target_triangle.setAutoDraw(True)
# *text_4* updates
if t >= 0.0 and text_4.status == NOT_STARTED:
# keep track of start time/frame for later
text_4.tStart = t # underestimates by a little under one frame
text_4.frameNStart = frameN # exact frame index
text_4.setAutoDraw(True)
# *target_triangle_02* updates
if t >= 0.0 and target_triangle_02.status == NOT_STARTED:
# keep track of start time/frame for later
target_triangle_02.tStart = t # underestimates by a little under one frame
target_triangle_02.frameNStart = frameN # exact frame index
target_triangle_02.setAutoDraw(True)
# *text_5* updates
if t >= 0.0 and text_5.status == NOT_STARTED:
# keep track of start time/frame for later
text_5.tStart = t # underestimates by a little under one frame
text_5.frameNStart = frameN # exact frame index
text_5.setAutoDraw(True)
# *key_resp_3* updates
if t >= 0.0 and key_resp_3.status == NOT_STARTED:
# keep track of start time/frame for later
key_resp_3.tStart = t # underestimates by a little under one frame
key_resp_3.frameNStart = frameN # exact frame index
key_resp_3.status = STARTED
# keyboard checking is just starting
key_resp_3.clock.reset() # now t=0
event.clearEvents(eventType='keyboard')
if key_resp_3.status == STARTED:
theseKeys = event.getKeys(keyList=['space'])
# check for quit:
if "escape" in theseKeys:
endExpNow = True
if len(theseKeys) > 0: # at least one key was pressed
key_resp_3.keys = theseKeys[-1] # just the last key pressed
key_resp_3.rt = key_resp_3.clock.getTime()
# a response ends the routine
continueRoutine = False
# 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 Target_PresentationComponents:
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 "Target_Presentation"-------
for thisComponent in Target_PresentationComponents:
if hasattr(thisComponent, "setAutoDraw"):
thisComponent.setAutoDraw(False)
# check responses
if key_resp_3.keys in ['', [], None]: # No response was made
key_resp_3.keys=None
# store data for thisExp (ExperimentHandler)
thisExp.addData('key_resp_3.keys',key_resp_3.keys)
if key_resp_3.keys != None: # we had a response
thisExp.addData('key_resp_3.rt', key_resp_3.rt)
thisExp.nextEntry()
# the Routine "Target_Presentation" was not non-slip safe, so reset the non-slip timer
routineTimer.reset()
#------Prepare to start Routine "Start_Fixation_Cross"-------
t = 0
Start_Fixation_CrossClock.reset() # clock
frameN = -1
routineTimer.add(1.000000)
# update component parameters for each repeat
# keep track of which components have finished
Start_Fixation_CrossComponents = []
Start_Fixation_CrossComponents.append(text_2)
for thisComponent in Start_Fixation_CrossComponents:
if hasattr(thisComponent, 'status'):
thisComponent.status = NOT_STARTED
#-------Start Routine "Start_Fixation_Cross"-------
continueRoutine = True
while continueRoutine and routineTimer.getTime() > 0:
# get current time
t = Start_Fixation_CrossClock.getTime()
frameN = frameN + 1 # number of completed frames (so 0 is the first frame)
# update/draw components on each frame
# *text_2* updates
if t >= 0.0 and text_2.status == NOT_STARTED:
# keep track of start time/frame for later
text_2.tStart = t # underestimates by a little under one frame
text_2.frameNStart = frameN # exact frame index
text_2.setAutoDraw(True)
if text_2.status == STARTED and t >= (0.0 + (1.0-win.monitorFramePeriod*0.75)): #most of one frame period left
text_2.setAutoDraw(False)
# 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 Start_Fixation_CrossComponents:
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 "Start_Fixation_Cross"-------
for thisComponent in Start_Fixation_CrossComponents:
if hasattr(thisComponent, "setAutoDraw"):
thisComponent.setAutoDraw(False)
# set up handler to look after randomisation of conditions etc
TrialLoop = data.TrialHandler(nReps=1, method='sequential',
extraInfo=expInfo, originPath=None,
trialList=data.importConditions('distractor_conditions.xlsx'),
seed=None, name='TrialLoop')
thisExp.addLoop(TrialLoop) # add the loop to the experiment
thisTrialLoop = TrialLoop.trialList[0] # so we can initialise stimuli with some values
# abbreviate parameter names if possible (e.g. rgb=thisTrialLoop.rgb)
if thisTrialLoop != None:
for paramName in thisTrialLoop.keys():
exec(paramName + '= thisTrialLoop.' + paramName)
for thisTrialLoop in TrialLoop:
currentLoop = TrialLoop
# abbreviate parameter names if possible (e.g. rgb = thisTrialLoop.rgb)
if thisTrialLoop != None:
for paramName in thisTrialLoop.keys():
exec(paramName + '= thisTrialLoop.' + paramName)
#------Prepare to start Routine "Trial"-------
t = 0
TrialClock.reset() # clock
frameN = -1
routineTimer.add(1.000000)
# update component parameters for each repeat
triangle_01.setOri(distractor_angle_01)
triangle_02.setOri(distractor_angle_02)
triangle_03.setOri(distractor_angle_03)
triangle_04.setOri(distractor_angle_04)
trial_resp = event.BuilderKeyResponse() # create an object of type KeyResponse
trial_resp.status = NOT_STARTED
# keep track of which components have finished
TrialComponents = []
TrialComponents.append(triangle_01)
TrialComponents.append(triangle_02)
TrialComponents.append(triangle_03)
TrialComponents.append(triangle_04)
TrialComponents.append(trial_resp)
for thisComponent in TrialComponents:
if hasattr(thisComponent, 'status'):
thisComponent.status = NOT_STARTED
#-------Start Routine "Trial"-------
continueRoutine = True
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
# *triangle_01* updates
if t >= 0 and triangle_01.status == NOT_STARTED:
# keep track of start time/frame for later
triangle_01.tStart = t # underestimates by a little under one frame
triangle_01.frameNStart = frameN # exact frame index
triangle_01.setAutoDraw(True)
if triangle_01.status == STARTED and t >= (0 + (1.0-win.monitorFramePeriod*0.75)): #most of one frame period left
triangle_01.setAutoDraw(False)
# *triangle_02* updates
if t >= 0 and triangle_02.status == NOT_STARTED:
# keep track of start time/frame for later
triangle_02.tStart = t # underestimates by a little under one frame
triangle_02.frameNStart = frameN # exact frame index
triangle_02.setAutoDraw(True)
if triangle_02.status == STARTED and t >= (0 + (1.0-win.monitorFramePeriod*0.75)): #most of one frame period left
triangle_02.setAutoDraw(False)
# *triangle_03* updates
if t >= 0 and triangle_03.status == NOT_STARTED:
# keep track of start time/frame for later
triangle_03.tStart = t # underestimates by a little under one frame
triangle_03.frameNStart = frameN # exact frame index
triangle_03.setAutoDraw(True)
if triangle_03.status == STARTED and t >= (0 + (1.0-win.monitorFramePeriod*0.75)): #most of one frame period left
triangle_03.setAutoDraw(False)
# *triangle_04* updates
if t >= 0 and triangle_04.status == NOT_STARTED:
# keep track of start time/frame for later
triangle_04.tStart = t # underestimates by a little under one frame
triangle_04.frameNStart = frameN # exact frame index
triangle_04.setAutoDraw(True)
if triangle_04.status == STARTED and t >= (0 + (1.0-win.monitorFramePeriod*0.75)): #most of one frame period left
triangle_04.setAutoDraw(False)
# *trial_resp* updates
if t >= 0.0 and trial_resp.status == NOT_STARTED:
# keep track of start time/frame for later
trial_resp.tStart = t # underestimates by a little under one frame
trial_resp.frameNStart = frameN # exact frame index
trial_resp.status = STARTED
# keyboard checking is just starting
trial_resp.clock.reset() # now t=0
event.clearEvents(eventType='keyboard')
if trial_resp.status == STARTED and t >= (0.0 + (1.0-win.monitorFramePeriod*0.75)): #most of one frame period left
trial_resp.status = STOPPED
if trial_resp.status == STARTED:
theseKeys = event.getKeys(keyList=['left', 'right'])
# check for quit:
if "escape" in theseKeys:
endExpNow = True
if len(theseKeys) > 0: # at least one key was pressed
trial_resp.keys = theseKeys[-1] # just the last key pressed
trial_resp.rt = trial_resp.clock.getTime()
# was this 'correct'?
if (trial_resp.keys == str(corrAns)) or (trial_resp.keys == corrAns):
trial_resp.corr = 1
else:
trial_resp.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 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 trial_resp.keys in ['', [], None]: # No response was made
trial_resp.keys=None
# was no response the correct answer?!
if str(corrAns).lower() == 'none': trial_resp.corr = 1 # correct non-response
else: trial_resp.corr = 0 # failed to respond (incorrectly)
# store data for TrialLoop (TrialHandler)
TrialLoop.addData('trial_resp.keys',trial_resp.keys)
TrialLoop.addData('trial_resp.corr', trial_resp.corr)
if trial_resp.keys != None: # we had a response
TrialLoop.addData('trial_resp.rt', trial_resp.rt)
thisExp.nextEntry()
# completed 1 repeats of 'TrialLoop'
#------Prepare to start Routine "Ending"-------
t = 0
EndingClock.reset() # clock
frameN = -1
routineTimer.add(3.000000)
# update component parameters for each repeat
# keep track of which components have finished
EndingComponents = []
EndingComponents.append(text_3)
for thisComponent in EndingComponents:
if hasattr(thisComponent, 'status'):
thisComponent.status = NOT_STARTED
#-------Start Routine "Ending"-------
continueRoutine = True
while continueRoutine and routineTimer.getTime() > 0:
# get current time
t = EndingClock.getTime()
frameN = frameN + 1 # number of completed frames (so 0 is the first frame)
# update/draw components on each frame
# *text_3* updates
if t >= 0.0 and text_3.status == NOT_STARTED:
# keep track of start time/frame for later
text_3.tStart = t # underestimates by a little under one frame
text_3.frameNStart = frameN # exact frame index
text_3.setAutoDraw(True)
if text_3.status == STARTED and t >= (0.0 + (3.0-win.monitorFramePeriod*0.75)): #most of one frame period left
text_3.setAutoDraw(False)
# 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 EndingComponents:
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 "Ending"-------
for thisComponent in EndingComponents:
if hasattr(thisComponent, "setAutoDraw"):
thisComponent.setAutoDraw(False)
win.close()
core.quit()
The layout of your Excel conditions file will probably need to change somewhat. PsychoPy is structured around treating each row in that file as corresponding to a trial, whereas you are currently spreading a trial across multiple rows. That will make it harder to store and collect responses appropriately with their corresponding stimuli. Suggest you lay it out like this:
numShifts angle_01 angle_02
4 [0,315,315,270] [45,45,225,225]
3 [0,45,315] [45,45,90]
etc
Then in Builder, insert a new loop, inside your existing one.
Importantly: de-select its "is trials" check box. That means this loop will run multiple times within a single trial, shifting the stimuli however many times are required for this particular trial.
Let's call the inner one stimulusLoop
and the outer trial-level loop trialLoop
). i.e. trialLoop
will run just once per trial, processing one line of the conditions file. stimulusLoop
will extract each entry of the list of angles for each stimulus, WITHIN a trial. To achieve this, put numShift
in the nReps field of the inner loop. e.g. given the conditions file above, it would run 4 times on the first trial, and 3 times on the second.
Then in the orientation field of each stimulus, put something like this (untested):
eval(angle_01)[stimulusLoop.thisN]
What this does is evaluate the list of angles (when it is read in, PsychoPy just thinks it is the list of characters "[0,315,315,270]"
. The eval()
function tells it to evaluate it as a Python expression. In this case, it will realise it is a list of numbers and we can then index it to get the current value, in this case using the current iteration number of the inner loop (stimulusLoop.thisN
).
Hopefully this will get you started.