psychopy.org | Reference | Downloads | Github

A line stop rotating when checking the key press

Hi all,

I am trying to draw a rotating line (a clock hand). Participants should press the spacebar any time that they want. Then, after 250 ms, a tone starts playing, and finally, after 250 ms, the routine should end. I drew a rotating line and it worked fine.

The problem is that after participants press space, the line stop rotating, but everything else is working as expected. I was wondering if I can do a trick so the line rotates until the end of the trial?

# each frame tab

clock_hand.ori = t *140.23  # one rotation in 2560 ms
clock_screen.draw()
clock_hand.draw()


k = event.getKeys()
if k == ['space']: # if space is pressed
    pre_tone= core.Clock ()
    pre_tone.reset ()
    while pre_tone.getTime() < .250: # wait for 250 ms
        clock_hand.ori = t *140.23  # meanwhile do the rotation
        clock_screen.draw()
        clock_hand.draw()
    now = ptb.GetSecs()
    tone.play(when=now+0.1)  # play the tone for 0.1s
    post_tone= core.Clock ()
    post_tone.reset ()
    while post_tone.getTime () < .250: # wait for 250 ms
        clock_hand.ori = t *140.23  # meanwhile do the rotation
        clock_screen.draw()
        clock_hand.draw()
    continueRoutine= False
elif k == ['q']:
    core.quit()

@Omidrezaa, try the following, assuming your Builder keyboard is called key_resp:

##### Begin Exp #####
sound_1 = sound.Sound('A', 
    secs=.25, 
    stereo=True, 
    hamming=True,
    name='sound_1')
sound_1.setVolume(1)

##### Begin Routine #####
startTime = None
stopTime = None

##### Each Frame #####
if key_resp.rt and startTime is None:  # If a key was pressed for the first time
    startTime = ptb.GetSecs() + .25  # Set start time
    sound_1.play(when=startTime)
    stopTime = t + .5  # Set stop time

if stopTime is not None and t >= stopTime:  # End routine after stop time
    continueRoutine = False

Here is the Builder file, with the rotating line as well.
rotatingLine.psyexp (10.6 KB)

1 Like

Dear @dvbridges. Your code works nice and it solved the rotation problem. I am not sure which part of my code caused the line to stop rotating, but your solution was brilliant.

There is just one issue. I am trying to record the line rotation at different parts of the trial:

  1. When the spacebar was pressed
  2. When the tone starts playing
  3. When the tone stops playing
  4. And at the final frame of the routine

Here is the code:

#Each Frame Tab
if key_space.rt and startTime is None:
    thisExp.addData("space_ori", clock_hand.ori);
    startTime = ptb.GetSecs() + .25
    thisExp.addData("pretone_ori", clock_hand.ori);
    sound_1.play(when=startTime)
    thisExp.addData("posttone_ori", clock_hand.ori);
    stopTime = t + random.uniform(.1,1)


if stopTime is not None and t >= stopTime:
    thisExp.addData("end_ori", clock_hand.ori);
    continueRoutine = False

The problem is that the first 3 orientation in my data file are exactly similar. However, the line is rotating and the orientation should increase.

And finally, could you please explain to me how t works? It seems that I cannot reset it with t.reset() but it can be done with

clock_trialClock.reset()
t = clock_trialClock.getTime()

Thank you so much.

@Omidrezaa, t is the trial timer, set by the trialClock at the beginning of each trial, so you should avoid resetting this clock.

The first three orientations are the same, because they are all recording the orientation at the same time, on the screen refresh that the space bar was pressed. If you want ori on keypress, ori on the time the sound is set to play, and ori at the end of the trial, you could do something like this:

##### Begin Routine #####
startTime = None
stopTime = None
preTone = None

##### Each Frame #####
if key_resp.rt and startTime is None:  # If a key was pressed for the first time
    startTime = ptb.GetSecs() + .25  # Set start time
    sound_1.play(when=startTime)
    stopTime = t + .5  # Set stop time

if startTime is not None:
    if preTone is None and ptb.GetSecs() >= startTime:
        preTone = polygon.ori

