Eyelink眼动仪与Psychopy连接

       

        本文介绍了如何将Eyelink眼动仪与Psychopy连接并输出Eyelink官方.EDF数据文件。在网上找了很久没找到这方面的操作流程,有用iohub(Psychopy内置眼动组件)实现的,但是感觉问题比较多,所以自己研究了一下。

        主要参考了Eyelink官方例程(picture.py),对其中的一些代码段进行了修改,虽然可能还存在问题,但在实际实验中确认了可行性。该操作过程不需要从头编写代码,仅用Builder中的Code组件即可实现。

        写在这里作为笔记,也供有需要的同学参考。

目录

1 准备工作

2 代码解读

2.1 实验前预设代码

2.1.1 额外引用

2.1.2 函数定义

结束/中止实验函数

中止记录函数

清屏函数

2.1.3 模式预设

2.1.4 本地数据文件夹创建

2.1.5 连接眼动仪

2.1.6 Host数据文件创建

2.1.8 显示设置

2.1.9 校准前设置

2.2 功能模块

2.2.1 开始校准

2.2.2 试次数记录与主机显示

2.2.3 试次前drift-check

2.2.4 开始记录

2.2.5 标记图片刺激呈现时刻

2.2.6 将背景图片录入数据文件

2.2.7 兴趣区

2.2.8 停止记录

2.2.9 试次结束后写入数据文件

3 可行的操作流程

4 可能存在的问题

5 总结        


1 准备工作

        首先要在实验程序运行机下载Eyelink SDK ,可以去官网下载,需要注册并等待(最长)24小时审核。网上也可以搜到网盘版本。安装好后,在SR Research文件夹下会有例程文件,打开python子文件夹下的examples文件夹,里面有Psychopy例程。

        随便打开一个例程文件夹,将里面的 EyeLinkCoreGraphicsPsychoPy.py 文件复制到实验程序目录下。


2 代码解读

        首先对实验中需要用到的代码进行功能解读,这些代码段将被置于Psychopy实验程序中的不同位置,具体操作流程见下一部分。

2.1 实验前预设代码

        预设部分将在实验开始前对眼动仪进行基本配置,这一部分可以作为整体插入实验。

2.1.1 额外引用

import pylink
import platform
import time
from EyeLinkCoreGraphicsPsychoPy import EyeLinkCoreGraphicsPsychoPy

2.1.2 函数定义

结束/中止实验函数

        用于关闭并下载数据文件到被试机。

def terminate_task():
    """ Terminate the task gracefully and retrieve the EDF data file

    file_to_retrieve: The EDF on the Host that we would like to download
    win: the current window used by the experimental script
    """

    el_tracker = pylink.getEYELINK()

    if el_tracker.isConnected():
        # Terminate the current trial first if the task terminated prematurely
        error = el_tracker.isRecording()
        if error == pylink.TRIAL_OK:
            abort_trial()

        # Put tracker in Offline mode
        el_tracker.setOfflineMode()

        # Clear the Host PC screen and wait for 500 ms
        el_tracker.sendCommand('clear_screen 0')
        pylink.msecDelay(500)

        # Close the edf data file on the Host
        el_tracker.closeDataFile()

        # Download the EDF data file from the Host PC to a local data folder
        # parameters: source_file_on_the_host, destination_file_on_local_drive
        local_edf = os.path.join(session_folder, session_identifier + '.EDF')
        try:
            el_tracker.receiveDataFile(edf_file, local_edf)
        except RuntimeError as error:
            print('ERROR:', error)

        # Close the link to the tracker.
        el_tracker.close()

中止记录函数

        用于异常中止数据记录。

def abort_trial():
    """Ends recording """

    el_tracker = pylink.getEYELINK()

    # Stop recording
    if el_tracker.isRecording():
        # add 100 ms to catch final trial events
        pylink.pumpDelay(100)
        el_tracker.stopRecording()

    # clear the screen
    clear_screen(win)
    # Send a message to clear the Data Viewer screen
    bgcolor_RGB = (116, 116, 116)
    el_tracker.sendMessage('!V CLEAR %d %d %d' % bgcolor_RGB)

    # send a message to mark trial end
    el_tracker.sendMessage('TRIAL_RESULT %d' % pylink.TRIAL_ERROR)

    return pylink.TRIAL_ERROR

