GLM analysis for fMRI

We can use mripy.preprocess.glm() to analyze our fMRI data using a General Linear Model.

import numpy as np
import matplotlib.pyplot as plt
from mripy import io, preprocess as prep

data_dir = 'path/to/my/experiment/data'
subject = 'sub-01'
res_dir = f"{data_dir}/{subject}/results"
stim_dir = f"{data_dir}/{subject}/stimuli"
runs = [f"{k:02d}" for k in [1, 2, 3, 4]] # ['01', '02', '03', '04']
TR = 2 # s

Block design

Fixed duration blocks

This is the most basic design, especially for functional localizers.

In the following example, we have Face and House blocks, and each block lasts 16 s. The model can be specified as BLOCK(16).

# Perform GLM using "BLOCK" model
design = OrderedDict()
design['Face'] = f"{stim_dir}/Face_block_onset.txt 'BLOCK(16)'"
design['House'] = f"{stim_dir}/House_block_onset.txt 'BLOCK(16)'"
contrasts = OrderedDict()
contrasts['F-H'] = '+Face -House'
prep.glm(in_files=[f"{res_dir}/epi{run}.scale.nii" for run in runs],
    out_file=f"{res_dir}/localizer.nii", design=design, contrasts=contrasts, TR=TR,
    motion_files=[f"{res_dir}/epi{run}.volreg.param.1D" for run in runs])

# The above method will generate the following files:
# - stats.localizer_REML.nii   # This is the usual stats file with F, beta, t values for each regressor

Variable duration blocks

This is useful for modelling spontaneous change in states, e.g., binocular rivalry.

In this case, we can use the “dmUBLOCK” model in AFNI. “dm” stands for “duration modulated”.

# Prepare event timing files with both block onset and block duration
# t['L'] and d['L'] contain the onset and duration of "Left eye dominant" event for all runs
t = {'L': [
            [1.2, 7.5, 15.7, 20.3, ...], # run01 "Left eye dominant" event onset
            [3.9, 9.4, 19.2, 25.5, ...], # run02 "Left eye dominant" event onset
            ...,
        ],
    'R': [...] # "Right eye dominant" event onset
}
d = {'L': [
            [4.3, 5.1, 2.6, 7.8, ...], # run01 "Left eye dominant" event duration
            [2.9, 6.2, 2.2, 5.3, ...], # run02 "Left eye dominant" event duration
            ...,
        ],
    'R': [...] # "Right eye dominant" event duration
}
for event in ['L', 'R']: # Left eye dominant vs Right eye dominant
    with open(f"{stim_dir}/{event}_rivalry.txt", 'w') as fout:
        # `tt` and `dd` contain the onset and duration for all events in a run
        # `ttt` and `ddd` are the onset and duration for each event (i.e., button press)
        for tt, dd in zip(t[event], d[event]):
            fout.write(' '.join([f"{ttt:8.3f}:{ddd:<8.3f}" for ttt, ddd in zip(tt, dd)]) + '\n')

# Perform GLM using "dmUBLOCK" model
design = OrderedDict()
design['L'] = f"{stim_dir}/L_rivalry.txt 'dmUBLOCK'"
design['R'] = f"{stim_dir}/R_rivalry.txt 'dmUBLOCK'"
contrasts = OrderedDict()
contrasts['L+R'] = '+0.5*L +0.5*R'
contrasts['L-R'] = '+L -R'
prep.glm(in_files=[f"{res_dir}/epi{run}.scale.nii" for run in runs],
    out_file=f"{res_dir}/rivalry.nii", design=design, contrasts=contrasts, TR=TR,
    motion_files=[f"{res_dir}/epi{run}.volreg.param.1D" for run in runs])

# The above method will generate the following files:
# - stats.rivalry_REML.nii   # This is the usual stats file with F, beta, t values for each regressor

Need more flexibility in doing GLM?

Calling 3dDeconvolve directly allows you to access more types of models, and control the behavior of the estimation process in more details.

If you need more help or details about the underlying algorithm, the ultimate source of reference is the AFNI documentation about its 3dDeconvolve command.