if stopTime is not None and t >= stopTime:  # End routine after stop time
    continueRoutine = False

##### End Routine #####
thisExp.addData("preToneOri", preTone)
thisExp.addData("postToneOri", polygon.ori)
1 Like

Dear @dvbridges. That was brilliant. It solved the problem. Thank you so much for your help. I would appreciate it if you could help me with two other issues. I searched a lot but I could not find a solution.

  1. I was thinking of resetting t, so after one complete rotation, I could measure orientation from 0 to 360. But you suggested not to reset t. The Other way is to divide the final rotation if it exceeds 360. However, this solution requires lot of if conditionals (if 360 < ori < 720 and if 720 < ori < 1080, etc.). Is there any way to record the orientation in the range of 0 to 360, no matter how many times the polygon rotate?

  2. The polygon is a clock hand, I am trying to draw a clock screen with major and minor markers and numbers (as below). I wrote a function to draw everything.


    It worked as i expected, but the problem is that if i call this function at the Each Frame Tab, then the polygon does not rotate smoothly and it jumps. I assume that drawing all those things takes time.

# draw circles with radius 'radius' around a ring with radius 'distance'
def ringOfMarkers(radius):

    #This loop draw 12 major ticks
    for tick in list (range(0,360,30)):
        ticks = math.radians(tick)
        x = math.cos(ticks) * (radius-20)
        y = math.sin(ticks) * (radius-20)
        major_ticks.setPos ([x,y])
        tick_ori= 90 - tick
        major_ticks.ori = tick_ori
        major_ticks.draw()

    #This loop draw 24 minor ticks
    for tick in list (range(0,360,15)):
        ticks = math.radians(tick)
        x = math.cos(ticks) * (radius-10)
        y = math.sin(ticks) * (radius-10)
        minor_ticks.setPos ([x,y])
        tick_ori= 90 - tick
        minor_ticks.ori = tick_ori
        minor_ticks.draw()

    #This loop draw 12 numbers above major ticks
    n= 0
    for i in range(12):
        tick= list (range(90,450,30))
        ticks = math.radians(tick[i])
        x = math.cos(ticks) * (radius+50)
        y = math.sin(ticks) * (radius+50)
        num_ticks.setText (str(n))
        n= n +5
        num_ticks.setPos ([-x,y])
        num_ticks.draw()

Now, is there any way to this clock without messing with the rotation?

Thank you.

@Omidrezaa, no problem. I think the first issue can be solved using the modulo operator, which will give you the ori in 360, no matter how large the number is e.g., 724 % 360 = 4 degrees.

The flashing could be because you are calling the draw commands across several loops. Instead, you could just set the shapes to autodraw. This way, you only need to call the draw method once at the beginning of the routine, and leave PsychoPy to call them onwards. So, you would use:

...
major_ticks.ori = tick_ori
major_ticks.autoDraw = True  # etc for each of your draw calls
...
1 Like

@dvbridges, I switched .draw() commands with .autoDraw = True in my function and now it just draw one singe shape in each for loop, not all of them.

@Omidrezaa, this will require some changes to your code. What you want to do, is create a new line for each tick, set its position and orientation, then set the autodraw to true at the beginning of the experiment. Here is how you can do that:

##### Begin Experiment #####

major = []  # store for major ticks
minor = []  # store for major ticks
tickRadius = 300
majorTickSize = 30
minorTickSize = 15

# draw circles with radius 'radius' around a ring with radius 'distance'
def ringOfMarkers(radius, deg, tickSize):

    ticks = math.radians(tick)
    x = math.cos(ticks) * (radius-20)
    y = math.sin(ticks) * (radius-20)
    
    currentTick = visual.Line(
        win=win, name='', units='pix', 
        start=(-(tickSize, 0.15)[0]/2.0, 0), end=(+(tickSize, 0.15)[0]/2.0, 0),
        ori=0, pos=(0, 0),
        lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
        fillColor=[1,1,1], fillColorSpace='rgb',
        opacity=1, depth=0.0, interpolate=True)
        
    currentTick.pos =  [x,y]
    tick_ori = 180 - tick
    currentTick.ori = tick_ori
    return currentTick

