We'll use test data supplied by SR Research (which I found in the cili package for Python). The test data can be found in the extdata/ directory of the package.
require(eyelinker) require(dplyr) #Look for file fpath <- system.file("extdata/mono500.asc.gz",package="eyelinker")
asc files can be gigantic, so it's a good idea to compress them, R doesn't mind (here they're compressed in gzip format, hence the .gz).
To read the file just call read.asc:
dat <- read.asc(fpath)
dat is a list with fields:
names(dat)
Some meta-data can be read from the "SAMPLES" lines in the asc file.
str(dat$info)
Here we have a monocular recording of the left eye.
Depending on how the Eyelink is set up, positions can be reported in pixels or degrees, relative to the head, the screen or the camera. I'm guessing the most common case is to use screen coordinates, but I don't know whether the coordinate system is stored in a predictable manner in asc files. If you have any suggestions please email me. I'll assume you know what the relevant units are.
The raw data has a simple structure:
raw <- dat$raw head(raw,3)
In a binocular recording the raw data has the following structure:
dat.bi <- system.file("extdata/bino1000.asc.gz",package="eyelinker") %>% read.asc head(dat.bi$raw,3)
The variables are the same as before, with the addition of a postfix corresponding to the eye (i.e. xpl is the x position of the left eye).
It's sometimes more convenient for plotting and analysis if the raw data are in "long" rather than "wide" format, as in the following example:
library(tidyr) raw.long <- dplyr::select(raw,time,xp,yp,block) %>% gather("coord","pos",xp,yp) head(raw.long,2) tail(raw.long,2)
The eye position is now in a single column rather than two, and the column "coord" tells us if the valuye corresponds to the x or y position. The benefits may not be obvious now, but it does make plotting the traces via ggplot2 a lot easier:
require(ggplot2) raw.long <- mutate(raw.long,ts=(time-min(time))/1e3) #let's have time in sec. ggplot(raw.long,aes(ts,pos,col=coord))+geom_point()
In this particular file there are four separate recording periods, corresponding to different "blocks" in the asc file, which we can check using:
ggplot(raw.long,aes(ts,pos,col=coord))+geom_line()+facet_wrap(~ block)
The Eyelink automatically detects saccades in an online fashion. The results are converted to a data.frame:
sac <- dat$sac head(sac,2)
Each line corresponds to a saccade, and the different columns are:
In the binocular case, we have:
head(dat.bi$sac,3)
The only difference is in the "eye" column, which tells you in which eye the saccade was first recorded.
To see if the saccades have been labelled correctly, we'll have to find the corresponding time samples in the raw data.
The easiest way to achieve this is to view the detected saccades as a set of temporal intervals, with endpoints given by stime and etime. We'll use function "%In%" to check if each time point in the raw data can be found in one of these intervals.
Sac <- cbind(sac$stime,sac$etime) #Define a set of intervals with these endpoints #See also: intervals package raw <- mutate(raw,saccade=time %In% Sac) head(raw,3) mean(raw$saccade)*100 #6% of time samples correspond to saccades
Now each time point labelled with "saccade==TRUE" corresponds to a saccade detected by the eye tracker.
Let's plot traces again:
mutate(raw.long,saccade=time %In% Sac) %>% filter(block==1) %>% ggplot(aes(ts,pos,group=coord,col=saccade))+geom_line()
Fixations are stored in a very similar way to saccades:
fix <- dat$fix head(fix,3)
Each line is a fixation, and the columns are:
We can re-use essentially the same code to label fixations as we did to label saccades:
Fix <- cbind(fix$stime,fix$etime) #Define a set of intervals mutate(raw.long,fixation=time %In% Fix) %>% filter(block==1) %>% ggplot(aes(ts,pos,group=coord,col=fixation))+geom_line()
We can get a fixation index using whichInterval:
mutate(raw,fix.index=whichInterval(time,Fix)) %>% head(4)
Let's check that the average x and y positions are correct:
raw <- mutate(raw,fix.index=whichInterval(time,Fix)) fix.check <- filter(raw,!is.na(fix.index)) %>% group_by(fix.index) %>% summarise(axp=mean(xp),ayp=mean(yp)) %>% ungroup head(fix.check,3)
We grouped all time samples according to fixation index, and computed mean x and y positions.
We verify that we recovered the right values:
all.equal(fix.check$axp,fix$axp) all.equal(fix.check$ayp,fix$ayp)
Blinks are detected automatically, and stored similarly to saccades and fixations. We load a different dataset:
fpath <- system.file("extdata/monoRemote500.asc.gz",package="eyelinker") dat <- read.asc(fpath) dat$blinks
The fields should be self-explanatory. We'll re-use some the code above to label the blinks:
Blk <- cbind(dat$blinks$stime,dat$blinks$etime) #Define a set of intervals filter(dat$raw,time %In% Blk) %>% head
Not surprisingly, during blinks, eye position data is unavailable. Unfortunately, it takes the eyetracker a bit of time to detect blinks, and the eye position data around blinks may be suspect. The eyelink manual suggests that getting rid of samples that are within 100ms of a blink should eliminate most problems. We'll use some functions from package intervals to expand our blinks by 100ms:
require(intervals) Suspect <- Intervals(Blk) %>% expand(100,"absolute") Suspect
Here's an example of a trace around a blink:
raw.long <- dplyr::select(dat$raw,time,xp,yp,block) %>% gather("coord","pos",xp,yp) raw.long <- mutate(raw.long,ts=(time-min(time))/1e3) #let's have time in sec. ex <- mutate(raw.long,suspect=time %In% Suspect) %>% filter(block==2) ggplot(ex,aes(ts,pos,group=coord,col=suspect))+geom_line()+coord_cartesian(xlim=c(34,40))+labs(x="time (s)")
The traces around the blink are indeed spurious.
The last data structure we need to cover contains messages:
head(dat$msg)
The lines correspond to "MSG" lines in the original asc file. Since messages can be anything read.asc leaves them unparsed. If you're interested in certain event types (e.g., time stamps), you'll have to parse msg$text yourself. Here for example we extract all messages that contain the words "Saccade_target":
library(stringr) filter(dat$msg,str_detect(text,fixed("blank_screen")))
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.