清屏函数

        用于清空屏幕。

def clear_screen(win):
    """ clear up the PsychoPy window"""

    win.fillColor = win.color
    win.flip()

2.1.3 模式预设

        设置了是否为Retina屏幕以及是否为模拟模式。

# Set this variable to True if you use the built-in retina screen as your
# primary display device on macOS. If have an external monitor, set this
# variable True if you choose to "Optimize for Built-in Retina Display"
# in the Displays preference settings.
use_retina = False  #选择是否为Mac Retina屏幕
# Set this variable to True to run the script in "Dummy Mode"
dummy_mode = False  #选择是否为模拟模式(无眼动仪情况下)

2.1.4 本地数据文件夹创建

        此处将被试号作为数据文件名,在实验程序文件夹中创建了results文件夹,并针对每个数据创建了数据文件名+时间的子文件夹。

#以被试号(str(expInfo['Participant']))作为数据文件名
edf_fname=(str(expInfo['Participant']))

results_folder = 'results'
if not os.path.exists(results_folder):
    os.makedirs(results_folder)

time_str = time.strftime("_%Y_%m_%d_%H_%M", time.localtime())
session_identifier = edf_fname + time_str

# create a folder for the current session in the "results" folder
session_folder = os.path.join(results_folder, session_identifier)
if not os.path.exists(session_folder):
    os.makedirs(session_folder)

2.1.5 连接眼动仪

        连接眼动仪主机,眼动仪主机IP一般为100.1.1.1。

if dummy_mode:
    el_tracker = pylink.EyeLink(None)
else:
    try:
        el_tracker = pylink.EyeLink("100.1.1.1")
    except RuntimeError as error:
        print('ERROR:', error)
        core.quit()

2.1.6 Host数据文件创建

        在主机创建并打开数据文件(会覆盖主机上先前的同名文件)。

edf_file = edf_fname + ".EDF"  #数据文件名称
try:
    el_tracker.openDataFile(edf_file)
except RuntimeError as err:
    print('ERROR:', err)
    # close the link if we have one open
    if el_tracker.isConnected():
        el_tracker.close()
    core.quit()

#实验程序名记录,建议路径及文件名中不要用中文
preamble_text = 'RECORDED BY %s' % os.path.basename(__file__)
el_tracker.sendCommand("add_file_preamble_text '%s'" % preamble_text)

2.1.7 配置眼动仪

# Put the tracker in offline mode before we change tracking parameters
el_tracker.setOfflineMode()

# Get the software version:  1-EyeLink I, 2-EyeLink II, 3/4-EyeLink 1000,
# 5-EyeLink 1000 Plus, 6-Portable DUO
eyelink_ver = 0  # set version to 0, in case running in Dummy mode
if not dummy_mode:
    vstr = el_tracker.getTrackerVersionString()
    eyelink_ver = int(vstr.split()[-1].split('.')[0])
    # print out some version info in the shell
    print('Running experiment on %s, version %d' % (vstr, eyelink_ver))

# File and Link data control
# what eye events to save in the EDF file, include everything by default
file_event_flags = 'LEFT,RIGHT,FIXATION,SACCADE,BLINK,MESSAGE,BUTTON,INPUT'
# what eye events to make available over the link, include everything by default
link_event_flags = 'LEFT,RIGHT,FIXATION,SACCADE,BLINK,BUTTON,FIXUPDATE,INPUT'
# what sample data to save in the EDF data file and to make available
# over the link, include the 'HTARGET' flag to save head target sticker
# data for supported eye trackers
if eyelink_ver > 3:
    file_sample_flags = 'LEFT,RIGHT,GAZE,HREF,RAW,AREA,HTARGET,GAZERES,BUTTON,STATUS,INPUT'
    link_sample_flags = 'LEFT,RIGHT,GAZE,GAZERES,AREA,HTARGET,STATUS,INPUT'
