In my task, I am presenting 8 female and 8 male pictures followed by either a STEM-related or Arts-related word (10 words in each category).
There are 3 blocks with 120 trials in each block and following is the pseudorandom order for each block:
60 trials where female face is paired with STEM-related word
20 trials where females face is paired with Arts-related word
20 trials where male face is paired with STEM-related word
20 trials where male face is paired with Arts-related word
And these trials need to be presented in a random sequence across the 3 blocks.
Each image stays on screen for 1800 ms and at the 1000th ms, the STEM or Arts related word is presented and it lasts for 800 ms. During this 800 ms, participants have to press ENTER key for trials with female face + STEM-related word only. If they press ENTER for the correct trial, then a happy face will be presented for 1000 ms and if they press ENTER for any other trial an X will be presented for 1000 ms.
Intertrial interval is 1000 ms.
Could anyone please help me with the coding aspect of this task. I am not very familiar with programming on Python so your help is very much appreciated.
@movivi, there is minimal coding needed for this experiment, most can all be made in Builder, using a image component, a keyboard component and a text component, in a loop with a conditions file that contains all your images, and a column for correct answers, which are fed into your keyboard component. If you can get that running, we can look at providing some feedback using a code component.
@dvbridges thank you for your prompt response. Following is the code from my progress in the builder version. Current caveats with the code are as follows:
I need to press Spacebar first to view the first image, I donât know why. I only allow enter key to be pressed during this routine.
If I donât press the Enter key before 1.8 seconds then the screen goes blank and the next image is not presented.
In the current version, the same face and word is paired across multiple iteration. That is not desired, instead, the conditions needed is that in the block 60 trials should be female faces + STEM words and so on (as described in my first post).
Due to unbalanced image vs. word stimuli (total images = 16 and total STEM/Arts word = 20) so I am not able to configure which rows are the correct response. Instead I used a column based criteria and I donât think itâs the right way to programme it but I am not aware of any other option to tell the programme that whichever pair, except for Female face + STEM word, is wrong and a sad face needs to be shown as feedback. Even not responding within 800 ms also results in a sad face.
# ------Prepare to start Routine "pr_bl"-------
t = 0
pr_blClock.reset() # clock
frameN = -1
continueRoutine = True
# update component parameters for each repeat
face.setOpacity(1)
face.setPos((0, 0))
face.setSize((1, 1))
face.setOri(0)
face.setImage(female_faces or male_faces)
word_pair.setColor('black', colorSpace='rgb')
word_pair.setPos((0, -0.7))
word_pair.setText(stem_words or arts_word)
word_pair.setFont('Arial')
word_pair.setHeight(0.1)
press_enter = event.BuilderKeyResponse()
happy_face.setOpacity(1)
happy_face.setPos((0, 0))
happy_face.setSize((0.7, 0.7))
happy_face.setOri(0)
happy_face.setImage(correct)
sad_face.setOpacity(1)
sad_face.setPos((0, 0))
sad_face.setSize((0.7, 0.7))
sad_face.setOri(0)
sad_face.setImage(incorrect)
# keep track of which components have finished
pr_blComponents = [face, word_pair, press_enter, ITI, happy_face, sad_face]
for thisComponent in pr_blComponents:
if hasattr(thisComponent, 'status'):
thisComponent.status = NOT_STARTED
# -------Start Routine "pr_bl"-------
while continueRoutine:
# get current time
t = pr_blClock.getTime()
frameN = frameN + 1 # number of completed frames (so 0 is the first frame)
# update/draw components on each frame
# *face* updates
if t >= 0 and face.status == NOT_STARTED:
# keep track of start time/frame for later
face.tStart = t
face.frameNStart = frameN # exact frame index
face.setAutoDraw(True)
frameRemains = 0 + 1.8- win.monitorFramePeriod * 0.75 # most of one frame period left
if face.status == STARTED and t >= frameRemains:
face.setAutoDraw(False)
# *word_pair* updates
if t >= 1 and word_pair.status == NOT_STARTED:
# keep track of start time/frame for later
word_pair.tStart = t
word_pair.frameNStart = frameN # exact frame index
word_pair.setAutoDraw(True)
frameRemains = 1 + 0.8- win.monitorFramePeriod * 0.75 # most of one frame period left
if word_pair.status == STARTED and t >= frameRemains:
word_pair.setAutoDraw(False)
# *press_enter* updates
if t >= 1 and press_enter.status == NOT_STARTED:
# keep track of start time/frame for later
press_enter.tStart = t
press_enter.frameNStart = frameN # exact frame index
press_enter.status = STARTED
# keyboard checking is just starting
win.callOnFlip(press_enter.clock.reset) # t=0 on next screen flip
event.clearEvents(eventType='keyboard')
frameRemains = 1 + 0.8- win.monitorFramePeriod * 0.75 # most of one frame period left
if press_enter.status == STARTED and t >= frameRemains:
press_enter.status = STOPPED
if press_enter.status == STARTED:
theseKeys = event.getKeys(keyList=['return'])
# check for quit:
if "escape" in theseKeys:
endExpNow = True
if len(theseKeys) > 0: # at least one key was pressed
press_enter.keys = theseKeys[-1] # just the last key pressed
press_enter.rt = press_enter.clock.getTime()
# was this 'correct'?
if (press_enter.keys == str(correctAns == female_faces and stem_words)) or (press_enter.keys == correctAns == female_faces and stem_words):
press_enter.corr = 1
else:
press_enter.corr = 0
# a response ends the routine
continueRoutine = False
# *ITI* updates
if t >= 2.8 and ITI.status == NOT_STARTED:
# keep track of start time/frame for later
ITI.tStart = t
ITI.frameNStart = frameN # exact frame index
ITI.setAutoDraw(True)
frameRemains = 2.8 + 1.0- win.monitorFramePeriod * 0.75 # most of one frame period left
if ITI.status == STARTED and t >= frameRemains:
ITI.setAutoDraw(False)
# *happy_face* updates
if (press_enter.corr == $female_faces and $stem_words) and happy_face.status == NOT_STARTED:
# keep track of start time/frame for later
happy_face.tStart = t
happy_face.frameNStart = frameN # exact frame index
happy_face.setAutoDraw(True)
if happy_face.status == STARTED and t >= (happy_face.tStart + 1.0):
happy_face.setAutoDraw(False)
# *sad_face* updates
if () and sad_face.status == NOT_STARTED:
# keep track of start time/frame for later
sad_face.tStart = t
sad_face.frameNStart = frameN # exact frame index
sad_face.setAutoDraw(True)
if sad_face.status == STARTED and t >= (sad_face.tStart + 1.0):
sad_face.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 pr_blComponents:
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()
Hope the code and the following condition sheet is helpful to debug my script. act_block.csv (368 Bytes)
I have updated my task and the only issue I am having is to draw the stimuli from the act_block.csv. How to tell psychopy to take the images and word pairs from the act_block.csv to be shown in the trials loop?
I am fighting a deadline so your help is really needed.
@movivi, you could create the entire stim list before the experiment with all conditions satisfied, but it would be randomly generated so that 60 trials pairing female faces with a randomly selected STEM-related word, 20 trials pairing female faces with a randomly selected ARts-related word etc. Would that be good?
Well, if you would like to create the stim list first, which would be a neater way of doing things, without trying to reference different excel sheets, then add a code component to your trial routine, and in the âBegin Experimentâ tab:
import pandas as pd
stim = pd.read_csv('act_block.csv') # Get stim
femFaces = list(stim['female_faces'])
maleFaces = list(stim['male_faces'])
stemWords = list(stim['stem_words'])
artWords = list(stim['art_words'])
# Create dict to store face-word pairs
stimDict = {'faceStim': [], 'wordStim': [], 'condition': []}
# stim numbers
stimNumbers = [60, 20, 20, 20]
stimFaces = [femFaces, femFaces, maleFaces, maleFaces]
stimWords = [stemWords, artWords, stemWords, artWords]
for index, block in enumerate(stimNumbers):
for trials in range(block):
# randomize order of faces and words
shuffle(stimFaces[index])
shuffle(stimWords[index])
stimDict['faceStim'].append(stimFaces[index][0])
stimDict['wordStim'].append(stimWords[index][0])
stimDict['condition'].append(index+1)
newStim = pd.DataFrame(stimDict)
newStim.to_csv('newStim.csv', index=False)
This will create a new CSV called ânewStim.csvâ, which you pass to your trial handler to use for your trials - just make sure your loop is set to random. Using this sheet, all you need to do is use the variables that were created for your stim, e.g., for your image, use $faceStim
Perfect! Of course this is a better way to run the task. Thank you so much, youâre a lifesaver @dvbridges !
I had a minor issue with providing feedback for these trials. So the idea is that if the participant presses the ENTER key for female face + STEM word, they will be shown a smiley face for 1 second but if they donât press ENTER key for this type of trials, an X image will be shown to them for 1 second as well.
If they press ENTER key for any other type of trials, an X image will be shown for 1 second and if they donât respond for those trials, no image will be shown and they can proceed to the next trial after 1 second.
I have tried providing the feedback and now the issue is that it even provides correct feedback for trials where participants have to not respond. How can I change it to fit the criteria I explained above. Your help is so much appreciated!
Following is the code where I think the change needs to be made:
# *key_resp_2* updates
if t >= 0 and key_resp_2.status == NOT_STARTED:
# keep track of start time/frame for later
key_resp_2.tStart = t
key_resp_2.frameNStart = frameN # exact frame index
key_resp_2.status = STARTED
# keyboard checking is just starting
win.callOnFlip(key_resp_2.clock.reset) # t=0 on next screen flip
event.clearEvents(eventType='keyboard')
frameRemains = 0 + 1.8- win.monitorFramePeriod * 0.75 # most of one frame period left
if key_resp_2.status == STARTED and t >= frameRemains:
key_resp_2.status = STOPPED
if key_resp_2.status == STARTED:
theseKeys = event.getKeys(keyList=['return', 'None'])
# check for quit:
if "escape" in theseKeys:
endExpNow = True
if len(theseKeys) > 0: # at least one key was pressed
key_resp_2.keys = theseKeys[-1] # just the last key pressed
key_resp_2.rt = key_resp_2.clock.getTime()
# was this 'correct'?
if (key_resp_2.keys == str(correctAns)) or (key_resp_2.keys == correctAns):
key_resp_2.corr = 1
else:
key_resp_2.corr = 0
# a response ends the routine
continueRoutine = False
# *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
text_3.frameNStart = frameN # exact frame index
text_3.setAutoDraw(True)
frameRemains = 0.0 + 1.0- win.monitorFramePeriod * 0.75 # most of one frame period left
if text_3.status == STARTED and t >= frameRemains:
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 trialComponents:
if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
continueRoutine = True
break # at least one component has not yet finished
# check for quit (the Esc key)
if endExpNow or event.getKeys(keyList=["escape"]):
core.quit()
# refresh the screen
if continueRoutine: # don't flip if this routine is over or we'll get a blank screen
win.flip()
# -------Ending Routine "trial"-------
for thisComponent in trialComponents:
if hasattr(thisComponent, "setAutoDraw"):
thisComponent.setAutoDraw(False)
# check responses
if key_resp_2.keys in ['', [], None]: # No response was made
key_resp_2.keys=None
# was no response the correct answer?!
if str(correctAns).lower() == 'none':
key_resp_2.corr = 1; # correct non-response
else:
key_resp_2.corr = 0; # failed to respond (incorrectly)
# store data for trials (TrialHandler)
trials.addData('key_resp_2.keys',key_resp_2.keys)
trials.addData('key_resp_2.corr', key_resp_2.corr)
if key_resp_2.keys != None: # we had a response
trials.addData('key_resp_2.rt', key_resp_2.rt)
# ------Prepare to start Routine "feedback"-------
t = 0
feedbackClock.reset() # clock
frameN = -1
continueRoutine = True
routineTimer.add(2.000000)
# update component parameters for each repeat
if key_resp_2.corr == 1:
happy_face.setImage('o.bmp')
else: happy_face.setImage('x.bmp')
happy_face.setOpacity(1)
happy_face.setPos((0, 0))
happy_face.setSize((0.5, 0.5))
happy_face.setOri(0)
# keep track of which components have finished
feedbackComponents = [happy_face, text_2]
for thisComponent in feedbackComponents:
if hasattr(thisComponent, 'status'):
thisComponent.status = NOT_STARTED
@dvbridges I tried running your code but the output it produces is very messy and unlike the sample csv file you provided. Enclosed is the csv file that the code generated on my laptop. I am not sure whatâs the issue here.
EDIT: Actually, I had to edit your csv file becaues it had many other entries in the columns that were not relevant. Use the following for your stim. See act_block.csv (260 Bytes)
Yes, for the feedback you need a slight amendment to the code I just posted. You want to create a new column for correct answers. You will then use this to determine which feedback to present:
import pandas as pd
stim = pd.read_csv('act_block.csv') # Get stim
femFaces = list(stim['female_faces'])
maleFaces = list(stim['male_faces'])
stemWords = list(stim['stem_words'])
artWords = list(stim['art_words'])
# Create dict to store face-word pairs
stimDict = {'faceStim': [], 'wordStim': [], 'condition': [], 'correctAns': []}
# stim numbers
stimNumbers = [60, 20, 20, 20]
stimFaces = [femFaces, femFaces, maleFaces, maleFaces]
stimWords = [stemWords, artWords, stemWords, artWords]
for index, block in enumerate(stimNumbers):
for trials in range(block):
# randomize order of faces and words
shuffle(stimFaces[index])
shuffle(stimWords[index])
stimDict['faceStim'].append(stimFaces[index][0])
stimDict['wordStim'].append(stimWords[index][0])
stimDict['condition'].append(index+1)
if index == 0: # Only use 'return' as correctAns for femFaces with stemWords
stimDict['correctAns'].append('return')
else:
stimDict['correctAns'].append('None')
newStim = pd.DataFrame(stimDict)
newStim.to_csv('newStim.csv', index=False)
So you will want to use the correctAns variable in your keyboard, which I beleive you have already done.
@dvbridges well the issue then is that I need to generate the csv file on my laptop with that additional column showing the correct response criteria and the enclosed csv is again showing a messy output. It would be ideal if I could produce similar csv as the sample you uploaded earlier. Also it might be important for me to show my supervisor how the pseudorandom sequence is generated on my own laptop.
I am not sure why the code is shuffling across rows and putting values of a specific column into another column as well as why the female and male pictures are all grouped together in one single cell.
If you look at your act_block.csv sheet, scroll down the sheet. Keep scrolling until you see entries that should not be there. You need to delete them.
Regarding your feedback, try the following in your feedback routine. In the code component, in the âBegin Routineâ tab:
# Correct ('return') response to female face + stem
if key_resp_2.corr and correctAns == 'return':
message = 'Smily face'
# Incorrect ('None') response to female face + stem
elif not key_resp_2.corr and correctAns == 'return':
message = 'X'
# Correct ('None') response to any face + any word except above
elif key_resp_2.corr and correctAns == 'None':
message = ''
# Inorrect ('return') response to any face + any word except above
elif not key_resp_2.corr and correctAns == 'None':
message = 'X'
Actually I tried doing something similar earlier and still the feedback was not right. Using your code: the incorrect feedback is given even for those trials I donât need to respond. Following is the snippet from the script:
# Correct ('return') response to female face + stem
if key_resp_2.corr and correctAns == 'return':
happy_face.setImage('o.bmp')
# Incorrect ('None') response to female face + stem
elif not key_resp_2.corr and correctAns == 'return':
happy_face.setImage('x.bmp')
# Correct ('None') response to any face + any word except above
elif key_resp_2.corr and correctAns == 'None':
happy_face.setAutoDraw(False)
# Inorrect ('return') response to any face + any word except above
elif not key_resp_2.corr and correctAns == 'None':
happy_face.setImage('x.bmp')
I am displaying pictures as feedback so o.bmp is the smiley face and the x.bmp is the X letter.
Yes I tried it with your code too and the code I pasted above is actually yours where I have replaced the message feedback to image feedback. Is there a different way to show image as a feedback?