Hacker’s Guide to SPM12
MR image file handling
In SPM, MR image files are handled in the form of the volume struct. If associated with a NIfTI image file, it has a field private that contains a nifti object, which in turn has a field dat that contains a file_array object.1
Volume struct
A volume struct represents a 3D MR image. Essential fields are:
fname |
filename of the image, from D.fname |
dim |
size of the volume (voxels × voxels × voxels), derived from D.dim |
mat |
4 × 4 affine transformation matrix mapping from voxel indices to real-world coordinates, from N.mat |
dt |
data format |
(1) |
data type (see spm_type), from property D.dtype |
(2) |
endianess (0: little, 1: big), from property D.be |
pinfo |
linear transformation from raw values, and file offset; if two-dimensional, values are plane-specific |
(1, :) |
slope, from D.scl_slope |
(2, :) |
intercept, from D.scl_inter |
(3, :) |
offset into file to (volume/plane) data start (bytes), derived from D.offset |
These further fields are normally present:
n |
volume indices |
(1) |
sequence index (for 4D files, else 1) |
(2) |
vector element index (for 5D files, else 1) |
descrip |
description of the image, from N.descrip |
private |
underlying nifti object (= N) |
mat (and mat0 below) is an affine transformation matrix, i.e. it has the augmented form
[ A b
0 0 0 1 ]where A is a 3 × 3 linear transformation matrix and b is a translation. Voxel indices (starting from 1) are transformed into real-world coordinates (in mm) by
[x y z 1] .' ← mat * [i j k 1] .'A NIfTI image can contain up to 7-dimensional data. The first three dimensions are interpreted as spatial (spanning a volume), the 4th as temporal or sequential, the 5th to 7th as indexing vector, matrix, or tensor elements.
SPM supports referencing volumes from a multi-volume file in up to two dimensions using the notation filename,#,#, where the # stands for indices referring to the 4th and 5th dimension. filename,# is equivalent to filename,#,1. filename is equivalent to a sequence of volume references, filename,1, filename,2, …, filename,n, where n is the length of the 4th dimension. For a single-volume file, filename is therefore equivalent to filename,1.
nifti object
A nifti object represents a NIfTI image, either in dual- (img+hdr) or single-file (nii) format. Important fields are:
hdr |
struct containing fields of the NIfTI header (hidden) |
dat |
image data represented by a file_array object (= D), initialized from dim, vox_offset, scl_slope, scl_inter, and detected endianess. |
raw |
raw image data (not linearly transformed), i.e. a copy of dat with empty D.scl_slope and D.scl_inter (hidden, read-only) |
mat0 |
4 × 4 affine transformation matrix derived from qform |
mat |
4 × 4 affine transformation matrix derived from sform if present, otherwise falls back on qform (= mat0) |
descrip |
description of the image |
This description references the NIfTI header fields stored in hdr.
Further fields that are often unset or unreliable:
extras |
additional data read from companion Matlab mat-file |
mat0_intent |
intention of mat0 ('UNKNOWN' | 'Scanner' | 'Aligned' | 'Talairach' | 'MNI152') |
mat_intent |
intention of mat |
intent |
interpretation of image data |
diminfo |
MR encoding of different dimensions |
timing |
timing information |
cal |
display calibration |
aux_file |
name of an auxiliary file |
The nifti object contained in a volume struct represents the whole NIfTI image, not just a specific volume, and its fields dat and raw the whole (up to 7D) image data.
file_array object
A file_array object D provides memory-mapped access to (a part of) a data file. It can be syntactically treated like a regular multidimensional array. Reference to array elements, D(i, j, k), is transparently translated into file read operations, and assignment to array elements, D(i, j, k) = y, translated into file write operations. Its fields are:
fname |
name of the data file |
dim |
dimensions |
dtype |
data type including endianness (string), based on properties dtype and be |
offset |
offset into file to data start (bytes) |
scl_slope |
slope |
scl_inter |
intercept |
The byte sequence in the file, starting from offset, is interpreted as a sequence of prod(dim) numbers in the format given by dtype. These raw numbers are linearly transformed by multiplying with scl_slope and adding scl_inter. The resulting sequence of numbers is treated like the linearly indexed contents of the array D. If both scl_slope and scl_inter are empty, the linear transform is omitted, i.e. D provides access to the raw numbers.
Supported data types and corresponding Matlab types are
'INT8' |
int8 |
'UINT8' |
uint8 |
'INT16' |
int16 |
'UINT16' |
uint16 |
'INT32' |
int32 |
'UINT32' |
uint32 |
'FLOAT32' |
single |
'FLOAT64' |
double |
The dtype field consists of the data type string, plus the suffix '-LE' or '-BE' to indicate endianness.
SPM functions for volume structs, nifti, and file_array objects
V = spm_vol(P)-
creates an array of volume structs
Vfrom image filename(s)Pand reads the header. Each filename may be appended with a volume reference of the form ‘,#’ or ‘,#,#’, see above.private.datcan now be read from (and written to). Y = spm_read_vols(V)-
reads image data
Yfrom an array of volume structsV. Internally it callsspm_slice_vol, which uses the volumepinfofor linear transformation (and ignoresD.scl_slopeandD.scl_inter). V = spm_create_vol(V)-
writes the image header (beginning of
niior separatehdrfile) according to the fields of the volume structV. Fieldsfname,dim, andmatneed to be set. If fieldsdt,pinfo,n,descrip, andprivateare not present, they are added with default values.pinfo(3)is patched with the data offset into the file (usually 352 forniiand 0 forimg). Multiple volumes (in separate files, or a multi-volume NIfTI image ifnis set correctly) can be created at once ifVis an array (but this fails ifdtandpinfoare not set in all volumes).private.datcan now be written to (and read from as far as it has been written), which creates theimgfile if applicable. V = spm_write_vol(V, Y)-
writes 3D data
Yto an image represented by the single volume structV.pinfo(1)andpinfo(2)are set heuristically ifpinfois not present or either one isInf,spm_create_volis called, and writing is done viaspm_write_plane(which usesprivate.dat). spm_check_orientations(V)-
determines whether all volumes in the struct array
Vhave the same values ofdimandmat; if not, an error is thrown. Alternatively, the result of the check can be queried by giving an output argument (falsein case of mismatch).
Within SPM12, these functions are mainly used through the wrappers spm_data_hdr_read, spm_data_read, spm_data_hdr_write, and spm_data_write. Note that spm_slice_vol reads data via resampling based on an affine transform, which is unnecessary and makes spm_read_vols slower than direct data access via private.dat.
N = nifti(P)-
creates an array of
niftiobjectsNfrom image filenamesPand reads the header.N.datcan now be read from (and written to). N = nifti-
creates a
niftiobject that is minimally initialized and not associated with an image file. After setting at least the fielddatto afile_arrayobject (thereby opening or creating a file),createcan be used to write the header information. create(N)-
writes NIfTI header information if
N.datreferences an image file, either to the beginning of the file (nii) or to a separate header file (img→hdr).
Reading and writing of NIfTI files (representing up to 7D arrays) can be completely achieved using nifti objects. The purpose of SPM’s volume struct arrays is to reference sequences of single volumes (3D arrays) within and across image files.
D = file_array(fname, dim, dtype, offset, scl_slope, scl_inter)-
creates a
file_arrayobject referencing an existing or not existing file. If it does not exist, writing toDcreates the file as far as necessary.Dcan be read from as far as the file is written (within the array index bounds). If thefile_arrayobject is added to aniftiobject, theoffsetis automatically set correctly.
SPM’s file_array objects are tailored for accessing image data in NIfTI files, but they can just as well be used for memory-mapped access to arbitary data files, interpreting their contents as linearly indexed elements of a multidimensional array.
First-level analysis
Module ‘fMRI model specification’
call hierarchy:
spm_run_fmri_spec: Setting up the general linear model for fMRI time-seriesspm_fmri_spm_ui: Setting up the general linear model for fMRI time-seriesspm_fMRI_design: Assembles a design for fMRI studies1st save of
SPM.matmodification of
xY.VY(:).pinfo for global scaling2nd save of
SPM.mat3rd save of
SPM.mat (only adds explicit mask)Fields of SPM.mat
xY |
data |
P |
volume names (filename,#) across sessions; two-dimensional char array |
VY(:) |
volume struct for each scan |
RT |
repeat time |
nscan(:) |
number of scans for each session |
xBF |
basis functions |
T |
microtime resolution (time points per scan) |
T0 |
microtime onset (reference for onset times) |
UNITS |
units in which onsets are specified ('scans'|'secs') |
Volterra |
order of convolution, 1: standard, 2: plus interactions |
dt |
time bin length (seconds) |
name |
description of basis functions |
length |
window length (seconds) |
order |
order of basis set |
bf |
set of basis functions (orthogonal but not normalized) |
Sess(:) |
sessions |
U(:) |
conditions |
dt |
same as xBF.dt |
ons |
onsets (in xBF.UNITS) |
dur |
durations (in xBF.UNITS) |
P(:) |
parametric modulations |
name |
name of modulator ('time'|...) |
h |
order of polynomial expansion (number of powers) |
i |
related causes (column indices into Sess.U.u) |
u |
set of causes (condition and modulated), as stick or boxcar functions of microtime |
name |
names of causes |
orth |
are modulated regressors orthogonalized? |
pst |
peristimulus times (seconds) |
C |
user-specified regressors |
C |
set of regressors |
name |
names of regressors |
row |
scans of session (rows of xX.X) |
col |
regressors for session (columns of xX.X) |
Fc(:) |
F-contrasts across all regressors for each condition or interaction (see xBF.Volterra) |
name |
name of the contrast |
i |
involved regressors (indices into Sess.col) |
p |
for each regressor, involved cause (column indices into Sess.U.u) |
xX |
design |
X |
design matrix (set of regressors; raw) |
name{:} |
names of regressors |
iH |
indices of indicator variables (unused?) |
iC |
indices of covariates (conditions / modulated / interactions & user-specified) |
iB |
indices of block effects (constant regressors for each session) |
iG |
indices of nuisance variables (unused?) |
K(:) |
high-pass filter for each session |
HParam |
cut-off period (seconds) |
row |
same as corresponding Sess.row |
RT |
same as xY.RT |
X0 |
set of low-frequency components to be removed (orthonormal cosines) |
xGX |
global variate |
iGXcalc |
global scaling option, 'none': session specific, 'scaling': scan specific |
sGXcalc |
calculation method, always 'mean voxel value' |
rg |
estimated global variate for each scan |
sGMsca |
global scaling method, erroneously(?) always 'session specific' |
GM |
target value for global scaling, always 100 |
gSF |
global scaling factor for each scan, constant within session if session specific |
xM |
masking |
gMT |
masking threshold parameter, fraction of global variate |
TH |
masking threshold for each scan, scaled version of gMT |
VM(:) |
volume struct for each explicit mask image |
xs.Masking |
description, either 'analysis threshold' or 'analysis threshold+explicit mask' |
T |
masking index (?) for each scan, always 1s |
I |
use implicit mask? |
xVi |
non-sphericity |
form |
form of non-sphericity ('i.i.d.' | 'FAST' | 'AR(0.2)' | 'AR(#)' user-specified parameter) |
Vi{:} |
covariance components (correlation matrices), for each session one (i.i.d), two (AR), or many |
xsDes |
design description |
Interscan_interval |
from xY.RT |
Number_of_sessions |
from length of nscan |
Basis_functions |
same as xBF.name |
Trials_per_session |
from lengths of Sess(:).U |
High_pass_Filter |
from minimum of xX.K(:).HParam |
Global_normalisation |
same as xGX.iGXcalc |
Global_calculation |
same as xGX.sGXcalc |
Grand_mean_scaling |
same as xGX.sGMsca |
factor(:) |
design factors, slowest first |
name |
name of factor |
levels |
number of levels |
SPMid |
SPM version and last function to write SPM.mat |
Module ‘Model estimation’, method ‘Classical’
depends on ‘fMRI model specification’
call hierarchy:
spm_run_fmri_est: Estimate parameters of a specified modelspm_spm: [Re]ML Estimation of a General Linear Modelspm_est_non_sphericity: Non-sphericity estimation using ReMLwrite
beta_*, mask, and ResMS imagesalso
ResI_* images for smoothness estimationspm_est_smoothness: Estimation of smoothness based on [residual] imageswrite
RPV imagedelete
ResI_* images1st save of
SPM.matspm_conman: Contrast manager: GUI for defining valid contrastsfor each contrast
spm_contrasts: Compute and store contrast parameters and inference SPM{.}write
ess & spmF or con & spmT image2nd and following save of
SPM.matwrite
Res_* imagesFields of SPM.mat
xVi |
non-sphericity |
h |
hyperparameters (weights of covariance components xVi.Vi{:}) |
V |
estimated serial non-sphericity (linear combination of covariance components, scaled to mean(diag(V)) = 1) |
Cy |
spatial covariance between scans |
UFp |
from defaults.stats.fmri.ufp |
xX |
design |
W |
whitening matrix (approx. sqrtm(inv(xVi.V))) |
xKXs |
space structure (see spm_sp) of K W X |
X |
filtered and whitened design matrix |
ds |
its singular values |
u |
its left singular vectors |
v |
its right singular vectors |
tol |
numerical tolerance (depends on max(abs(ds))) |
rk |
its rank (based on tol) |
oP |
orthogonal projector on X |
oPp |
orthogonal projector on X' |
ups |
space in which this one is embeded |
sus |
subspace |
pKX |
pseudoinverse of xX.xKXs.X |
V |
filtered and whitened nonsphericity K W xVi.V W' K' |
trRV |
trace(R V) after filtering and whitening, based on R from xX.xKXs.X and xX.V |
trRVRV |
trace(R V R V) after filtering and whitening |
erdf |
effective residual degrees of freedom after filtering and whitening, trace(R V) ^ 2 / trace(R V R V) |
Bcov |
nonsphericity of parameter estimates, gives estimation covariance if multiplied with voxel-specific ResMS |
nKX |
xX.xKXs.X scaled for display |
Vbeta(:) |
volume struct of parameter estimates image 'beta_*', for each regressor; data generated by xX.pKX * spm_filter(xX.K, W * Y) |
VResMS |
volume struct of residual mean of squares image 'ResMS' (unbiased estimator of error variance)2 |
VM |
volume struct of analysis mask image, i.e. combination of explicit mask(s) xM.VM, threshold mask xM.TH, and implicit (zero/NaN) mask |
xVol |
search volume |
XYZ |
coordinates of in-mask voxels (in mm) |
S |
number of in-mask voxels |
M |
from xY.VY(1).mat |
iM |
inv(M) |
DIM |
from xY.VY(1).dim |
VRpv |
volume struct of resels per voxel image 'RPV' |
FWHM |
full width at half maximum of average Gaussian point spread function, three-dimensional; based on resels per voxel |
R |
“resel counts”, see spm_resels_vol; based on mask and FWHM |
xCon(:) |
contrast structure, for each contrast; based on factor |
name |
name of contrast |
STAT |
'F' or 'T' |
c |
contrast column vector or matrix |
X0 |
reduced design matrix |
ukX0 |
in the basis of xX.X |
iX0 |
how contrast was specified |
X1o |
remaining design space |
ukX1o |
in the basis of xX.X |
eidf |
effective interest (numerator) degrees of freedom |
Vcon |
volume struct of 'con' or 'ess' image |
Vspm |
volume struct of 'spmT' or 'spmF' image |
SPMid |
SPM version and last function to write SPM.mat |
swd |
directory of SPM.mat |
Module ‘Model estimation’, method ‘Bayesian 1st-level’
information in this section is preliminary!
depends on ‘fMRI model specification’
Fields of SPM.mat
PPM |
parameters and some results of Bayesian estimation |
space_type |
|
block_type |
|
priors |
|
AR_P |
order of autoregressive model of nonsphericity |
VB |
|
window |
|
Sess(:) |
for each session |
VHp |
volume struct of standard deviation error image |
VAR(:) |
volume struct of AR parameter image, for each parameter |
block |
|
update_F |
|
AN_slices |
|
maxits |
|
tol |
|
compute_det_D |
|
Gamma |
|
xCon |
? contrast structure |
VCbeta(:) |
volume struct of posterior mean of beta image, for each regressor |
VM |
volume struct of analysis mask image |
VPsd |
volume struct of posterior standard deviation of beta image, for each regressor |
xCon |
? contrast structure (again) |
xVol |
search volume |
XYZ |
coordinates of in-mask voxels (in mm) |
S |
number of in-mask voxels |
M |
from xY.VY(1).mat |
iM |
inv(M) |
DIM |
from xY.VY(1).dim |
VRpv |
? set to [] |
FWHM |
? “full width at half maximum”, but scalar |
R |
? “resel counts”, but scalar |
labels |
? |
xX |
design |
W |
“whitening matrix”, but set to the identity matrix |
erdf |
“effective residual degrees of freedom”, but set to the number of scans |
xKXs |
? space structure (see spm_sp) of K W X, but different from ‘Classical’, possibly no whitening |
nKX |
xX.xKXs.X scaled for display |
swd |
directory of SPM.mat |
Other resources
Nikki Sullivan’s SPM data structures
NiBabel Image use-cases in SPM
SPM Wikibook Programming intro
Footnotes
The
niftiandfile_arrayobjects are implemented using Matlab’s pre-2008a object-oriented syntax.]
In the following, we refer to the containedniftiobject asNand the containedfile_arrayobject asD.↩︎Residuals
resare obtained by applying the residual-forming matrix ofxX.xKXstospm_filter(xX.K, W * Y), thenResMS = sum(res .^ 2) / xX.trRV; later regularized byResMS = ResMS + 1e-3 * max(ResMS(isfinite(ResMS)))↩︎