psychopy.org | Reference | Downloads | Github

Visual countdown timer

screen
timing

#1

Hi all,

I am stumped trying to create a visual countdown timer for participants. I have a task in which participants have 7 seconds to respond and I want them to easily how much time they have. I want one solid bar to move from the far right of the top of the screen to the far left of the top of the screen, proportional to the length of the countdown.

I first tried to create a rectangle that I reduced in width until the timer runs out.

import time
import sys
from psychopy import visual,event,core
win = visual.Window([1536,864], color="black", fullscr=True,allowGUI=False, winType='pyglet',monitor="testMonitor", units="pix") #open a window

square = visual.Rect(win,lineColor="black",fillColor="blue",size=[1536,100],pos =(0,0), units = 'pix')

def countdownTracker(time):
    timer = core.CountdownTimer(time)
    while timer.getTime() > 0: #until the timer is negative, after which time has elapsed
        width = timer.getTime()*400 #gets the current time on the countdown clock and multiples it by a constant for scaling
        square.size = [width,100] #changes the width of the rectangle, proportional to the timer, about 0.017 pix smaller per cycle
        square.pos[0] -= 97 #changes the position of the x-axis of the square, so that it decreases in size from right to left without moving (approximate)
        square.draw() #draw rectangle

        win.flip() #make the rectangle visible
countdownTracker(5)
core.quit()
win.close()

However, I didn’t like that solution, because:

  1. Despite that I set the width of the rectangle equal to the width of the screen, the rectangle was not the full width of the screen.
  2. Balancing how much the width reduces every repeat with changing the position was too finicky. The amount that I scaled the width and the position by was plug and chug. These were poor solutions, which is what caused me to re-think my approach. (There is probably an elegant solution based on frame rates and how often the code will cycle through this loop, but I didn’t get there).

That is when I went back to the API and found visual.Line. This solution appeared much more elegant. I was able to draw a line the entire width of the screen using the dimensions of win. I also thought it that I could change the end point of the line in the ‘while’ loop, but with a fixed starting point. Thus, there is no balance between the width of the shape and its position.

timer = core.CountdownTimer(5)
while timer.getTime() > 0:
        line.end[0] -= 5.1
        line.draw()
        win.flip()

However, the line does not change its shape at all, despite that this code does seem to access and make calculations on the end point. Can someone please help me understand why I cannot update and re-draw a line with a new endpoint within a loop? Also, I chose to reduce the end-point by 5.1, because, on average, this code cycles through the loop 301 times and the screen width divided by 301 is ~5.1 If anyone has a more elegant solution for that, too, that would be great. I can’t seem to wrap my head around what to do, here.

Thank you!!


#2

Please provide all of the relevant code, including the creation of the line object and the window.


#3

Thank you for responding, Michael. Sorry if the initial code wasn’t helpful. Here is everything:

import time
import sys
from psychopy import visual,event,core
 
win = visual.Window([1536,864], color="black", fullscr=True,allowGUI=False, winType='pyglet',monitor="testMonitor", units="pix") #open a window

lineEndPoint = win.size[0]/2
line = visual.Line(win, start=(-lineEndPoint, 417), end=(lineEndPoint, 417),lineColor = "blue",lineWidth = 30)

timer = core.CountdownTimer(5)
while timer.getTime() > 0:
        line.end[0] -= 5.12
        line.draw()
        win.flip()

win.close()
sys.exit()

#4

There seems to be some sort of bug specific to line.end rather than, say, line.setEnd()

To get your task going, I ended up using a shape stimulus like this:

line2 = visual.ShapeStim(win, vertices=[[-lineEndPoint, 0], [lineEndPoint, 0]])

# and then updating like this:

while timer.getTime() > 0:
    x = lineEndPoint - 5.12
    line2.setVertices([[lineEndPoint, 0], [x, 0]])
    line2.draw()
    win.flip()

core.quit()

NB use core.quit() rather than sys.exit(): I’m not sure the latter will necessarily allow PsychoPy to do its tidying up that happens via core.quit() (which also makes the win.close() redundant, as it will do that for you anyway).


#5

Thank you, Michael. I appreciate the time.

I also appreciate the notes about core.quit(). I always use it in my code, and I’m not even sure why sys.exit() ended up in this file.


#6

I got this working with a couple of tweaks after your notes, Michael. Thanks, again.

It counts down from some set timer at a constant rate, determined by the average refresh rate (~60.5) of the monitor. The line starts at the far-right side of the window and approaches the far-left (it never quite reaches it, which doesn’t bother me too much, as the difference is imperceptible).

Here is the final code:

import time
import sys
from psychopy import visual,event,core
 
win = visual.Window([1000,800], color="black", fullscr=False,allowGUI=False, winType='pyglet',monitor="testMonitor", units="pix") #open a window

lEndPoint = -(win.size[0]/2)
rEndPoint = (win.size[0]/2)

line = visual.ShapeStim(win, vertices=[(lEndPoint, 0), (rEndPoint, 0)],lineColor = "blue", lineWidth = 30)

time = 10

dist = win.size[0]/(60.5*time)

timer = core.CountdownTimer(time)
while timer.getTime() > 0:
    rEndPoint -= dist
    line.vertices = [(lEndPoint,0),(rEndPoint,0)]
    line.draw()
    win.flip()


core.quit()

It is worth noting that line.setVertices() did not work to reduce the size of the line. A screen-width line appeared and never changed size. However, line.vertices() did work. I’m not sure if there is another bug hiding somewhere, or if this is a version-specific issue (I am still running psychopy v 1.90.3).

Hope someone else might find this helpful. Cheers.