library(ggplot)
pdir <- getwd()
# https://github.com/LabNeuroCogDevel/autoeyescore
setwd("/mnt/hdd/home/foranw/src/work/autoeyescore")
source("score_arrington.R")
setwd(pdir)
We ran an eye calibration task first with the 32 channel head coil (once). Then with the 64 (three times).
The second 64 channel collection had the mirror tilled such that the projected screen was especially low in the field of view (“low”). The third 64 run was with the screen reflecting on the top of the mirror (“high”).
echo "time(PM) filename"
perl -MFile::Basename=basename -lne 'if(m/TimeStamp.*?([0-9:]+) PM/){$t=$1; print $t, " ", (basename($ARGV) =~ s/sub-will|_.*//gr)}' ~/scratch/sub-will*|sort
The task is a visually guided saccade/fixation calibration run configured so a yellow circle (“dot”) appears for 2s. The small dot is positioned on the horizontal meridian from 90% left of the screen edge to 90% right excluding 20% on either side of center. A 2 second center fixation cross (“iti”) is between each dot.
There should have been 10 dot events with positions evenly distributed (2s dot + 2s iti * 10 == 40s total), but the last trial was never presented. (The last screen flipped w/o a wait, so the program ended early.)
eyetxts <- Sys.glob("~/scratch/sub-will*.txt")
tracking_list <- lapply(eyetxts, \(f) read_arr(f) %>%
# msg is 'iti' or like '1 dot .9'
separate(msg,c("trial","event","pos"),sep=" ") %>%
mutate(event=ifelse(is.na(event), 'iti',event),
trial=as.numeric(trial),
pos=round(as.numeric(pos),2),
# include where data comes from
fname=gsub('.*/sub-will|_.*','',f)) %>%
fill(trial, pos, .direction="up") %>%
group_by(trial) %>%
mutate(t=TotalTime-first(TotalTime)))
tracking_list %>%
lapply(\(x) x%>%group_by(fname) %>%
summarise(trials=max(trial,na.rm=T), time=max(TotalTime), samples=n()))%>%
bind_rows
fname | trials | time | samples |
---|---|---|---|
32test | 9 | 37.8196 | 2265 |
64screen2test-high | 9 | 37.8697 | 2267 |
64screentest-low | 9 | 37.903 | 2262 |
64test | 9 | 37.903 | 2273 |
There were two especially bad runs. With the 32 channel coil arrington’s software could not get a stable pupil lock. In the “low” version of 64 channel coil run, the pupil tracking was less noisy but also frequent periods of no tracking.
# many plots into grid. prefer facet_wrap version
plots <- lapply(tracking_list,
\(d) ggplot(d %>% filter(!is.na(trial)) +
aes(x=t, y=X_CorrectedGaze, color=event) +
geom_point() +
facet_wrap(~pos) +
theme(legend.position='none')+
ggtitle(d$fname[1]))
do.call(cowplot::plot_grid, plots)
d_all <- tracking_list %>% bind_rows %>% filter(!is.na(trial))
d_median <- d_all %>%
group_by(fname,trial,event,pos) %>%
summarise(x_median=median(X_CorrectedGaze,na.rm=T))
raw_plot <- ggplot(d_all) +
aes(x=t, y=X_CorrectedGaze, color=event) +
geom_point(size=.5) +
facet_grid(fname~pos) +
geom_hline(data=d_median,aes(yintercept=x_median, linetype=event)) +
lims(y=c(-1.5,1.5),x=c(1,4)) +
cowplot::theme_cowplot() +
labs(y="eye x pos (corrected)", x="time (s)",
title="[VGS] Per trial-position horz. eye time series; median event lines")
print(raw_plot)
Surprisingly, even the noisy 32 channel coil’s median fixation can correctly identify at least the side of the dot during fixation.
med_vals <- d_median %>%
spread(event,x_median) %>%
mutate(dist=iti-dot) %>%
rbind(data.frame(
fname='ideal', pos=c(-.9,.9),
dist=c(.5-.05, .5-.95))) %>%
mutate(type=case_when(
grepl('64',fname)~ '64 channel coil',
grepl('32',fname)~ '32 channel coil',
grepl('ideal',fname)~ 'ideal',
TRUE ~ 'oops'))
ggplot(med_vals) +
aes(x=pos, y=dist, color=fname, linetype=type) +
geom_hline(yintercept=0, alpha=.5) +
geom_point() +
geom_line(aes(group=fname)) +
scale_linetype_manual(values=c(5,3,1)) +
cowplot::theme_cowplot() +
labs(x="presented dot's horz. position (-1 left, 1 right)",
y="median center fix (iti) - median dot fix",
linetype="head coil",
color="data from",
title="horz gaze side discrimination")
d_in1sec <- d_median %>%
inner_join(d_all) %>% group_by(fname, pos, trial, event) %>%
mutate(event_t=TotalTime - first(TotalTime)) %>%
filter(event_t>1)# %>%
base_box <- function(d) ggplot(d) +
aes(y=X_CorrectedGaze-x_median, fill=fname) +
geom_boxplot() +
cowplot::theme_cowplot() +
scale_x_discrete(labels = NULL, breaks = NULL) +
labs(y="diff from median. 1s after onset", fill="data from")
cowplot::plot_grid(
d_in1sec %>% filter(event=='dot') %>% base_box +
facet_grid(.~pos) + theme(legend.position="none") +
labs(y="", title="eye tracking quality: range around median fixation point"),
base_box(d_in1sec) + facet_grid(.~event),
nrow=2)
LNCDR::ld8from(txtfiles)
11878 was a single 8 min run with bad tracking.
plot_overview(dr_list[[1]])
11880 has noisy data. Small head with coil center obstructing eye. Participant might have also ignored all neutral trials. complicating averaging.
plot_overview(dr_list[[2]])
plot_overview(dr_list[[3]])