else:
    file_sample_flags = 'LEFT,RIGHT,GAZE,HREF,RAW,AREA,GAZERES,BUTTON,STATUS,INPUT'
    link_sample_flags = 'LEFT,RIGHT,GAZE,GAZERES,AREA,STATUS,INPUT'
el_tracker.sendCommand("file_event_filter = %s" % file_event_flags)
el_tracker.sendCommand("file_sample_data = %s" % file_sample_flags)
el_tracker.sendCommand("link_event_filter = %s" % link_event_flags)
el_tracker.sendCommand("link_sample_data = %s" % link_sample_flags)

# Optional tracking parameters
# Sample rate, 250, 500, 1000, or 2000, check your tracker specification
# if eyelink_ver > 2:
#     el_tracker.sendCommand("sample_rate 1000")
# Choose a calibration type, H3, HV3, HV5, HV13 (HV = horizontal/vertical),
el_tracker.sendCommand("calibration_type = HV9")
# Set a gamepad button to accept calibration/drift check target
# You need a supported gamepad/button box that is connected to the Host PC
#el_tracker.sendCommand("button_function 5 'accept_target_fixation'")

2.1.8 显示设置

        根据实际情况在psychopy显示器中心提前设置显示器参数(width, distance)

# get the native screen resolution used by PsychoPy
scn_width, scn_height = win.size #置于Win创建后
# resolution fix for Mac retina displays
if 'Darwin' in platform.system():
    if use_retina:
        scn_width = int(scn_width/2.0)
        scn_height = int(scn_height/2.0)
# Pass the display pixel coordinates (left, top, right, bottom) to the tracker
# see the EyeLink Installation Guide, "Customizing Screen Settings"
el_coords = "screen_pixel_coords = 0 0 %d %d" % (scn_width - 1, scn_height - 1)
el_tracker.sendCommand(el_coords)
# Write a DISPLAY_COORDS message to the EDF file
# Data Viewer needs this piece of info for proper visualization, see Data
# Viewer User Manual, "Protocol for EyeLink Data to Viewer Integration"
dv_coords = "DISPLAY_COORDS  0 0 %d %d" % (scn_width - 1, scn_height - 1)
el_tracker.sendMessage(dv_coords)

2.1.9 校准前设置

        设置校准界面的背景色、前景色、校准目标类型、大小以及校准时的声音(可复制例程中的音频文件),并在psychopy中打开该图形界面。

# Configure a graphics environment (genv) for tracker calibration
genv = EyeLinkCoreGraphicsPsychoPy(el_tracker, win)
print(genv)  # print out the version number of the CoreGraphics library

# Set background and foreground colors for the calibration target
# in PsychoPy, (-1, -1, -1)=black, (1, 1, 1)=white, (0, 0, 0)=mid-gray
foreground_color = (-1, -1, -1) #校准时的文字颜色
background_color = win.color #校准时的背景色,此处与实验一致
genv.setCalibrationColors(foreground_color, background_color)
# Set up the calibration target
#
# The target could be a "circle" (default), a "picture", a "movie" clip,
# or a rotating "spiral". To configure the type of calibration target, set
# genv.setTargetType to "circle", "picture", "movie", or "spiral", e.g.,
# genv.setTargetType('picture')
#
# Use gen.setPictureTarget() to set a "picture" target
# genv.setPictureTarget(os.path.join('images', 'fixTarget.bmp'))
#
# Use genv.setMovieTarget() to set a "movie" target
# genv.setMovieTarget(os.path.join('videos', 'calibVid.mov'))

# Use a circle as the calibration target
genv.setTargetType('circle') #设置校准目标类型,此处为圆圈
# Configure the size of the calibration target (in pixels)
genv.setTargetSize(24) #设置目标大小
genv.setCalibrationSounds('', '', '') #设置校准过程中声音(可省略)
# resolution fix for macOS retina display issues
if use_retina:
    genv.fixMacRetinaDisplay()

