2 Analysis Setup
2.1 Defining Variables and Folders
First, we set up some global variables that will be used throughout the analyses. This includes the identifiers of the subjects to include in the analysis, specifics of the design such as the number of days and events per day and the regions of interest for representational similarity analysis.
#-------- DEFINE GLOBAL VARIABLES ---------
# list of subject IDs(excluded: 58 and 68 because of bad memory performance and MRI acquisition problems)
<- c("031", "032", "033", "034", "035", "036", "037", "038", "039", "054",
subjects "055", "056", "057", "059", "061", "062", "063", "064", "065", "066",
"069", "070", "071", "072", "073", "074", "075", "076")
<- length(subjects)
n_subs
# PVT scanning runs and blocks within each run
<- c("pre", "post")
runs <- length(runs)
n_runs <- 10
n_blocks
# design parameters
<- 4
n_days <- 5
n_events_day <- n_days*n_events_day
n_pics
# Main regions of interest for analyses
<- c("aHPC_lr", "alEC_lr")
rois
# Regions of interest in MNI space
= c("alEC_lr")
rois_ec = c("aHPC_lr")
rois_hpc <- c(rois_hpc, rois_ec)
rois_mni <- c("#dd4731", "#079cd6")
roi_colors
# Regions of interest to get from Freesurfer
= c("hpc_lr", "ec_lr")
rois_fs # numeric Freesurfer labels
#(https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/AnatomicalROI/FreeSurferColorLUT)
= list(c(17,53), c(1006,2006))
labels_fs <- wes_palette(n = length(rois_fs), name = "FantasticFox1")
roi_colors_fs
# Define colors to use in plots
<- scico::scico(n=5, begin = 0, end = 0.6, palette = "bamako")
event_colors <- c("#26588E", "#33602D", "#C1AA6E")
time_colors
<- c("#dd4731", # main
aHPC_colors "#26588E", # within main, from scico::scico(n=5, begin = 0.1, end = 0.7, palette = "devon")[3],
"#e31a1c", # within low
"#800026", # within high
"#A54440", # across main, from scico::scico(n=5, begin = 0.3, end = 0.9, palette = "lajolla")[3]
"#feb24c", # across low
"#fc4e2a") # across high
names(aHPC_colors) <- c("main", "within_main", "within_low", "within_high", "across_main", "across_low", "across_high")
<- c("#855C85",
alEC_colors "#225ea8",
"#1d91c0",
"#0c2c84",
"#7fcdbb",
"#c7e9b4",
"#41b6c4")
names(alEC_colors) <- c("main", "within_main", "within_low", "within_high", "across_main", "across_low", "across_high")
<- "#F5DF4D"
day_time_int_color <- unname(alEC_colors["main"])
time_within_across_color <- "#939597" ultimate_gray
Folder Structure
In a second step, we create some folders that will be used during the analysis. These are folders that contain raw data as well as folders into which processed data is written. CAVE: Some Markdown files still create their own folders.
#-------- SET UP FOLDERS ---------
<- c()
dirs
# directory with logs from picture viewing task
$pvt_log_dir <- here("data", "behavior", "logs", "picture_viewing_task")
dirs
# directory with logs from day learning task
$dlt_log_dir <- here("data", "behavior", "logs", "day_learning")
dirs
# directory with logs from the timeline task
$timeline_dat_dir <- here("data", "behavior", "timeline")
dirs
# directories for freesurfer ROIs
$rois_fs_dirs = here("data", "mri", "rois", rois_fs)
dirs
# directories for final ROIs in analysis space
$rois_ss_dirs <- here("data", "mri", "rois", rois, "samespace")
dirs
# directories for the MNI ROIs in analysis space
$rois_mni_ss_dirs <- here("data", "mri", "rois", rois_mni, "samespace")
dirs
# directory where preprocessed data lies for each run and block
$feat_dir <- here("data", "mri", "processed", "functionalDataPerBlock")
dirs
# directory with MRI data in the analysis space (samespace)
$samespace_dir <- here("data", "mri", "processed", "samespace")
dirs
# base directory for RSA
$rsa_dir <- here("data", "mri", "rsa")
dirs
# directories for RSA correlation matrices
$rsa_roi_corr_mat_dirs <- here("data", "mri", "rsa", "correlation_matrices", rois)
dirs
# directories for RSA pattern similarity change
$rsa_roi_ps_change_dirs <- here("data", "mri", "rsa", "pattern_similarity_change", rois)
dirs
# directories for the cleaned timeseries data
$rsa_roi_clean_timeseries_dirs <- here("data", "mri", "rsa", "clean_roi_timeseries", rois)
dirs
# directories for the relevant volumes of each ROI
$rsa_roi_rel_vol_dirs <- here("data", "mri", "rsa", "relevant_roi_volumes", rois)
dirs
# directory for data on which to run RSA
$rsa_dat_dir <- here("data", "mri", "rsa", "data_for_rsa")
dirs
$mask_dir <- here("data", "mri", "processed", "group_masks")
dirs
# base directory for RSA searchlights
$searchlight <- here("data", "mri", "rsa", "searchlight")
dirs
# data for final analysis (this will be shared)
$data4analysis <- here("data_for_analysis")
dirs
# directory for source data for nature communications
$source_dat_dir <-here("source_data_natcomms")
dirsif(dir.exists(dirs$source_dat_dir)){unlink(dirs$source_dat_dir, recursive = TRUE)} # delete if already existing to make sure to start with empty directory
# directory to save figures to
= here("figures")
fig_dir if(!dir.exists(fig_dir)){dir.create(fig_dir)}
# create all the directories
<- lapply(unlist(dirs), function(x) if(!dir.exists(x)) dir.create(x, recursive=TRUE)) dirs_created
2.2 Define Analysis Functions
Analysis Logic
We run both the behavioral analysis and the RSA using two approaches: A summary statistics approach and with linear mixed effect models.
In the analysis of the timeline task, we test whether virtual time explains remembered times when competing for variance with order and real time in seconds.
In order to assess the change in pattern similarity that is due to the learning task, we will later calculate the difference of the Fisher transformed mean correlation coefficients for every pair of scenes between the pre and the post picture viewing tasks (see this section). We will analyze how these difference values relate to various predictor variables derived from the learning task, such as the temporal distance between pairs of scenes within a day.
The two analysis approaches are briefly outlined below.
Summary Statistics
The summary statistics approach is based on permutation testing.
# control number of permutations used throughout the analyses
<- 10000 n_perm
We will use 10000 random permutations throughout the analyses.
In the summary statistics approach, we use the different time metrics as predictors for the remembered times in the timeline task. We will thus run one GLM per participant. In RSA, we set up a GLM with the given variable from the learning task as a predictor and the pairwise RSA difference values as criterion for every participant.
The resulting model coefficients are then compared to a null distribution obtained from shuffling the dependent variable of the linear model (i.e. pattern similarity change) for a large number of times. This results in a p-value for each coefficient, which is transformed to a Z-score. The Z-scores are then taken to the second level for group-level statistics.
This is also described in the methods section of the manuscript:
For the summary statistics approach, we ran a multiple regression analysis for each participant with virtual time, sequence position (order), and real time since the first event of a day as predictors of responses in the timeline task. To test whether virtual time indeed explained participants’ responses even when competing for variance with order and real time, included in the model as control predictors of no interest, we compared the participant-specific t-values of the resulting regression coefficients against null distributions obtained from shuffling the remembered times against the predictors 10000 times. We converted the resulting p-values to Z-values and tested these against zero using a permutation-based t-test (10000 random sign-flips, Figure 2E).
In the summary statistics approach, we used the different time metrics as predictors for pattern similarity change. We set up a GLM with the given variable from the day learning task as a predictor and the pairwise representational change values as the criterion for every participant. The t-values of the resulting model coefficients were then compared to a null distribution obtained from shuffling the dependent variable of the linear model (i.e. pattern similarity change) 10000 times. This approach to permutation-testing of regression coefficients controls Type I errors even under situations of collinear regressors (Anderson and Legendre, 1999). Resulting p-values for each coefficient were transformed to a Z-score. The Z-scores were then used for group-level inferential statistics.
We start by defining the function that permutes the dependent variable of the linear model. This approach was described e.g. by Manly (1997) and is referred to as permutation of raw data in Anderson & Legendre (1999), who compare different ways to implement permutation tests for (partial) regression coefficients. Their simulations show that the chosen approach does well in terms of controlling type I errors and power, even under situations of collinear regressors.
# define function that calculates z-value for permutation
<- function(in_dat = df, lm_formula = lm_formula, nsim = 1000){
lm_perm_jb
# run the model for original data and store observed t-values
<- lm(formula = lm_formula, data=in_dat)
lm_fit <- coef(summary(lm_fit))[,"t value"]
obs
# extract the dependent variable from the formula
<- str_extract(lm_formula, "[^~]+")
dv <- str_replace_all(dv, fixed(" "), "")
dv if(!(dv %in% colnames(in_dat))){stop("Cannot find dependent variable in input data");}
# initialize df for permutation
<- in_dat
data_perm
# set aside space for results
<- matrix(nrow = nsim, ncol = length(obs))
res
for (i in 1:nsim) {
# scramble response value
<- sample(nrow(in_dat))
perm <- in_dat[dv]
dv_dat <- dv_dat[perm,]
data_perm[dv]
# compute linear model and store the t-value of predictor
<- lm(formula = lm_formula, data=data_perm)
lm_fit_perm <- coef(summary(lm_fit_perm))[,"t value"]
res[i,]
}
# append the observed value to the list of results
<- rbind(res,obs)
res
# calculate p-value for each coefficient and transform to z
<- rep(0,length(obs))
p <- rep(0,length(obs))
z for (i_coef in 1:length(obs)){
<- sum(res[,i_coef] >= obs[i_coef])/nrow(res)
p[i_coef] <- qnorm(1-p[i_coef])
z[i_coef]
}return(z)
}
The Z-values resulting from this first-level permutation are then analyzed on the group level. For t-tests, we use random sign-flips (c.f. the one-sample t-test in FSL Randomise) to non-parametrically test against 0 or assess within-participant differences between conditions. For this, we use the function defined below that is a reduced version of this Matlab implementation.
Group-level statistics were carried out using permutation-based procedures. For t-tests, we compared the observed t-values against a surrogate distribution obtained from 10000 random sign-flips to non-parametrically test against 0 or to assess within-participant differences between conditions. Permutation-based repeated measures ANOVAs were carried out using the permuco package (Frossard and Renaud, 2019).
# function for permutation-based one-sample t-test against 0
# requires tidyverse and broom
# diff should be a vector of values (e.g. differences between paired samples)
# returns the data frame from broom::tidy(t.test(diff)) with an additional column
# for the p-value from n_perm random sign flips
# this function is based on the mult_comp_perm_t1 function for Matlab by David Groppe)
<- function(in_dat, n_perm=10000){
paired_t_perm_jb
# make sure input is a vector
<- unname(unlist(in_dat))
in_dat
# number of observations
<- length(in_dat)
n
# get t of unpermuted data
<- t.test(in_dat) %>% broom::tidy()
t_stats
# run n_perm iterations
<- vector("numeric", n_perm)
t_perm
for(i in 1:n_perm){
# randomly shuffle sign of each observation and get t-value
<- in_dat*sample(c(-1,1),n,replace=TRUE)
dat_perm <- abs(t.test(dat_perm)$statistic)
t_perm[i]
}
#add the negative of all values since we assume null hypothesis distribution is symmetric
<-c(t_perm, -t_perm)
t_perm
# add observed t-value so p cannot be 0
<-c(t_perm, t_stats$statistic)
t_perm
# calculate two-tailed p-value
<- mean(t_perm >= abs(t_stats$statistic))*2
p_perm <- t_stats %>%
t_stats ::add_column(p_perm = p_perm, .after = "p.value")
tibblereturn(t_stats)
}
Linear Mixed Effects
Linear Mixed Effects models consist of fixed and random effects. In our case the fixed case are of interest and consist of the temporal relationships that could explain remembered times or pattern similarity change (the dependent variables in the behavioral analysis and RSA, respectively). Random effects account for the fact that data points from different participants enter the estimation of the fixed effects.
Following the recommendation for maximal random effect structures by Barr et al. (JML, 2013), we first attempt to fit a model with a random effects structure including random intercepts and random slopes for participants. If the model does not converge or results in singular fits, we reduce the random effects structure, attempting to always at least keep random slopes for the fixed effect of interest in the model as these are crucial to avoid anti-conservativity.
Statistical inference about a model is done using a likelihood ratio tests against a nested, reduced model. The reduced model is identical to the full model, only the fixed effect of interest is removed.
LME assumptions
The code below defines a function to generate three diagnostic plots to visually assess the assumptions of a mixed model.
- Linearity & Homoscedasticity: Residual plot
- Normality of residuals: QQ-Plot and histogram of the residuals
It returns a ggplot object based on the input LME model. Probably these diagnostic plots will not end up in the manuscript because space is limited.
<- function(lmm = lmm_full){
lmm_diagplots_jb
# residual plot to inspect homoscedasticity
<- ggplot() +
resids_gg geom_point(aes(x = fitted(lmm), y = residuals(lmm)),
size = 1, shape = 16, alpha = 0.5) +
geom_smooth(aes(x = fitted(lmm), y = residuals(lmm)),
formula = "y ~ x", method='glm', se = TRUE, color = "darkred") +
ylab('residuals') +
xlab('fitted values') +
ggtitle("Residual Plot")+
theme_cowplot()
# data frame for QQ plot and histogram
<- data.frame(r = residuals(lmm))
df
# QQ-Plot look at residuals of the model
<- ggplot(df, aes(sample = r)) +
qqplot_gg stat_qq(size = 1, shape = 16, alpha = 0.5) +
stat_qq_line(color = "darkred") +
ggtitle("QQ-Plot")+
theme_cowplot()
# Histogram of residuals
<-ggplot(df, aes(x=r)) +
hist_gg geom_histogram(aes(y=..density..), colour="black", fill="darkgrey", bins = 50) +
geom_density(color="darkred") +
xlab("magnitude of residual") +
ggtitle("Histogram of Residuals")+
theme_cowplot()
# collect for final figure
<- resids_gg + qqplot_gg + hist_gg &
diag_fig theme(text = element_text(size=10),
axis.text = element_text(size=8),
legend.position = 'auto',
aspect.ratio = 1,
plot.title = element_text(hjust = 0.5)) &
plot_annotation(tag_levels = 'A',
caption = paste0(deparse(formula(lmm)), collapse=""))
return (diag_fig)
}
LME Summary Tables
To summarize final models we create tables inspired by the best practice guidelines by Meteyard & Davies (JML, 2020). Examples can be found on their OSF page, in particular the example reporting table on page 4 of this document.
To create these tables we rely on the broom.mixed package to get the LME model summary in a tidy format. The tidy dataframes are then converted to huxtables, which can be nicely formatted.
The function below takes as an input the tidy data frames for fixed effects, random effects as well as the ANOVA results from the comparison of the full model against a reduced model without the fixed effect of interest. All are merged into one table to limit the number of tables.
<- function(fix_df, ran_df, aov_mdl, fe_terms=NULL, re_terms=NULL, re_groups=NULL, lme_form=NULL, caption="Summary of Linear Mixed Effects Model"){
make_lme_huxtable
########### FIXED EFFECTS
# create huxtable for fixed effects and format it
<- fix_df %>% huxtable::huxtable(., add_colnames = TRUE) %>%
fix_hux
# set standard error and t-value column names
set_contents(row = 1, col = 4, value = "SE") %>%
set_contents(row = 1, col = 5, value = "t-value") %>%
# merge the confidence column title and reset column name
merge_cells(row = 1, col = 6:7) %>%
set_contents(row = 1, col = 6, value = "95% CI") %>%
set_align(row = 1, col = 6, value = "center") %>%
# align the contents
set_align(row = 1, col = 2:5, "center") %>%
set_align(row = 1, col = 1, "center") %>%
set_align(row = 2:nrow(.), col = 3:7, "center") %>%
# how many digits to print?
set_number_format(row = 2:nrow(.), col = 3:7, 6) %>%
set_number_format(row = 2:nrow(.), col = 5, 2) %>%
# add header row for fixed effects
::select(-effect) %>% # remove column to the left because not needed
dplyr::insert_row(.,c("fixed effects", rep("",ncol(.)-1)), after=0) %>%
huxtableset_align(row=1,col=1, value="center") %>%
set_header_cols(col=1, value=TRUE) %>%
# bottom border
set_bottom_border(row=nrow(.), value=0.5)
# if names for fixed effect terms are supplied use them
if(!is.null(fe_terms)){fix_hux$term[3:nrow(fix_hux)] <- fe_terms}
########### RANDOM EFFECTS
# create huxtable for random effects and format it
<- ran_df %>% huxtable::huxtable(., add_colnames = TRUE) %>%
ran_hux
# how many digits to print?
set_number_format(row = 2:nrow(.), col = 4, 6) %>%
# align header row
set_align(row = 1, col = c(1,2), "left") %>%
# add header row for random effects
select(-effect) %>% # remove column to the left because not needed
::insert_row(.,c("random effects", rep("",ncol(.)-1)), after=0) %>%
huxtableset_align(row=1,col=1, value="center") %>%
set_header_cols(col=1, value=TRUE) %>%
# center estimate data
set_align(row=2:nrow(.),col=ncol(.), value="center") %>%
# bottom border
set_bottom_border(row=nrow(.), value=0.5)
# if names for random effect grouping factor are supplied use them
if(!is.null(re_groups)){ran_hux$group[3:nrow(ran_hux)] <- re_groups}
# if names for random effect terms are supplied use them
if(!is.null(re_terms)){ran_hux$term[3:nrow(ran_hux)] <- re_terms}
########### MODEL COMPARISON
# determine how many digits of p-value to print
if(aov_mdl$`Pr(>Chisq)`[2]>=0.001)
<-3} # 3 digits if p>0.001
{num_fmtelse{
<-NA # use default -> scientific notation
num_fmt$`Pr(>Chisq)`[2]<-formatC(aov_mdl$`Pr(>Chisq)`[2], format = "e", digits = 2)}
aov_mdl
<- aov_mdl %>% as.data.frame() %>%
aov_hux ::rownames_to_column(var ="term") %>%
tibblehuxtable(add_colnames = TRUE) %>%
# align contents
set_align(row = 1:nrow(.), col = c(2:ncol(.)), "center") %>%
set_align(row = 1, col = 1, "left") %>%
# add header row for model comparison
::insert_row(.,c("model comparison", rep("",ncol(.)-1)), after=0) %>%
huxtable#merge_cells(1:nrow(.), col=1) %>%
set_align(row=1,col=1, value="center") %>%
set_header_cols(col=1, value=TRUE) %>%
# add model names
set_contents(row = 2:nrow(.), col = "term", value = c("model", "reduced model", "full model")) %>%
set_align(row = 2, col = 2, "left") %>%
# change some column names
set_contents(row = 2, col="Pr(>Chisq)", value = "p") %>%
set_contents(row = 2, col="logLik", value = "LL") %>%
set_contents(row = 2, col="Df", value = "df") %>%
set_contents(row = 2, col="Chisq", value = "X2") %>%
#set_contents(row = 2, col="Chisq", value = expression("$\\chi"^"2")) %>%
# how many digits to print? (do at the end because affected by adding columns)
set_number_format(row = 3:4, col = 3:7, 2) %>%
set_number_format(row = 4, col = 9, num_fmt) %>%
# remove the deviance and BIC column
remove_cols(deviance) %>%
remove_cols(BIC) %>%
# bottom border
set_bottom_border(row=nrow(.), value=0.5)
####### MERGE THE THREE HUXTABLES
# to be able to merge the tables, they need to have the same number of columns.
# the AOV table has 7 columns, so we add 1 empty columns to the fixed effects
# table and 4 empty columns to the random effects table. We merge these with
# existing columns immediately
<-ran_hux %>% mutate(a=NA, b=NA, c=NA, d=NA, .after = "term") %>%
ran_hux merge_across(row = 1:nrow(.), col = 2:6)
<-fix_hux %>% mutate(a=NA, .after = "term") %>%
fix_hux merge_across(row = 1:nrow(.), col = 1:2)
# merge
<- huxtable::add_rows(fix_hux, ran_hux) %>%
lmm_hux ::add_rows(., aov_hux) %>%
huxtable
# set the header and bottom border
set_caption(caption) %>%
set_caption_pos("topleft") %>%
set_bottom_border(row=nrow(.), value=0.5) %>%
add_footnote(sprintf("model: %s; \nSE: standard error, CI: confidence interval, SD: standard deviation, npar: number of parameters, LL: log likelihood, df: degrees of freedom, corr.: correlation",
border = 0.5)
lme_form),
return(lmm_hux)
}
The resulting huxtables nicely summarize the mixed models in the HTML documentation. To collect the tables in a word file that accompanies the manuscript, we convert them to flextables. These can be written to Word documents using the officer package. The function below does the conversion plus some touching up to end up with nicely formatted tables in Word.
<-function(ht = lmm_hux){
convert_huxtable_to_flextable
# define border style to apply to selected cells
=officer::fp_cell(border.right = fp_border(), border.top = fp_border())
def_cell_l=officer::fp_cell(border.bottom = fp_border(), border.top = fp_border())
def_cell_t
# define text style to apply to selected cells
=officer::fp_par(text.align = "center", padding=3)
def_par=officer::fp_text(bold=TRUE)
def_tex
# find the header rows (where new sections of the table begin)
<- match(c("term", "group", "model"), ht$term)
head_rows
# find the cell where we want to add Chi2
<- which(ht=="X2", arr.ind = TRUE)
x2_cell
# create the flextable
<- ht %>% huxtable::as_flextable() %>%
ft
# add border at the bottom to the rows that have the names
::style(i=head_rows, pr_c = def_cell_t, pr_p = def_par, pr_t = def_tex) %>%
flextable::style(i=head_rows-1, pr_c = def_cell_t, pr_p = def_par, pr_t = def_tex) %>%
flextable::bg(i=head_rows-1, bg="lightgrey", part="all") %>%
flextable
# left-align the first and second column (apart from no. of params in model comparison) and add consistent padding
::align(., j=c(1,2), align="left") %>%
flextable::align(., i = c(head_rows[3], head_rows[3]+1, head_rows[3]+2), j=2, align="center") %>%
flextable::padding(., padding = 3, part="all") %>%
flextable
# set font style
::fontsize(size=10, part="all") %>%
flextable::font(fontname = font2use) %>%
flextable
# replace X2 with greek letter chi and superscript 2
::compose(i=x2_cell[1], j=x2_cell[2], value = as_paragraph("\U03C7", as_sup("2"))) %>%
flextable
# autofit to page
::set_table_properties(layout="autofit", width=1)
flextable
# set caption style
<- flextable::set_caption(ft,caption = ft$caption$value, style="Normal")
ft
return(ft)
}
Here we open the word file that we want to write the tables to. It is based on a .docx-file where the themes for headings and text was manually modified to match the style of the manuscript.
<- officer::read_docx(here("virtem_code", "modified_headings.docx"))
stables_docx
<- stables_docx %>%
stables_docx ::body_add_par("Supplemental Tables", style = "heading 1", pos = "on") officer
Brain Plots in ggplot
These functions are based on the ggBrain package. Plus, there are two simple custom functions to transform between 1mm MNI and matrix coordinates.
ggBrain functions to get template background
The first function returns a ggplot object of the template brain (MNI 1mm in our case).
# function from ggBrain that returns a ggplot object of the template brain
<-function(col_template,row_ind,col_ind, ...){
getggTemplate<-getBrainFrame(row_ind=row_ind, col_ind=col_ind, ...)
templateFrame
<-length(col_template)
nif(n>1) col_cut<-as.numeric(cut(templateFrame$value,n))
if(n==1) col_cut=1
<-ggplot()+facet_just_unique(row_ind,col_ind)
p
for(i in 1:n){
if(all(col_cut!=i)) next
<-which(names(templateFrame)=='value')
drop_ind#so it doesn't conflict with mappings to "value" later on
<-templateFrame[col_cut==i,-drop_ind]
templateFrame_col<- p + geom_tile(data=templateFrame_col,aes(x=row,y=col),fill=col_template[i])
p
}
p
}
# another internal function from ggBrain used to plot the template
<-function(row_ind,col_ind){
facet_just_uniqueif( all(row_ind==row_ind[1]) & all(col_ind==col_ind[1]))
<-NULL
outif( all(row_ind==row_ind[1]) & !all(col_ind==col_ind[1]))
<-facet_grid(.~col_ind)
outif(!all(row_ind==row_ind[1]) & all(col_ind==col_ind[1]))
<-facet_grid(row_ind~.)
outif(!all(row_ind==row_ind[1]) & !all(col_ind==col_ind[1]))
<-facet_grid(row_ind~col_ind)
outreturn(out)
}
Coordinate transforms
The code section below defines two helper functions that transform between the MNI coordinate system and the matrix coordinates of R. CAVE: Only use with 1mm MNI space.
# to get accurate labels of panels in MNI space, define functions to convert coords
<- function(mni_coords, output_zero_based = FALSE){
mni2vox
<- mni_coords[1] * -1 + 90
x<- mni_coords[2] * 1 + 126
y<- mni_coords[3] * 1 + 72
z<- c(x,y,z)
vox_coords
# add +1 if output coordinates should not be zero-based
if(!output_zero_based){vox_coords <- vox_coords+1}
return(vox_coords)
}
# convert from voxel coordinates to MNI with option for whether voxel coordinates are from
# R (i.e. not zero-based) or FSL (i.e. zero-based)
<- function(vox_coords, input_zero_based = FALSE){
vox2mni
# if input coordinates are from R, i.e. not zero-based, subtract -1
if(!input_zero_based){vox_coords <- vox_coords-1}
# subtract voxel space coordinates of origin (MNI=0,0,0 at 90,126,72 in FSL voxel coordinates)
<- -1*(vox_coords[1]-90)
mni_x <- vox_coords[2]-126
mni_y <- vox_coords[3]-72
mni_z
<- c(mni_x,mni_y,mni_z)
mni_coords return(mni_coords)
}
Waiting for Cluster Jobs
The function below is a convenient function to pause the execution of a script until a batch of condor jobs has finished. The function takes the ID of a condor batch as an input. It uses condor_q via the system-command. If 0 is returned, it terminates and moves on. Else the function sleeps for (a default of) 60s and then tries again. No checks are performed whether the jobs finished without errors. This function merely pauses the execution of the code to wait for long jobs to finish.
<- function(batch_id, wait_interval = 60){
pause_until_batch_done
print(sprintf("Monitoring batch job %s. Checking status every %d seconds.",
batch_id, wait_interval))
<- TRUE
batch_running ::tic()
tictocwhile (batch_running){
# sleep for the specified number of seconds
Sys.sleep(wait_interval)
# check if batch finished (condor_q returns character(0))
<- system(sprintf("condor_q -l %s", batch_id), intern=TRUE)
status
if(identical(status, character(0))){
print("Batch jobs finished. Moving on.")
::toc()
tictoc<- FALSE
batch_running else{
} print(sprintf("Batch jobs still running. Waiting another %d seconds.",
wait_interval))
}
} }