# Create 12 major ticks
for idx, tick in enumerate(list(range(0,360,30))):
    currentTick = ringOfMarkers(tickRadius, tick, majorTickSize)
    currentTick.autoDraw = True
    major.append(currentTick)

# Create 12 minor ticks
for idx, tick in enumerate(list(range(15, 360, 30))):
    currentTick = ringOfMarkers(tickRadius, tick, minorTickSize)
    currentTick.autoDraw = True
    minor.append(currentTick)

The ticks are operating in pixels, so if you cannot get the ticks to show, let me know and I will share the Builder file.

Dear @dvbridges, thank you again for your time. It seems that I cannot draw the ticks and numbers of the clock. My unit of the experiment is in pix.

I attached the builder file.

clock.psyexp (28.4 KB)

@Omidrezaa, they were drawing, but the color of the ticks was white, on a white background, so you could not see them. Attached is a working version with numbers. I moved the tick and number function to the Begin Experiment tab, as it only needs to run once.

clock_DB.psyexp (24.6 KB)

Note, that when your trial ends, the autodraw setting for the ticks and text means that the clock face will persist until you tell it to stop. All you need to do, at the end of the routine, is loop through majorTicks, minorTicks and tickText, and tell each object to turn autodraw off e.g.,

# End Routine
for line in majorTick:
    line.autodraw = False
1 Like

In case you cannot open the file, since I am using 2020.1, see below for new changes that you can add to your code.

Begin Experiment

# draw circles with radius 'radius' around a ring with clock radius, tick degree, and tick size
def ringOfMarkers(radius, deg, tickSize):

    ticks = math.radians(deg)
    x = math.cos(ticks) * (radius-20)
    y = math.sin(ticks) * (radius-20)
    
    currentTick = visual.Line(
        win=win, name='', units='pix', 
        start=(-(tickSize, 0.15)[0]/2.0, 0), end=(+(tickSize, 0.15)[0]/2.0, 0),
        ori=0, pos=(0, 0),
        lineWidth=1, lineColor=[1,1,1], lineColorSpace='rgb',
        fillColor=[1,1,1], fillColorSpace='rgb',
        opacity=1, depth=0.0, interpolate=True)
        
    currentTick.pos =  [x,y]
    tick_ori = 180 - tick
    currentTick.ori = tick_ori
    return currentTick

# draw text outside clock radius
def ringOfText(radius, deg, tickN):

    ticks = math.radians(deg)
    x = math.cos(ticks) * (radius-20)
    y = math.sin(ticks) * (radius-20)
    
    currentTick = visual.TextStim(win=win,
            text='',
            units='pix', height=40, 
            color='black')
    
    currentTick.text = str(tickN)
    currentTick.pos =  [x,y]
    return currentTick

Begin Routine

major = []  # store for major ticks
minor = []  # store for minor ticks
tickText=[] # Store for tick text
tickRadius = 300
majorTickSize = 30
minorTickSize = 15
tickN = 15

# Create 12 major ticks
for idx, tick in enumerate(list(range(0,360,30))):
    currentTick = ringOfMarkers(tickRadius, tick, majorTickSize)
    currentTick.autoDraw = True
    major.append(currentTick)

# Create 12 minor ticks
for idx, tick in enumerate(list(range(15, 360, 30))):
    currentTick = ringOfMarkers(tickRadius, tick, minorTickSize)
    currentTick.autoDraw = True
    minor.append(currentTick)

# Create numbers 
for idx, tick in enumerate(list(range(360, 0, -30))):
    currentTick = ringOfText(tickRadius + 50, tick, (tickN % 60))
    currentTick.autoDraw = True
    tickText.append(currentTick)
    tickN += 5
1 Like

Dear @dvbridges. You are a gem. I could not solve all these problems without your generous help. I appreciate that. Before ending this post, one minor issue is setting the autodraw to False.