# Request Pylink to use the PsychoPy window we opened above for calibration
pylink.openGraphicsEx(genv)

2.2 功能模块

2.2.1 开始校准

        启动设置界面进行校准(空屏后按回车开始)。

if not dummy_mode:
    try:
        clear_screen(win)
        el_tracker.doTrackerSetup()
    except RuntimeError as err:
        print('ERROR:', err)
        el_tracker.exitCalibration()

2.2.2 试次数记录与主机显示

        将试次计数和刺激图片路径显示在主机界面下方,此处需要提前定义变量:trial_index(试次计数)及 picdir(刺激图片路径)。

el_tracker = pylink.getEYELINK()
# put the tracker in the offline mode first
el_tracker.setOfflineMode()

# clear the host screen before we draw the backdrop
# el_tracker.sendCommand('clear_screen 0')
el_tracker.sendMessage('TRIALID %d' % trial_index) #
el_tracker.sendMessage('PIC %s' % picdir)
# Component updates done
# record_status_message : show some info on the Host PC
# here we show how many trial has been tested
status_msg = 'TRIAL number %d-PIC:%s'  % (trial_index,picdir)
el_tracker.sendCommand("record_status_message '%s'" % status_msg)
# Skip drift-check if running the script in Dummy Mode

2.2.3 试次前drift-check

        (慎用)在每个试次前进行drift-check,实际实验中按ESC可以从该环节进入设置界面进行重新校准,但校准完成后很大概率实验程序会报错闪退,可能是和实验程序本身的ESC有冲突。

# Skip drift-check if running the script in Dummy Mode
while not dummy_mode:
    # terminate the task if no longer connected to the tracker or
    # user pressed Ctrl-C to terminate the task
    if (not el_tracker.isConnected()) or el_tracker.breakPressed():
        terminate_task()
    # drift-check and re-do camera setup if ESCAPE is pressed
    try:
        error = el_tracker.doDriftCorrect(int(scn_width/2.0),
                                          int(scn_height/2.0), 1, 1)
        # break following a success drift-check
        if error is not pylink.ESC_KEY:
            break
    except:
        pass
    clear_screen(win)
    
    
    

2.2.4 开始记录

        运行该代码,眼动仪开始记录。

if not dummy_mode:
    try:
        el_tracker.startRecording(1, 1, 1, 1)
    except RuntimeError as error:
        print("ERROR:", error)
        abort_trial()

2.2.5 标记图片刺激呈现时刻

        最好在刺激呈现后立即运行,中间不要隔太多行,但在Psychopy中有点难实现。

el_tracker.sendMessage('image_onset')
img_onset_time = core.getTime()  # record the image onset time

2.2.6 将背景图片录入数据文件

        将背景图片的相对路径(bg_imag)写入数据文件,在数据查看器中会作为背景显示,文件路径里千万不要有空格,最好也不要有中文。

        因为是相对数据文件的位置,所以要加 '../../'。 S[0]  和 S[1] 为图片材料像素大小,可根据具体情况进行修改。

clear_screen(win)
bgcolor_RGB = (116, 116, 116)
el_tracker.sendMessage('!V CLEAR %d %d %d' % bgcolor_RGB)
bg_image = '../../'+picdir 
imgload_msg = '!V IMGLOAD CENTER %s %d %d %d %d' % (bg_image,
                                                        int(scn_width/2.0),
                                                        int(scn_height/2.0),
                                                        int(S[0]),
                                                        int(S[1]))
el_tracker.sendMessage(imgload_msg)

2.2.7 兴趣区

        创建一个矩形兴趣区(可省略)。

ia_pars = (1, left, top, right, bottom, 'screen_center')
el_tracker.sendMessage('!V IAREA RECTANGLE %d %d %d %d %d %s' % ia_pars)

2.2.8 停止记录

        停止数据记录。

