0

I have some experience in Matlab, but am very new to PsychoPy.

For now I would like to continuously switch between two images until there is a keyboard response. Each image should stay on the screen exactly for 100ms, and I want to be able to verify that this is the case (e.g. in the log file).

I sort of got it right by using core.wait(.084) after win.flip() - on a 60Hz screen that gives approximately 100ms. I am verifying it by writing the frame of each flip to the log file with win.logOnFlip()

But I believe I could be way more precise I only knew how to define the duration of the image in terms of frames.

The function core.wait() only takes time in seconds, and not in frames, correct?

I would be very grateful if you could give me some tips on how to achieve (and verify) presentation of each image for 6 frames.

Thanks a ton in advance

Best

Sebastian

Here my code:

import os                           # for file/folder operations
from psychopy import visual, event, core, gui, data, logging

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

# screen size in pixels
scrsize = (600,400)                

# gather info participant
exp_name = 'MyFirstPsychoPy'
exp_info = {
        'participant': '',  
        }
dlg = gui.DlgFromDict(dictionary=exp_info, title=exp_name)

# if user pressed cancel quit
if dlg.OK == False:
    core.quit()  

# Get date and time
exp_info['date'] = data.getDateStr()
exp_info['exp_name'] = exp_name

#save a log file for detail verbose info
filename = _thisDir + os.sep + 'data/%s_%s_%s' %(exp_info['participant'], exp_name, exp_info['date'])
# print filename   #to see if path correct
logFile = logging.LogFile(filename+'.log', level=logging.DEBUG)
logging.console.setLevel(logging.WARNING)  #  outputs to the screen, not a file


# Create a window small window
win = visual.Window(size=scrsize, color='white', units='pix', fullscr=False)

# or go full screen
#win = visual.Window([1280,1024], fullscr=True, allowGUI=False, waitBlanking=True)

# this is supposed to record all frames
win.setRecordFrameIntervals(True)   

# show instructions until spacebar
start_message = visual.TextStim(win,
                            text="hello. you will see mondrians. press space to respond.",
                            color='red', height=20)
event.clearEvents()
keys = event.getKeys(keyList=['space', 'escape'])  #allow only space and escape keys
while len(keys) == 0:
    start_message.draw()
    win.flip()

    keys = event.getKeys(keyList=['space', 'escape'])
    if len(keys)>0:
        break

print keys  #show on output screen
keys = event.clearEvents()  # empty keys
keys = event.getKeys(keyList=['space', 'escape'])


# define 2 pictures
bitmap1 = visual.ImageStim(win, 'Mondrians/Mask_1.bmp', size=scrsize)
bitmap2 = visual.ImageStim(win, 'Mondrians/Mask_2.bmp', size=scrsize)
bitmap = bitmap1


# Initialize clock to register response time
rt_clock = core.Clock()
rt_clock.reset()  # set rt clock to 0


# show alternating pics until response
frameN = 0
while len(keys) == 0:       

    if bitmap == bitmap1:
        bitmap = bitmap2
    else:
        bitmap = bitmap1

    bitmap.draw() 

    win.logOnFlip(msg='frame=%i' %frameN, level=logging.DEBUG)  #record the time of win.flip() in the log file
    win.flip()  # show image
    frameN = frameN + 1 

    core.wait(.084)  # wait 100 ms


    keys = event.getKeys(keyList=['space', 'escape'])    #record resp

    # if response stop
    if len(keys)>0:
        rt = rt_clock.getTime()
        break      

print keys, rt  #show resp and rt on screen

win.saveFrameIntervals(filename+'.log', clear=True)

win.close()
core.quit()

1 Answers1

1

Yes, there is a better way! The standard solution exploits the fact that win.flip() halts code execution until the next monitor update. So looping over win.flip() will give you exactly one frame per loop. So to switch between two imagesStims (bitmap1 and bitmap2) until there is a response:

clock = core.Clock()  # to assess timing
keepLooping = True
while keepLooping:  # continue until break
    for thisBitmap in [bitmap1, bitmap2]:  # alternate between images
        if keepLooping:  # do not show bitmap2 if a key was pressed on bitmap1
            for frameN in range(6):  # 100 ms
                thisBitmap.draw()
                print clock.getTime()
                win.callOnFlip(clock.reset)  # ... or use win.logOnFlip
                win.flip()  # inner loop is timed to this, as long as the rest of the code in here doesn't take longer than a frame.

                keys = event.getKeys(keyList=['space', 'escape'])
                if keys:  # notice simplification. [] evaluates to False.
                    rt = rt_clock.getTime()
                    keepLooping = False
                    break

... and then the rest. I've used core.Clock() to assess the time here, but your win.logOnFlip() is just as good. Depends on what kind of output you want.

Note that event.getKeys() records the time this line was executed, not the time that the key was pressed. Therefore it adds a small delay. In this "frame-locked loop" the key response is therefore discretized to frame intervals. If you want to get real asynchronous polling of the keyboard state (i.e. if up to +16ms error in RT recording matters), use the iohub module. Many keyboards have an inherent 10-30 ms latency anyway, so you can't get rid of all latency.

Jonas Lindeløv
  • 5,442
  • 6
  • 31
  • 54
  • Hi Jonas, thanks so much for your response. Following your suggestion, images are now presented for what appear to be 100ms/6 frames. Unfortunately, I'm not sure about precise timing because the experiment now never stops (it somehow doesn't take the spacebar once the images are switching back and forth), and the log file is not written (a blank log file is created). The times printed to the output window with print clock.getTime() are in the 0.0002 range, which I don't understand. Isn't the clock supposed to be in seconds? So. What now? Thanks again. – Sebastian Korb Jan 10 '15 at 17:31
  • Absent log file: is just because I chose to output flip-durations to console rather than a log file. You can just insert your approach, if you want the file (using ``win.logOnFlip``). Flip time: yes, that should be ~0.0167. Maybe your computer doesn't synchronize with the monitor. Try running Coder --> Demos --> Timing --> timesByFrames.py which gives you a histogram of your frame durations as seen from the computer. It should be narrow around 0.01667. If it isn't, upgrade graphics card drivers or use a different computer. – Jonas Lindeløv Jan 10 '15 at 19:35
  • Registering keys: sorry, just had a single break within three loops. I've updated the answer and made a ``keepLooping`` variable to control whether it should keep looping. Not very elegant but it works :-) – Jonas Lindeløv Jan 10 '15 at 19:39
  • Hi Jonas, everything is working fine now. My computer synchronizes fine with the monitor, as tested with the timesByFrames demo, and I thought the beginning of a log file should nevertheless be created, even if I then don't write in it at each win.flip(). But it doesn't matter, I have gone back to using lonOnFlip and it works. Thanks again. – Sebastian Korb Jan 13 '15 at 08:55
  • You're welcome. Remember to accept this answer if it solved your problem (tick mark on the left). That way other users can see that there is a solution. – Jonas Lindeløv Jan 13 '15 at 18:06