psychopy.org | Reference | Downloads | Github

How can I render a "blank" mid-sentence?

I am trying to display a sentence for participants to fill-in-the-blank. If I just want a line, I can use a Polygon. However, with variable text either side, that’s too complicated.

I’ve tried using a series of underscore characters _____ in my text. In programs like Excel (or this website), that displays as a single line. In Psychopy, it displays with spaces in between: _ _ _ _ _

Is there a way of getting a single solid line?

(Using v1.83.04 on Windows, if that’s relevant.)

Can you post code to replicate? I can’t replicate

from psychopy import visual, event
win = visual.Window()
stim = visual.TextStim(win, text='goat______horse')
stim.draw()
win.flip()
event.waitKeys()

produces
.

Similarly for arial, times new roman, comic sans ms.

Here’s what I’m getting:

I’ve been unable to trim the code down and still keep a working program, so here’s the full code that Builder generated:

from __future__ import division  # so that 1/3=0.333 instead of 1/3=0
from psychopy import locale_setup, 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
import sys # to get file system encoding

# Ensure that relative paths start from the same directory as this script
_thisDir = os.path.dirname(os.path.abspath(__file__)).decode(sys.getfilesystemencoding())
os.chdir(_thisDir)

# Store info about the experiment session
expName = u'temp'  # from the Builder filename that created this script
expInfo = {u'session': u'001'}
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 + u'data/temp'

# 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=False,
    dataFileName=filename)
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=[860, 540], fullscr=False, screen=0, allowGUI=True, allowStencil=False,
    monitor=u'testMonitor', color=[0,0,0], 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 "trial"
trialClock = core.Clock()
routine = core.StaticPeriod(win=win, screenHz=expInfo['frameRate'], name='routine')
text = visual.TextStim(win=win, ori=0, name='text',
    text='Sentence with ____ gap to fill',    font='Arial',
    pos=[0, 0], height=0.1, wrapWidth=None,
    color='white', colorSpace='rgb', opacity=1,
    depth=-1.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 "trial"-------
t = 0
trialClock.reset()  # clock 
frameN = -1
# update component parameters for each repeat
# keep track of which components have finished
trialComponents = []
trialComponents.append(routine)
trialComponents.append(text)
for thisComponent in trialComponents:
    if hasattr(thisComponent, 'status'):
        thisComponent.status = NOT_STARTED

#-------Start Routine "trial"-------
continueRoutine = True
while continueRoutine:
    # 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
    
    # *text* updates
    if t >= 0.0 and text.status == NOT_STARTED:
        # keep track of start time/frame for later
        text.tStart = t  # underestimates by a little under one frame
        text.frameNStart = frameN  # exact frame index
        text.setAutoDraw(True)
    # *routine* period
    if t >= 0.0 and routine.status == NOT_STARTED:
        # keep track of start time/frame for later
        routine.tStart = t  # underestimates by a little under one frame
        routine.frameNStart = frameN  # exact frame index
        routine.start(0.5)
    elif routine.status == STARTED: #one frame should pass before updating params and completing
        routine.complete() #finish the static period
    
    # 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)
# the Routine "trial" was not non-slip safe, so reset the non-slip timer
routineTimer.reset()
# these shouldn't be strictly necessary (should auto-save)
thisExp.saveAsPickle(filename)
# make sure everything is closed down
thisExp.abort() # or data files will save again on exit
win.close()
core.quit()

Your code works fine here (also psychopy 1.83.04, but on Ubuntu 16.04):

I’m reporting this as a potential bug. See Inconsistent text rendering · Issue #1212 · psychopy/psychopy · GitHub. It should be identical on all systems. For now, you can try to set a different font. Maybe you can find one that makes a continuous underline.

I would have thought this is a font thing, but potentially a difference by operating system. Either way for regular TextStim the text is rendered by pyglet, so I expect it would need changing there, not in PsychoPy.

@Ollyver, will you get back to us if using a different font worked? If not, then @jon is probably right and we can’t really do anything about it. You would simply have to use a different computer and/or change to Ubuntu :wink:

Originally I used Arial.
Comic Sans looked the same, presumably whatever the default is?
Papyrus, Verdana and Courier New look like themselves, but have separate lines.

I do get a continuous line with Times New Roman and Garamond.

So it looks like serif fonts give a continuous underline, and sans-serif fonts do not.

1 Like