python中的眼动数据分析(Eye-link)

时间:2018-08-12 14:56:09

标签: python-3.x psychopy neuroscience eye-tracking

我有来自眼动追踪的数据(.edf文件-来自SR研究的Eyelink)。我想对其进行分析,并获得各种方法,例如注视,扫视,持续时间等。 是否有现有的软件包可以分析眼动数据? 谢谢!

3 个答案:

答案 0 :(得分:1)

至少要将.edf文件导入到熊猫DF中,可以使用Niklas Wilming制作的以下程序包:RecyclerView
这应该已经解决了扫视和注视-看一下自述文件。将它们放入数据框中后,您可以对它进行任何分析。

答案 1 :(得分:1)

嘿,这个问题似乎还很老,但也许我可以重新激活它,因为我目前正面临着同样的情况。 首先,我建议将.edf转换为.asc文件。这样,更容易阅读以获得第一印象。 为此,存在许多工具,但是我使用了SR-Research Eyelink Developers Kit(here)。

我不知道您的设置,但是Eyelink 1000本身可以检测扫视和注视。我在.asc文件中的情况如下:

SFIX L   10350642
10350642      864.3   542.7  2317.0
...
...
10350962      863.2   540.4  2354.0
EFIX L   10350642   10350962    322   863.1   541.2    2339
SSACC L  10350964
10350964      863.4   539.8  2359.0
...
...
10351004      683.4   511.2  2363.0
ESACC L  10350964   10351004    42    863.4   539.8   683.4   511.2    5.79     221

第一个数字对应于时间戳,第二个和第三个对应于x-y坐标,最后一个是您的瞳孔直径(我不知道ESACC之后的最后一个数字是什么)。

SFIX -> start fixation
EFIX -> end fixation
SSACC -> start saccade
ESACC -> end saccade

您也可以查看PyGaze,但我还没有使用它,但是在搜索工具箱时,总是弹出此工具箱。

编辑 我找到了这个工具箱here。看起来很酷,并且可以很好地处理示例数据,但不幸的是,不适用于我的

修改2号 在我处理自己的Eyetracking数据后,我重新考虑了这个问题,我认为我可能会共享编写的用于处理数据的函数:

def eyedata2pandasframe(directory):
'''
This function takes a directory from which it tries to read in ASCII files containing eyetracking data
It returns  eye_data: A pandas dataframe containing data from fixations AND saccades fix_data: A pandas dataframe containing only data from fixations
            sac_data: pandas dataframe containing only data from saccades
            fixation: numpy array containing information about fixation onsets and offsets
            saccades: numpy array containing information about saccade onsets and offsets
            blinks: numpy array containing information about blink onsets and offsets 
            trials: numpy array containing information about trial onsets 
'''
eye_data= []
fix_data = []
sac_data = []
data_header = {0: 'TimeStamp',1: 'X_Coord',2: 'Y_Coord',3: 'Diameter'}
event_header = {0: 'Start', 1: 'End'}
start_reading = False
in_blink = False
in_saccade = False
fix_timestamps = []
sac_timestamps = []
blink_timestamps = []
trials = []
sample_rate_info = []
sample_rate = 0
# read the file and store, depending on the messages the data
# we have the following structure:
# a header -- every line starts with a '**'
# a bunch of messages containing information about callibration/validation and so on all starting with 'MSG'
# followed by:
# START 10350638    LEFT    SAMPLES EVENTS
# PRESCALER 1
# VPRESCALER    1
# PUPIL AREA
# EVENTS    GAZE    LEFT    RATE     500.00 TRACKING    CR  FILTER  2
# SAMPLES   GAZE    LEFT    RATE     500.00 TRACKING    CR  FILTER  2
# followed by the actual data:
# normal data --> [TIMESTAMP]\t [X-Coords]\t [Y-Coords]\t [Diameter]
# Start of EVENTS [BLINKS FIXATION SACCADES] --> S[EVENTNAME] [EYE] [TIMESTAMP]
# End of EVENTS --> E[EVENT] [EYE] [TIMESTAMP_START]\t [TIMESTAMP_END]\t [TIME OF EVENT]\t [X-Coords start]\t [Y-Coords start]\t [X_Coords end]\t [Y-Coords end]\t [?]\t [?]
# Trial messages --> MSG timestamp\t TRIAL [TRIALNUMBER]
try:
    with open(directory) as f:
        csv_reader = csv.reader(f, delimiter ='\t')
        for i, row in enumerate (csv_reader):
            if any ('RATE' in item for item in row):
                sample_rate_info = row
            if any('SYNCTIME' in item for item in row):          # only start reading after this message
                start_reading = True
            elif any('SFIX' in item for item in row): pass
                #fix_timestamps[0].append (row)
            elif any('EFIX' in item for item in row):
                fix_timestamps.append ([row[0].split(' ')[4],row[1]])
                #fix_timestamps[1].append (row)
            elif any('SSACC' in item for item in row): 
                #sac_timestamps[0].append (row)
                in_saccade = True
            elif any('ESACC' in item for item in row):
                sac_timestamps.append ([row[0].split(' ')[3],row[1]])
                in_saccade = False
            elif any('SBLINK' in item for item in row):          # stop reading here because the blinks contain NaN
                # blink_timestamps[0].append (row)
                in_blink = True
            elif any('EBLINK' in item for item in row):          # start reading again. the blink ended
                blink_timestamps.append ([row[0].split(' ')[2],row[1]])
                in_blink = False
            elif any('TRIAL' in item for item in row):
                # the first element is 'MSG', we don't need it, then we split the second element to seperate the timestamp and only keep it as an integer
                trials.append (int(row[1].split(' ')[0]))
            elif start_reading and not in_blink:
                eye_data.append(row)
                if in_saccade:
                    sac_data.append(row)
                else:
                    fix_data.append(row)

    # drop the last data point, because it is the 'END' message
    eye_data.pop(-1)
    sac_data.pop(-1)
    fix_data.pop(-1)
    # convert every item in list into a float, substract the start of the first trial to set the start of the first video to t0=0
    # then devide by 1000 to convert from milliseconds to seconds
    for row in eye_data:
        for i, item in enumerate (row):
            row[i] = float (item)

    for row in fix_data:
        for i, item in enumerate (row):
            row[i] = float (item)

    for row in sac_data:
        for i, item in enumerate (row):
            row[i] = float (item)

    for row in fix_timestamps:
        for i, item in enumerate (row):
            row [i] = (float(item)-trials[0])/1000

    for row in sac_timestamps:
        for i, item in enumerate (row):
            row [i] = (float(item)-trials[0])/1000

    for row in blink_timestamps:
        for i, item in enumerate (row):
            row [i] = (float(item)-trials[0])/1000

    sample_rate = float (sample_rate_info[4])

    # convert into pandas fix_data Frames for a better overview
    eye_data = pd.DataFrame(eye_data)
    fix_data = pd.DataFrame(fix_data)
    sac_data = pd.DataFrame(sac_data)
    fix_timestamps = pd.DataFrame(fix_timestamps)
    sac_timestamps = pd.DataFrame(sac_timestamps)
    trials = np.array(trials)
    blink_timestamps = pd.DataFrame(blink_timestamps)
    # rename header for an even better overview
    eye_data = eye_data.rename(columns=data_header)
    fix_data = fix_data.rename(columns=data_header)
    sac_data = sac_data.rename(columns=data_header)
    fix_timestamps = fix_timestamps.rename(columns=event_header)
    sac_timestamps = sac_timestamps.rename(columns=event_header)
    blink_timestamps = blink_timestamps.rename(columns=event_header)
    # substract the first timestamp of trials to set the start of the first video to t0=0
    eye_data.TimeStamp -= trials[0]
    fix_data.TimeStamp -= trials[0]
    sac_data.TimeStamp -= trials[0]
    trials -= trials[0]
    trials = trials /1000      # does not work with trials/=1000
    # devide TimeStamp to get time in seconds
    eye_data.TimeStamp /=1000
    fix_data.TimeStamp /=1000
    sac_data.TimeStamp /=1000
    return eye_data, fix_data, sac_data, fix_timestamps, sac_timestamps, blink_timestamps, trials, sample_rate
except:
    print ('Could not read ' + str(directory) + ' properly!!! Returned empty data')
    return eye_data, fix_data, sac_data, fix_timestamps, sac_timestamps, blink_timestamps, trials, sample_rate

希望对您有帮助。您可能需要更改代码的某些部分,例如在哪里拆分字符串以获取有关事件开启/偏移的关键信息的索引。或者,您不想将时间戳转换为秒,或者不想将第一次试用的开始时间设置为0。这取决于您。 另外,在我的数据中,我们还发送了一条消息,告知我们何时开始测量(“ SYNCTIME”),而我的实验中只有一个条件,因此只有一个“ TRIAL”消息

欢呼

答案 2 :(得分:0)

pyeparse似乎是另一个可用于眼动数据分析的库(至今似乎尚未维护)。

以下是他们的示例的简短摘录:

import numpy as np
import matplotlib.pyplot as plt

import pyeparse as pp

fname = '../pyeparse/tests/data/test_raw.edf'

raw = pp.read_raw(fname)

# visualize initial calibration
raw.plot_calibration(title='5-Point Calibration')

# create heatmap
raw.plot_heatmap(start=3., stop=60.)

编辑:发布答案后,我发现了一个不错的列表,其中列出了许多用于Eyelink edf数据分析的潜在工具:https://github.com/davebraze/FDBeye/wiki/Researcher-Contributed-Eye-Tracking-Tools