psychopy logging time when participant stopped moving cursor - python

I am using a rating scale. Participants use the 't' and 'b' keys to move the cursor along the scale. Each trial is currently 6 seconds long. If a participant stops pressing 't' or 'b' before 6 seconds are up, I want to log the time of the last keypress in my logfile. However, I'm not sure how to check which keypress is the last. I was thinking of logging the RT of the last keypress in the list, but code is checking for keypresses on every refresh. This is what I have so far:
trialNum=0
for eachPic in catPictures:
prevPos = 0
key=[]
b_list=[]
t_list=[]
timer = core.CountdownTimer(TrialDuration)
event.clearEvents() # get rid of other, unprocessed events
while timer.getTime() > 0:
for key in event.getKeys():
if key in ['escape']:
core.quit() # quit if they press escape
if key in ['b']:
# add keypress to list for each keypress. then move cursor proportionally to length of this list
b_list.append(key)
prevPos+=len(b_list)
if key in ['t']:
t_list.append(key)
prevPos-=len(t_list)

I would just have one list of keys and check the last element once the timer is up, i.e. after the while-loop (upon finish trial).
Don't initiate a whole new timer in each loop. Just reset it. Much more ressource-efficient.
Indent stuff in the while loop.
I don't understand why you move the cursor the distance of the number of previous key presses in that trial. It seems more reasonable to move it a fixed distance per key press. So I did that below.
Definitely check out Jeremy Gray's proposal of using the built-in psychopy.visual.RatingScale (another answer to this question).
Untested code:
timer = core.CountdownTimer(TrialDuration)
stepSize = 1
for eachPic in catPictures:
prevPos = 0 # keeps track of the slider position
rts=[] # used to keep track of what the latest reaction time was. Reset in the beginning of every trial.
timer.reset()
event.clearEvents() # get rid of other, unprocessed events
while timer.getTime() > 0:
for key, rt in event.getKeys(timeStamped=timer): # time keys to this clock
rts += [rt] # add this reaction time to the list
if key in ['escape']:
core.quit() # quit if they press escape
if key in ['b']:
# add keypress to list for each keypress. then move cursor proportionally to length of this list
prevPos+=stepSize
if key in ['t']:
prevPos-=stepSize
# Log here instead of print
print rts[-1]

For a given rating scale, rs, all of the subject's activity is available in rs.history, both during a trial and afterwards. The history is just a list of tuples, where each tuple is (rating, time). If the scale has started, the first tuple is always (None, 0.0). If the last two ratings are the same, then the subject accepted that rating. If they are different, the subject was moving around on the scale but had not accepted a rating at the point when the scale timed out.
Example history:
[(None, 0.0), (3, 0.777), (3, 1.396)]
from psychopy import visual, core
win = visual.Window()
rs = visual.RatingScale(win)
c = core.CountdownTimer(3)
while c.getTime() > 0:
rs.draw()
win.flip()
# print or log:
print rs.history # entire history
print rs.history[-1][1] # just the time of the last rating

Related

Two while loops on a countdown seem to be overlapping and running at the same time

