|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
__author__ = "Edwin Dalmaijer" |
|
|
|
import numpy |
|
|
|
|
|
def blink_detection(x, y, time, missing=0.0, minlen=10): |
|
"""Detects blinks, defined as a period of missing data that lasts for at |
|
least a minimal amount of samples |
|
|
|
arguments |
|
|
|
x - numpy array of x positions |
|
y - numpy array of y positions |
|
time - numpy array of EyeTribe timestamps |
|
|
|
keyword arguments |
|
|
|
missing - value to be used for missing data (default = 0.0) |
|
minlen - integer indicating the minimal amount of consecutive |
|
missing samples |
|
|
|
returns |
|
Sblk, Eblk |
|
Sblk - list of lists, each containing [starttime] |
|
Eblk - list of lists, each containing [starttime, endtime, duration] |
|
""" |
|
|
|
|
|
Sblk = [] |
|
Eblk = [] |
|
|
|
|
|
mx = numpy.array(x == missing, dtype=int) |
|
my = numpy.array(y == missing, dtype=int) |
|
miss = numpy.array((mx + my) == 2, dtype=int) |
|
|
|
|
|
diff = numpy.diff(miss) |
|
starts = numpy.where(diff == 1)[0] + 1 |
|
ends = numpy.where(diff == -1)[0] + 1 |
|
|
|
|
|
for i in range(len(starts)): |
|
|
|
s = starts[i] |
|
|
|
if i < len(ends): |
|
e = ends[i] |
|
elif len(ends) > 0: |
|
e = ends[-1] |
|
else: |
|
e = -1 |
|
|
|
|
|
if e - s >= minlen: |
|
|
|
Sblk.append([time[s]]) |
|
|
|
Eblk.append([time[s], time[e], time[e] - time[s]]) |
|
|
|
return Sblk, Eblk |
|
|
|
|
|
def remove_missing(x, y, time, missing): |
|
mx = numpy.array(x == missing, dtype=int) |
|
my = numpy.array(y == missing, dtype=int) |
|
x = x[(mx + my) != 2] |
|
y = y[(mx + my) != 2] |
|
time = time[(mx + my) != 2] |
|
return x, y, time |
|
|
|
|
|
def fixation_detection(x, y, time, missing=0.0, maxdist=25, mindur=50): |
|
"""Detects fixations, defined as consecutive samples with an inter-sample |
|
distance of less than a set amount of pixels (disregarding missing data) |
|
|
|
arguments |
|
|
|
x - numpy array of x positions |
|
y - numpy array of y positions |
|
time - numpy array of EyeTribe timestamps |
|
|
|
keyword arguments |
|
|
|
missing - value to be used for missing data (default = 0.0) |
|
maxdist - maximal inter sample distance in pixels (default = 25) |
|
mindur - minimal duration of a fixation in milliseconds; detected |
|
fixation cadidates will be disregarded if they are below |
|
this duration (default = 100) |
|
|
|
returns |
|
Sfix, Efix |
|
Sfix - list of lists, each containing [starttime] |
|
Efix - list of lists, each containing [starttime, endtime, duration, endx, endy] |
|
""" |
|
|
|
x, y, time = remove_missing(x, y, time, missing) |
|
|
|
|
|
Sfix = [] |
|
Efix = [] |
|
|
|
|
|
si = 0 |
|
fixstart = False |
|
for i in range(1, len(x)): |
|
|
|
|
|
squared_distance = ((x[si] - x[i]) ** 2 + (y[si] - y[i]) ** 2) |
|
dist = 0.0 |
|
if squared_distance > 0: |
|
dist = squared_distance ** 0.5 |
|
|
|
if dist <= maxdist and not fixstart: |
|
|
|
si = 0 + i |
|
fixstart = True |
|
Sfix.append([time[i]]) |
|
elif dist > maxdist and fixstart: |
|
|
|
fixstart = False |
|
|
|
if time[i - 1] - Sfix[-1][0] >= mindur: |
|
Efix.append([Sfix[-1][0], time[i - 1], time[i - 1] - Sfix[-1][0], x[si], y[si]]) |
|
|
|
else: |
|
Sfix.pop(-1) |
|
si = 0 + i |
|
elif not fixstart: |
|
si += 1 |
|
|
|
if len(Sfix) > len(Efix): |
|
Efix.append([Sfix[-1][0], time[len(x) - 1], time[len(x) - 1] - Sfix[-1][0], x[si], y[si]]) |
|
return Sfix, Efix |
|
|
|
|
|
def saccade_detection(x, y, time, missing=0.0, minlen=5, maxvel=40, maxacc=340): |
|
"""Detects saccades, defined as consecutive samples with an inter-sample |
|
velocity of over a velocity threshold or an acceleration threshold |
|
|
|
arguments |
|
|
|
x - numpy array of x positions |
|
y - numpy array of y positions |
|
time - numpy array of tracker timestamps in milliseconds |
|
|
|
keyword arguments |
|
|
|
missing - value to be used for missing data (default = 0.0) |
|
minlen - minimal length of saccades in milliseconds; all detected |
|
saccades with len(sac) < minlen will be ignored |
|
(default = 5) |
|
maxvel - velocity threshold in pixels/second (default = 40) |
|
maxacc - acceleration threshold in pixels / second**2 |
|
(default = 340) |
|
|
|
returns |
|
Ssac, Esac |
|
Ssac - list of lists, each containing [starttime] |
|
Esac - list of lists, each containing [starttime, endtime, duration, startx, starty, endx, endy] |
|
""" |
|
x, y, time = remove_missing(x, y, time, missing) |
|
|
|
|
|
Ssac = [] |
|
Esac = [] |
|
|
|
|
|
|
|
|
|
intdist = (numpy.diff(x) ** 2 + numpy.diff(y) ** 2) ** 0.5 |
|
|
|
inttime = numpy.diff(time) |
|
|
|
inttime = inttime / 1000.0 |
|
|
|
|
|
|
|
|
|
vel = intdist / inttime |
|
|
|
|
|
acc = numpy.diff(vel) |
|
|
|
|
|
t0i = 0 |
|
stop = False |
|
while not stop: |
|
|
|
|
|
|
|
|
|
|
|
sacstarts = numpy.where((vel[1 + t0i:] > maxvel).astype(int) + (acc[t0i:] > maxacc).astype(int) >= 1)[0] |
|
if len(sacstarts) > 0: |
|
|
|
t1i = t0i + sacstarts[0] + 1 |
|
if t1i >= len(time) - 1: |
|
t1i = len(time) - 2 |
|
t1 = time[t1i] |
|
|
|
|
|
Ssac.append([t1]) |
|
|
|
|
|
sacends = numpy.where((vel[1 + t1i:] < maxvel).astype(int) + (acc[t1i:] < maxacc).astype(int) == 2)[0] |
|
if len(sacends) > 0: |
|
|
|
t2i = sacends[0] + 1 + t1i + 2 |
|
if t2i >= len(time): |
|
t2i = len(time) - 1 |
|
t2 = time[t2i] |
|
dur = t2 - t1 |
|
|
|
|
|
if dur >= minlen: |
|
|
|
Esac.append([t1, t2, dur, x[t1i], y[t1i], x[t2i], y[t2i]]) |
|
else: |
|
|
|
Ssac.pop(-1) |
|
|
|
|
|
t0i = 0 + t2i |
|
else: |
|
stop = True |
|
else: |
|
stop = True |
|
|
|
return Ssac, Esac |
|
|