for line in major: #the name of the list is major, not majorTick
    print (line)
    line.autodraw = False

I saw all those lines using the print function. But they are remaining on the screen in the next routine.

@Omidrezaa yes there were typos there :|. The autodraw property is case sensitive, and should be set using line.autoDraw = False.

@dvbridges In the experiment that you sent, I think the tone occurs immediately after a key press, not after 250 ms. We had the code below in each frame:

if key_space.rt and startTime is None: #If a key was pressed for the first time
    spaceOri= clock_hand.ori
    startTime = ptb.GetSecs() + .25 #set start time
    tone.play(when=startTime) #play a tone after 250 ms for 100 ms

I assume that it is not running ptb.GetSecs() + .25 correctly. I tested this by adding the code to record two slices of time at the end of that loop:

    time1 = ptb.GetSecs()
    time2 = ptb.GetSecs() + .25

However, those two variables give a similar value in the datafile. Is something wrong with the loading of Psychtoolbox?

@Omidrezaa, you may not have PsychToolBox installed in your system if this is not working correctly. The delay works in my version. Are you using standalone PsychoPy? PTB requires 64 bit Python3.

Yes, I am using updated python and Psychopy. I think the problem is with one part of the code. I changed your code and moved tony.play and stopTimeassignment to the second for loop from the first for loop. Now all those manipulations (e.g., adding 5 secs toptb.GetSecs()or setting a greating number tostopTime`) would reflect on the experiment.

if key_space.rt and startTime is None: #If a key was pressed for the first time
    spaceOri = clock_hand.ori
    startTime = ptb.GetSecs() + .25 #set start time
    #tone.play(when=startTime) #play a tone after 250 ms for 100 ms
    #stopTime = t + random.uniform(1,2) #End this trial after 1000-2000 ms

if startTime is not None: #if participant pressed the spacebar
    if preTone is None and ptb.GetSecs() >= startTime: #if current time is greater than start time
        preTone = clock_hand.ori
        tone.play(when=startTime) #play a tone after 250 ms for 100 ms
        stopTime = t + random.uniform(1,2) #End this trial after 1000-2000 ms

if stopTime is not None and t >= stopTime: #End routine after stop time
    continueRoutine = False

Right, I see. I put the startTime and play calls in the first conditional because: the first conditional waits for a keypress, then it schedules the tone to start after 250ms - the important part there is the scheduling, using the when argument. The second conditional awaits the start time that the tone is due to play. When the second conditional is satisfied, it records the orientation, so you are recording the orientation at the onset of the tone. Stoptime is also in the first conditional to plan the stop time of the tone and end the trial, but it could also be in the second conditional.

the first conditional waits for a keypress, then it schedules the tone to start after 250ms

The interesting thing is that leaving the tone play command at the first loop would not wait for .25 to play the tone. It plays the tone immediately after the keypress, which is weird. I changed .25 to greate numbers but it does not change the tone play time. That was also true for the stopTime.

I think adding them to the second loop works because we had another condition that must be true there: and ptb.GetSecs() >= startTime:. However, if current time is greater than startTime, how does the tone play at the startTime? Is not it weird? Do you know what I mean? Practically, this code works as I wanted, but it does not make any sense and the previous code was intuitively true, but not working.

Right, that maybe suggests that the when argument is not being implemented. Your version of your code would work if the when argument is not being processed, where the tone will play immediately after the call to play is made. Which version of PsychoPy are you using? Have you set your sound preferences to use PTB?

Hi David,

You are right. stopTime is working fine in both loops. So the problem is with the tone and when. But if the when argument is not processing, it should not make any difference in which loop we put it. Right?

The Psychopy version that I use is V2020.1.oOrc2.

Have you set your sound preferences to use PTB

I am not sure if I already did that or not. Here is how I set my sound:

tone = sound.Sound(value = 1000, 
    secs=.1, 
    stereo=True, 
    hamming=True,
    name='tone')

And here is my prefrence window:

You can also find the builder attached.
clock.psyexp (27.5 KB)