I am using the psychopy code from this publication: "Using a variant of the optomotor response as a visual defect detection assay in zebrafish". The code is a series of while loops on a countdown. Each loop should run for 10 seconds. I want to add another countdown before the first 10 seconds loop that will last for 5 seconds and will just be a black screen.
The problem that I am having is that the first 5 seconds while loop (the black screen) is overlapping with the first 10 seconds while loop. This results in the first 10 second loop only lasting for 5 seconds. Oddly enough, the rest of the loops that are supposed to last for 10 seconds each are not overlapped or disturbed.
Does anyone have any ideas why this might be happening? I don't get any errors when I run my code.
Irrelevant note: I am planning on changing the times for each loop
#import libraries needed from PsychoPy
from psychopy import visual, core, event, sys
#create a window
mywin = visual.Window([1680,1050], monitor="testMonitor", units="deg", fullscr = True)
#To run the stimulus, "save" + "run"
#Create the black screen for acclimation
TIMEBLACK = 5
black = visual.GratingStim(win=mywin, size=999, pos=[0,0], sf=0, color=[-1,-1,-1])
blacktimer = core.CountdownTimer(TIMEBLACK)
#Create OMR Stimuli
TIME = 10 #this is seconds of stimulus display and should be changed based on experimental needs (See Table 2)
SPEED = 1.04 #1.033 is adult optimal, 1.04 is larvae optimal. See table for additional speeds; correlates to 12 angular cycles for adults and 16 angular cycles for larvae.
grating = visual.RadialStim(win=mywin, mask='circle', tex='saw',size=20, color=[1,1,1],pos=[0,0],angularCycles = 4, angularRes = 3600, contrast = -1.0) #angularCycles are the number of black/white bars presented by the stimulus. Adult optimal is 12 angular cycles, larvae optimal is 16 angular cycles (See Table 1).
fixation = visual.GratingStim(win=mywin, size=999, pos=[0,0], sf=0, color=[0.5,0.5,0.5])
timer = core.CountdownTimer(TIME)
while blacktimer.getTime()>0:
black.draw()
mywin.flip()
if len(event.getKeys())>0: sys.exit(0)
event.clearEvents()
blacktimer.reset(TIMEBLACK)
#draw the stimuli and update the window. The phase is always advanced by 0.05 of a cycle.
while True: #this creates a never-ending loop
while timer.getTime()>0:
grating.setAngularPhase(SPEED, '-')
grating.draw()
mywin.flip()
if len(event.getKeys())>0: sys.exit(0)
event.clearEvents()
timer.reset(TIME)
while timer.getTime()>0:
fixation.draw()
mywin.flip()
if len(event.getKeys())>0: sys.exit(0)
event.clearEvents()
timer.reset(TIME)
while timer.getTime()>0:
grating.setAngularPhase(SPEED, '+')
grating.draw()
mywin.flip()
if len(event.getKeys())>0: sys.exit(0)
event.clearEvents()
timer.reset(TIME)
while timer.getTime()>0:
fixation.draw()
mywin.flip()
if len(event.getKeys())>0: sys.exit(0)
event.clearEvents()
timer.reset(TIME)
if len(event.getKeys())>0: break
event.clearEvents()
#cleanup. To exit, press any key.
mywin.close()
core.quit()
Here are my updates after edits. I moved everything having to do with the black screen to be before the first 10 second while loop:
#Create the black screen for acclimation
TIMEBLACK = 5
black = visual.GratingStim(win=mywin, size=999, pos=[0,0], sf=0, color=[1,-1,-1])
blacktimer = core.CountdownTimer(TIMEBLACK)
while blacktimer.getTime()>0:
black.draw()
mywin.flip()
if len(event.getKeys())>0: core.quit(0)
blacktimer.reset(TIMEBLACK)
#Create OMR Stimuli
TIME = 10 #this is seconds of stimulus display and should be changed based on experimental needs (See Table 2)
SPEED = 1.04 #1.033 is adult optimal, 1.04 is larvae optimal. See table for additional speeds; correlates to 12 angular cycles for adults and 16 angular cycles for larvae.
grating = visual.RadialStim(win=mywin, mask='circle', tex='saw',size=20, color=[1,1,1],pos=[0,0],angularCycles = 4, angularRes = 3600, contrast = -1.0) #angularCycles are the number of black/white bars presented by the stimulus. Adult optimal is 12 angular cycles, larvae optimal is 16 angular cycles (See Table 1).
fixation = visual.GratingStim(win=mywin, size=999, pos=[0,0], sf=0, color=[0.5,0.5,0.5])
timer = core.CountdownTimer(TIME)
#draw the stimuli and update the window. The phase is always advanced by 0.05 of a cycle.
while True: #this creates a never-ending loop
while timer.getTime()>0:
grating.setAngularPhase(SPEED, '-')
grating.draw()
mywin.flip()
if len(event.getKeys())>0: core.quit(0)
timer.reset(TIME)
In the original version of your code, you create the first timer like this:
blacktimer = core.CountdownTimer(TIMEBLACK) # will run for 5 s
You then create a second one like this:
timer = core.CountdownTimer(TIME) # will run for 10 s
Each timer begins counting down from the moment it is created (or reset). So the second one (timer) will have counted down to ~ 5 seconds by the time the first (blacktimer) has reached 0 s, meaning that by the time you start using timer, half of its time has already elapsed.
Each subsequent time that you use timer, it will run correctly for 10 s, as you are re-setting it immediately before each loop.
Your revised version of the code seems to address this issue.
Additional suggestions: you should avoid calling sys.exit(0). PsychoPy provides a more graceful way of exiting via core.quit(), which does some tidying up on the way out, including saving any current data. Also no need to keep calling event.clearEvents() here: if a key event had occurred, your code would have exited.
Lastly, the recommendation these days (even for experienced Python developers) is to use the Builder GUI to generate the script for you. It uses best-practice techniques to give you good timing, and uses up-to-date APIs. For example, Builder scripts no longer use the old event module for keypress handling, instead using the much higher-performance Keyboard object. The GUI still allows you to insert custom Python via its graphical "Code Components", for when you need functionality that the GUI doesn't provide. It takes care of ensuring the code runs at the right time (e.g. at the beginning of each trial, or on every screen refresh) and means you can focus on whatever your unique functionality is, while all the housekeeping of recording data and showing stimuli on time is taken care of for you.
You'll also find that the best support for PsychoPy is from the dedicated forum at https://discourse.psychopy.org, rather than here at StackOverflow. The PsychoPy developers themselves are much more likely to see your posts there.

