Presenting a written story at a specific wpm rate line by line until it fills the screen

If this template helps then use it. If not then just delete and start from scratch.

OS (Win10):
PsychoPy version (2021.2.3)
**Standard Standalone? (y)

Hi PsychoPy Community,

I would be grateful for help with a problem I am having where PsychoPy crashes the experiment whenever I insert a custom code block into my script. I am 100% sure it is due to my lack of coding experience rather than any issues with PsychoPy.
I am using PsychoPy v2021.2.3 which I am aware is an older version, but I need to use this version for one of my studies. However, if I can achieve my objectives with a newer version and can have two versions of psychopy running on a computer then I could entertain this option. Unless the current coded script I have can be fixed. I also need the finished script to ideally interface back with builder until I am more fluent with python coding, since I would potentially be attempting to add eye tracking components and EEG markers into the output data.

OBJECTIVE
The objective is to display a written “Word Story” (Black Arial font on a white screen) one line at a time on the screen at a rate of 185 words per minute until the screen is evenly filled with 24 lines of text. After a short pause of 2s the screen should clear and the story continue filling up the next screen and so on until the entire story has been presented.
Before the story the routine script should start with a Welcome screen (black screen with white text) “Welcome, you are about to read a written story. Press Space to start” – where the space bar will progress the script to the written story.
After the story the end screen should display automatically for 20s when the story ends and read “Thank you for reading, please see the researcher now” – (on a black screen with white text).

PROBLEM
I have created a script with the basic flow from WelcomeScreen, Blank500, WordStory, Blank500, and End Screen as a starting point in builder. The full story is coded within the script and defined in the # Initialize components for Routine “WordStory” under the variable “text”. I have already created an audio story and silent picture story with a similar routine setup which work well. It is just the written story presentation I am having trouble building since all attempts to insert a code block for the specifications I need to present the stimuli have failed.

I have attached a github link to the current script “MikeHooter.py” if this helps and could be modified so that it achieves the objective or help me to understand what I should write and insert to have better success with this. https://github.com/KendalJohnson/SharingZone/blob/main/MikeHooter.py

These are the pieces I have been attempting to work with, however I don’t think restricting the screen size is a good idea since I have seen people run into issues with this. I am also unsure of the height. I also don’t think it needs an event.wait_keys():
import psychopy
from psychopy import core, visual, event

Set up the experiment

win = visual.Window([800, 600], color=“white”)

Set up the text stimulus

text = visual.TextStim(win, text=“PUT MY TEXT HERE”, height=0.5, font=“Arial”, color=“black”)

Set the words per minute speed

words_per_minute = 185

Set the lines per screen

lines_per_screen = 24

Calculate the time per line

time_per_line = 60.0 / (words_per_minute * lines_per_screen)

Set up the pause time

pause_time = 2.0

Split the text into lines

lines = text.text.split(“\n”)

Set the current line to 0

current_line = 0

While there are still lines to display

while current_line < len(lines):
# Display the current line
text.text = lines[current_line]
text.draw()

# Wait for the appropriate time
core.wait(time_per_line)

# Increment the current line
current_line += 1

# Check if the screen is full
if current_line % lines_per_screen == 0:
    # Clear the screen
    core.flip()

    # Wait for the pause time
    core.wait(pause_time)

Wait for a key press

event.wait_keys()

Close the window

win.close()
Many thanks in advance as any help on this is greatly appreciated.
Acknowledgement in my PhD thesis if you can help me solve this :blush:not notable, but this is the best I can offer… and the hope that my research may contribute to improvements in early Dementia screening.
Kendal

I now have the story presenting at the right rate but after this routine it throw you out of the experiment. I am having trouble getting the code block to continue on to the next routine after the word story. Any suggestions???

MikeHooter.psyexp (31.8 KB)

If you are getting thrown out then there should be an error message in the Runner

Thank you! This alluded to the fact it was picking up extra blank lines in the excel conditions spreadsheet, so deleted the content in the proceeding rows after the final stimuli and if fixed the problem. This is now the code that works and displays my story over 2 full screens. I had to play with it for a bit to figure out how much text would need to be in each row of the excel spreadsheet. It is reading the story from two rows. However now I am having trouble getting Veranda text…

This is the solution:
MikeHooter.psyexp (40.7 KB)

set up handler to look after randomisation of conditions etc

trials = data.TrialHandler(nReps=1.0, method=‘sequential’,
extraInfo=expInfo, originPath=-1,
trialList=data.importConditions(‘lists/wordstory.xlsx’),
seed=None, name=‘trials’)
thisExp.addLoop(trials) # add the loop to the experiment
thisTrial = trials.trialList[0] # so we can initialise stimuli with some values

abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)

if thisTrial != None:
for paramName in thisTrial:
exec(‘{} = thisTrial[paramName]’.format(paramName))