el_tracker.stopRecording()

2.2.9 试次结束后写入数据文件

        在试次结束后将试次信息写入数据文件,此处写入了两个条件、图片材料路径、按键反应类型及反应时。除了最后一句代码,其它部分可按实际情况进行修改。

# record trial variables to the EDF data file, for details, see Data
# Viewer User Manual, "Protocol for EyeLink Data to Viewer Integration"
el_tracker.sendMessage('!V TRIAL_VAR condition1 %s' % con1)
el_tracker.sendMessage('!V TRIAL_VAR condition2 %s' % con2)
el_tracker.sendMessage('!V TRIAL_VAR image %s' % picdir)
el_tracker.sendMessage('!V TRIAL_VAR RT %f' % key_resp_10.rt)
el_tracker.sendMessage('!V TRIAL_VAR Key %s' % key_resp_10.keys)
# send a 'TRIAL_RESULT' message to mark the end of trial, see Data
# Viewer User Manual, "Protocol for EyeLink Data to Viewer Integration"
el_tracker.sendMessage('TRIAL_RESULT %d' % pylink.TRIAL_OK)


3 可行的操作流程

        基础程序编写:将包含以上代码的Code组件置于Psychopy的不同部分即可实现连通,所以第一步是编好一个不需要眼动仪的完整程序。

        插入实验前预设代码:在整个实验程序最前端新建一个空白Routine,添加Code组件,将所有实验前预设代码按顺序写入(Begin Routine中,如无特殊说明,下同)。

        开始校准:一般每次休息后都应该进行校准,因此可以在每次休息后的部分新建空白Routine,添加Code组件,写入校准代码段。

        试次中流程:其余代码应该在每个试次循环中均运行一次。

        首先可以在每个试次循环的最开始创建空白Routine,插入试次数(提前定义并在每个试次中更新)记录与主机显示代码,发送要显示在屏幕上的试次信息,可以在其后添加drift-check代码(注:drift-check过程中最好不要按ESC进入设置界面,很可能会闪退,程序较短这一步可以不加,或者用注视点检测代替);

        随后,在刺激呈现后立即开始数据记录、标记图片刺激呈现时刻并将背景图片录入数据文件。在Psychopy中,图片刺激好像是在进入Routine的第二帧才刷新,所以很难保持完全同步,不过要求不高的话应该也可以忽略不计了。这三部分代码可以放在呈现刺激Routine里的Code组件中。此处也可以设置兴趣区,不过一般会在数据处理阶段设置。

        然后在刺激消失时(或者是需要结束记录的地方)添加结束记录代码,如果刺激呈现单独占用一个Routine,可以将结束记录代码写入这一部分的End Routine中。

        最后,在试次结束后(记录被试反应后)将试次条件、反应时等信息录入数据文件,可将这部分代码放在试次最后一个Routine的Code组件(End Routine)中。

        结束实验并下载数据:在实验的最后加入Code组件,运行 terminate_task() 函数


4 可能存在的问题

        计时问题:没办法保证时间的精确记录,不过要求不高的话这个问题可以忽略;

        按ESC退出后数据文件无法保存,记录无法中止:在Psychopy中可以设置按ESC退出实验程序,但是无法保存眼动数据也无法结束记录,如果在试次记录开始后按ESC,主机上依然会继续记录。一个可行的解决办法是将程序导出为python代码,然后在代码中搜索Esc,在每一个ESC按键事件后添加terminate_task()abort_trial()两个函数;

        代码的简洁性:可用,但是可能有一些代码是多余的;

        Drift-check阶段进入设置界面会闪退:可能存在按键冲突。

        


5 总结        

        还是有一点点复杂的,如果没有特殊需求的话还是用Eyelink自带的实验编程软件吧,编写好一定要完整试运行一下。

        用pylink包还可以将图片刺激传输到主机作为背景,但是好像会比较耗时,如果是视频刺激的话应该也可以解决。

        其他功能可以参考官方例程及Pylink api userguide。

        

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 24
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值