Changing one dictionary at two different events leads to KeyError in some cases (because of timing of key event + event due to dt)

Copy of the final question at the bottom:
"I have no idea how to solve this bug. One action is time dependent and the other one is player input dependent. Someone with an idea? Is there something in python working with mutable data which handles cases where one object gets changed at different times from different places?"
I code my own Tetris in Pygame. At the moment my goal is to rebuild the game mode "Survival" from here: https://jstris.jezevec10.com/?play=4
The player needs to survive as long as possible while every 1 second a new line with only 1 free spot will be added at the bottom and shifts the game field towards the top.
My problem is, there is a key.event where the player drops a piece and clears a row with that action and on the other hand there is time event where every one second a line is randomly added to the field. Both events change values in a dictionary. In general this code works but there is very small window of time where dependent on the actions the code throws an error.
Short explanation of my game:
There is a grid ( list of lists 10x20 ) where each element is a RGB color (0,0,0) starting everytime with black and will be filled with information (colors) of the current falling piece or the field at the bottom where pieces are locked. The grid will be drawn every frame. Pieces which are already placed are stored in a dictionary. The grid requests the dict for the locked pieces every time before drawing.
In this picture you see the original game. At that moment 6 lines already are added to the field.
Every line will be generated at the bottom of the field with the grey colour with one free random spot. Everytime a line will be generated the information is added in my "locked" dictionary ( key = (x,y) : value = (153,153,153) ) and a function changes the y value in every key by 1 to the top.
At the same time if the player clears a row (in this picture it would be the 6th row counting from the bottom) there is a function which deletes this cleared row in the "locked" dict and changes all y values which are greater than that line by 1 to the bottom.
Here are the two functions:
def clear_rows(grid, locked): # (0,0) is at the top left, x increases right, y increases down
i = len(grid) - 1 # last row
rows_cleared = 0
while i > -1:
row = grid[i]
if BLACK not in row: #which means the row is full and can be cleared
i_new = i + rows_cleared
for j in range(len(row)):
del locked[(j, i_new)]
rows_cleared += 1
for key in sorted(list(locked), key=lambda x: x[1])[::-1]:
x_coord, y_coord = key
if y_coord < i_new:
new_key = (x_coord, y_coord + 1)
locked[new_key] = locked.pop(key)
i -= 1
else:
i -= 1
return rows_cleared
and
def survival_add_line(locked):
for key in sorted(list(locked)):
x, y = key
new_key = (x, y - 1)
locked[new_key] = locked.pop(key)
gap = random.randint(0,9)
for i in range(10):
if not i == gap:
locked[(i, 19)] = SURVIVAL_GRAY
This is the code in my mainloop:
if level_time / 1000 >= 1:
survival_add_line(locked)
level_time = 0
if change_piece:
for pos in tetrimino_position:
locked[(pos[0], pos[1])] = current_piece.color
current_piece = next_pieces[0]
next_pieces.pop(0)
next_pieces.append(new_tetromino())
change_piece = False
cleared_lines += clear_rows(grid, locked)
I noticed the bug can only happen at the very bottom of the field. If I understand it right it happens when the player drops a piece in that exact moment when survival_add_line() is already called but before if change_piece.
Survival_add_line() shifts every line one to the top, then the clear_row function wants to delete the last row (which is now the second last) and it can't find the key in the last row to delete --> KeyError.
I have no idea how to solve this bug. One action is time dependent and the other one is player input dependent.
Someone with an idea?
Is there something in python working with mutable data which handles cases where one object gets changed at different times from different places?
Instead of calling both statements with if, an if/elif block gets the job done! (change_piece can now be delayed by 1 frame, but that's not noticeable for the player)
Posted on behalf of the question asker

How could I make a dash in a 2D pygame python program?

If the pygame program is just a basic entity you can move normally with arrow keys, how could i make it so that if space is pressed, based on the arrow key that was being held at the moment of pressing, a player dashes slightly to the specified direction? My idea is that when the space is pressed, program checks if other keys are being held down and based on that it rapidly increases the x and/or y coordinate and then stops at specific time, but I don't know how to make it stop as all of this is happening inside a main game loop. Any insight is highly appreciated.
You can use time.perf_counter.
So for using that function you will need to import module time
import time
And now you can store the value of time.perf_counter in a variable when space is pressed.
import time
jumping = False
start_time = 0
while True:
if ("space is pressed"): # Replace condition with what you want
jumping = True
start_time = time.perf_counter()
# Add your code here
if jumping:
# change of x, y and other jumping mechanism code here
if jumping and time.perf_counter() - start_time > 1: # Replace 1 with the amount you want this if condition to trigger, also add value in seconds
jumping = False
I'm not sure what your code is like, but this is how I'd go about it:
def dash(self):
keys = pygame.keys.get_pressed()
if keys[pygame.K_SPACE] and not self.dashing and self.jumping:
# ^^ Can be any key
if self.facing_right:
self.x += 20
if self.facing_left:
self.x -= 20
self.dashing = True
Make sure that when you hit the ground it does self.dashing = False else you will be able to dash forever.
Just put this function in the player class if you are doing it OOP.
Else if you are not using classes, put it anywhere in the file and take out all self..

skip a video (in a loop) based on keypress in PsychoPy using MovieStim2

I play 3 videos using MovieStim2 in PsychoPy. I suspect this issue stems from not really understanding loops, though.
I want to skip to the next video when the participant presses a key. I understand how my code quits when the participant presses "q", but I'm not sure how to make it skip to the next video when they press "b", for example. Here is my current code:
vidNum = 1
for f in ['clip1.mpg', 'clip2.mpg', 'clip3.mpg']:
clock.reset()
#logfile.write("AfterForLoopTime,%s,Video %s\n" % (core.getTime(), vidNum))
# Create your movie stim.
mov = visual.MovieStim2(win, videopath+f,
size=640,
# pos specifies the /center/ of the movie stim location
pos=[0, 0],
flipVert=False,
flipHoriz=False,
) # loop=False - need to comment this to use a loop
# Start the movie stim by preparing it to play
shouldflip = mov.play()
logfile.write("AfterShouldflipLine58,%s, Video %s\n" % (clock.getTime(), vidNum))
while mov.status != visual.FINISHED:
# Only flip when a new frame should be displayed. Can significantly reduce
# CPU usage. This only makes sense if the movie is the only /dynamic/ stim
# displayed.
if shouldflip:
# Movie has already been drawn , so just draw text stim and flip
#text.draw()
win.flip()
else:
# Give the OS a break if a flip is not needed
time.sleep(0.001)
# Drawn movie stim again. Updating of movie stim frames as necessary
# is handled internally.
shouldflip = mov.draw()
# Check for action keys.....
for key in event.getKeys():
if key in ['escape', 'q']:
win.close()
core.quit()
I tried adding code similar to the last chunk which quits after q:
elif key in ['b']:
break
but I realize that I really want to break out of this loop:
for f in ['clip1.mpg', 'clip2.mpg', 'clip3.mpg']:
However, this doesn't seem to work either
for f in ['clip1.mpg', 'clip2.mpg', 'clip3.mpg']:
for key in event.getKeys():
if key in ['b']:
break
The loop you actually want to break is:
while mov.status != visual.FINISHED:
The easiest way I can think of is to just set your movie status to -1 (or visual.FINISHED) if the user hits the key.
For example:
if key in ['b']:
mov.status = -1
This will break you out of your current video from what I can tell.

Write out only first result in while loop in psychopy

I want to print my trial onset time to my logfile. However, I need to write to the logfile within a while (timer) loop, which means what whatever I do in that loop will be done for every screen refresh.
The problem is that I only want to write the result of the first clock.getTime() call to the logfile. If I do this:
while timer.getTime() >0: # while time isn't up (turns neg when time's up)
for key in event.getKeys():
if key in ['escape']:
core.quit() # quit if they press escape
timeText.draw(window)
timeline.draw(window)
cursorImage.draw(window)
## flip so it actually appears
window.flip()
OnsetTime = clock.getTime()
logfile.write('OnsetTime, %s' % OnsetTime)
I get a bunch of lines of my logfile that say 'OnsetTime' and the time - one for every refresh.
I only want the first one to be printed, but I'm not sure how to do that.
This is just another way of doing what CasualDemon's proposing, but one which I think is more elegant (three lines of code for the logging instead of 5):
def logOnsetTime():
"""Function which allows launching this code right after a window.flip()"""
logfile.write('OnsetTime, %s' % clock.getTime())
window.callOnFlip(logOnsetTime) # runs on first flip
while timer.getTime() >0: # while time isn't up (turns neg when time's up)
for key in event.getKeys():
if key in ['escape']:
core.quit() # quit if they press escape
timeText.draw(window)
timeline.draw(window)
cursorImage.draw(window)
## flip so it actually appears.
window.flip()
If you want a log for every keypress, put the window.callOnFlip(logOnsetTime) inside the while loop. There's also a window.logOnFlip method specifically for logging, but that just saves an input string to the log, timestamped to a global clock, so it wouldn't save the time of your clock.
I think you want something like this:
while timer.getTime() >0: # while time isn't up (turns neg when time's up)
first = True
for key in event.getKeys():
if key in ['escape']:
core.quit() # quit if they press escape
timeText.draw(window)
timeline.draw(window)
cursorImage.draw(window)
## flip so it actually appears
window.flip()
OnsetTime = clock.getTime()
if first:
logfile.write('OnsetTime, %s' % OnsetTime)
first = False
However if you only want the very first one, first = True will have to be outside the While loop instead.

Categories