for thisTrial in trials:
currentLoop = trials
# abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)
if thisTrial != None:
for paramName in thisTrial:
exec(‘{} = thisTrial[paramName]’.format(paramName))

# ------Prepare to start Routine "WordStory"-------
continueRoutine = True
# update component parameters for each repeat
textWordStory.setText('')
words = Sentence.split()
i = 0
frameCounter = 0 #it all happens within the same routine so make our own counter

key_resp.keys = []
key_resp.rt = []
_key_resp_allKeys = []
# keep track of which components have finished
WordStoryComponents = [textWordStory, key_resp]
for thisComponent in WordStoryComponents:
    thisComponent.tStart = None
    thisComponent.tStop = None
    thisComponent.tStartRefresh = None
    thisComponent.tStopRefresh = None
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED
# reset timers
t = 0
_timeToFirstFrame = win.getFutureFlipTime(clock="now")
WordStoryClock.reset(-_timeToFirstFrame)  # t0 is time of first possible flip
frameN = -1

# -------Run Routine "WordStory"-------
while continueRoutine:
    # get current time
    t = WordStoryClock.getTime()
    tThisFlip = win.getFutureFlipTime(clock=WordStoryClock)
    tThisFlipGlobal = win.getFutureFlipTime(clock=None)
    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
    # update/draw components on each frame
    
    # *textWordStory* updates
    if textWordStory.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
        # keep track of start time/frame for later
        textWordStory.frameNStart = frameN  # exact frame index
        textWordStory.tStart = t  # local t and not account for scr refresh
        textWordStory.tStartRefresh = tThisFlipGlobal  # on global time
        win.timeOnFlip(textWordStory, 'tStartRefresh')  # time at next scr refresh
        textWordStory.setAutoDraw(True)
    if i <= len(words)-1: #-1 to account for the list index starting at 0
        #frameCount = 1 + (len(words[i].split()) * int(0.166667*60)) #int because there is a leftover so the == below doesn't work. Change frameCounter set and reset to 0.2 instead of 0 if this is an issue.
        frameCount = 19      #int(0.166667*60)
        frameCounter+=1 #our own frame counter
        if frameCounter == frameCount:
            textWordStory.setText(textWordStory.text + ' ' + words[i]) #set the word
            i+=1
            frameCounter = 0
    else:
        if len(key_resp.keys) > 0:
            continueRoutine = False
    
    # *key_resp* updates
    waitOnFlip = False
    if key_resp.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
        # keep track of start time/frame for later
        key_resp.frameNStart = frameN  # exact frame index
        key_resp.tStart = t  # local t and not account for scr refresh
        key_resp.tStartRefresh = tThisFlipGlobal  # on global time
        win.timeOnFlip(key_resp, 'tStartRefresh')  # time at next scr refresh
        key_resp.status = STARTED
        # keyboard checking is just starting
        waitOnFlip = True
        win.callOnFlip(key_resp.clock.reset)  # t=0 on next screen flip
        win.callOnFlip(key_resp.clearEvents, eventType='keyboard')  # clear events on next screen flip
    if key_resp.status == STARTED and not waitOnFlip:
        theseKeys = key_resp.getKeys(keyList=['space'], waitRelease=False)
        _key_resp_allKeys.extend(theseKeys)
        if len(_key_resp_allKeys):
            key_resp.keys = _key_resp_allKeys[-1].name  # just the last key pressed
            key_resp.rt = _key_resp_allKeys[-1].rt
    
    # check for quit (typically the Esc key)
    if endExpNow or defaultKeyboard.getKeys(keyList=["escape"]):
        core.quit()
    
    # 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 WordStoryComponents:
        if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
            continueRoutine = True
            break  # at least one component has not yet finished
    
    # refresh the screen
    if continueRoutine:  # don't flip if this routine is over or we'll get a blank screen
        win.flip()

# -------Ending Routine "WordStory"-------
for thisComponent in WordStoryComponents:
    if hasattr(thisComponent, "setAutoDraw"):
        thisComponent.setAutoDraw(False)
trials.addData('textWordStory.started', textWordStory.tStartRefresh)
trials.addData('textWordStory.stopped', textWordStory.tStopRefresh)
# check responses
if key_resp.keys in ['', [], None]:  # No response was made
    key_resp.keys = None
trials.addData('key_resp.keys',key_resp.keys)
if key_resp.keys != None:  # we had a response
    trials.addData('key_resp.rt', key_resp.rt)
trials.addData('key_resp.started', key_resp.tStartRefresh)
trials.addData('key_resp.stopped', key_resp.tStopRefresh)
# the Routine "WordStory" was not non-slip safe, so reset the non-slip timer
routineTimer.reset()
thisExp.nextEntry()

completed 1.0 repeats of ‘trials’

Did you mean: Verdana font

Oh yes! It seems the biggest problems are my own fault and the easiest fixes… in most cases. Thanks to you and this forum or I would still be trying to download a Veranda.ttf file haha!

1 Like