SPM5 Gem 1: Introduction to SPM5 scripting

Date: Tue, 13 Dec 2005 17:03:13 +0100
From: John Ashburner <john@FIL.ION.UCL.AC.UK>
To: SPM@JISCMAIL.AC.UK
Subject: Re: [SPM] Where can we find some development materials for SPM ?

>    As you know,we usually need to modify the code of SPM to fit our
> problem.but we can not find some relevant development  tutorials. Would you
> please tell me how to learn the framework of SPM step by step ?
>   In addition, I want to know where I can find the details of the SPM
> structure.

It may be easiest to learn by example.  If you want to develop a new 
user-interface for SPM5, then you would create a file called spm_config_*.m, 
similar to the other spm_config.m files (if you strip out the documentation 
parts, you will see that these are actually quite small).  Your spm_config* 
file can then be added to the toolbox subdirectory and accessed through the 
TOOLS pulldown.

The help button allows you to navigate through the documentation of each of 
the Matlab functions, which you may find useful.

For reading and writing images, you would use these functions.
    spm_vol
    spm_slice_vol
    spm_sample_vol

    spm_create_vol
    spm_write_plane
    spm_write_vol

    spm_get_space

There is Matlab help on all these functions.  Alternatively, you could use the 
NIFTI routines directly.  There is no documentation on this, but here are a 
few examples of how you can use them:
======================================

% Example of creating a simulated .nii file.
dat            = file_array;
dat.fname = 'junk.nii';
dat.dim     = [64 64 32];
dat.dtype  = 'FLOAT64-BE';
dat.offset  = ceil(348/8)*8;

% alternatively:
% dat = file_array( 'junk.nii',dim,dtype,off,scale,inter)

disp(dat)

% Create an empty NIFTI structure
N = nifti;

fieldnames(N) % Dump fieldnames

% Creating all the NIFTI header stuff
N.dat = dat;
N.mat = [2 0 0 -110 ; 0 2 0 -110; 0 0 -2 92; 0 0 0 1];
N.mat_intent = 'xxx'
N.mat_intent = 'Scanner';
N.mat0 = N.mat;
N.mat0_intent = 'Aligned';

N.diminfo.slice = 3;
N.diminfo.phase = 2;
N.diminfo.frequency = 2;
N.diminfo.slice_time.code='xxx';
N.diminfo.slice_time.code = 'sequential_increasing';
N.diminfo.slice_time.start = 1;
N.diminfo.slice_time.end = 32;
N.diminfo.slice_time.duration = 3/32;

N.intent.code='xxx' ; % dump possibilities
N.intent.code='FTEST'; % or N.intent.code=4;
N.intent.param = [4 8];

N.timing.toffset = 28800;
N.timing.tspace=3;
N.descrip = 'This is a NIFTI-1 file';
N.aux_file='aux-file-name.txt';
N.cal = [0 1];

create(N); % Writes hdr info

dat(:,:,:)=0;

[i,j,k] = ndgrid(1:64,1:64,1:32);
dat(find((i-32).^2+(j-32).^2+(k*2-32).^2 < 30^2))=1;
dat(find((i-32).^2+(j-32).^2+(k*2-32).^2 < 15^2))=2;


% displaying a slice
imagesc(dat(:,:,12));colorbar

% get a handle to 'junk.nii';
M=nifti('junk.nii');

imagesc(M.dat(:,:,12));
======================================

Best regards,
-John

SPM2 Gem 13: Corrected cluster size threshold

This is a script that will tell you the corrected cluster size threshold for given cluster-defining threshold: corrclusth.m

The usage is pretty self explanatory:

 Find the corrected cluster size threshold for a given alpha
 function [k,Pc] =CorrClusTh(SPM,u,alpha,guess)
 SPM   - SPM data structure
 u     - Cluster defining threshold
         If less than zero, u is taken to be uncorrected P-value
 alpha - FWE-corrected level (defaults to 0.05)
 guess - Set to NaN to use a Newton-Rhapson search (default)
         Or provide a explicit list (e.g. 1:1000) of cluster sizes to
         search over.
         If guess is a (non-NaN) scalar nothing happens, except the the
         corrected P-value of guess is printed. 

 Finds the corrected cluster size (spatial extent) threshold for a given
 cluster defining threshold u and FWE-corrected level alpha. 
  

To find the 0.05 (default alpha) corrected cluster size threshold for a 0.01 cluster-defining threshold:

>> load SPM
>> CorrClusTh(SPM,0.01)
  For a cluster-defining threshold of 2.4671 the level 0.05 corrected
  cluster size threshold is 7860 and has size (corrected P-value) 0.0499926
  

Notice that, due to the discreteness of cluster sizes you cannot get an exact 0.05 threshold.

The function uses an automated search which may sometimes fail. If you specify a 4th argument you can manually specify the cluster sizes to search over:

>> CorrClusTh(SPM,0.01,0.05,6000:7000)

  WARNING: Within the range of cluster sizes searched (6000...7000)
  a corrected P-value <= alpha was not found (smallest P: 0.0819589)

  Try increasing the range or an automatic search.

Ooops… bad range.

Lastly, you can use it as a look up for a specific cluster size threshold. For example, how much over the 0.05 level would a cluster size of 7859 be?

>> CorrClusTh(SPM,0.01,0.05,7859)
  For a cluster-defining threshold of 2.4671 a cluster size threshold of
  7859 has corrected P-value 0.050021

Just a pinch!

SPM2 Gem 12: fMRI Analysis threshold

This is an update of Gem12 for SPM99, originally by Stefan Kiebel.

> Is it possible to instruct spm99 to search all voxels within a given
> mask image rather than all above a fixed or a %mean threshold? 

Yes, with SPM2 it's possible to use several masking options. 

To recap, there are 3 sorts of masks used in SPM2:
   1. an analysis threshold
   2. implicit masking
   3. explicit masking

1: One can set this threshold for each image to -Inf to switch off this
   threshold.
2: If the image allows this, NaN at a voxel position masks this voxel
   from the statistics, otherwise the mask value is zero (and the user
   can choose, whether implicit masking should be used at all).
3: Use mask image file(s), where NaN (when image format allows this) or
   a non-positive value masks a voxel.

On top of this, SPM automatically removes any voxels with constant
values over time.

So what you want is an analysis, where one only applies an explicit
mask. 

In SPM2 for PET, you can do this by going for the Full Monty and
choosing -Inf for the implicit mask and no 0-thresholding. Specify one
or more mask images. (You could also define a new model structure,
controlling the way SPM for PET asks questions).

With fMRI data/models, SPM2 is fully capable of doing explicit
masking, but the user interface for fMRI doesn't ask for it. One way
to do this type of masking anyway is to change the SPM.mat file
*after* you specify your model, but *before* clicking 'Estimate'.
Specifically:

   1. Load the SPM.mat file,
             load SPM
      set the SPM.xM.TH values all to -Inf,
             SPM.xM.TH = -Inf*SPM.xM.TH;
      and, in case that you have an image format not allowing NaNs,
      set SPM.xM.I to 0
             SPM.xM.I = 0;

   2. If using a mask image, set SPM.xM.VM to a vector of structures,
      where each structure element is the output of spm_vol. For instance:
               SPM.xM.VM = spm_vol('Maskimage');

   3. Finally, save by
               save SPM SPM

SPM2 Gem 10: Creating a mask from a XYZ

This is a “Jan’s Gem” from Jan Gläscher…

Subject: Re: [SPM] creating a mask from voxel coordinates
From: Jan Gläscher <glaescher@UKE.UNI-HAMBURG.DE>
Date: Tue, 15 Mar 2005 12:24:27 +0100
To: SPM@JISCMAIL.AC.UK


Dear Susana,

you can try the following recipe.  It might not be the easiest way, but it
should work.

Suppose your n MNI coordinates are saved in a 3xn matrix called mni.
(This is essential (not the name, but the format of 3xn), otherwise the
steps below won't work).

1. Create an spm_vol handle to the image, that you determined the
   coordinates from

   Vin = spm_vol(spm_get(1,'*.img','Select image'));

2. Read the information in the image. (The data are not needed, this is
   done purely for the purpose of setting up a matrix of voxel coordinates.

   [Y,XYZ] = spm_read_vols(Vin);

3. Setup a matrix of zeros in the dimensions of the input image

   mask = zeros(Vin.dim(1:3));

4. Now loop over all voxels in your mni variable and set the corresponding
   location in the mask matrix to 1:

   for v = 1:size(mni,2)
      mask(find(XYZ(1,:)==mni(1,v)&XYZ(2,:)==mni(2,v)&XYZ(3,:)==mni(3,v))) = 1;
   end

5. Setup an spm_vol output file handle and change the filename

   Vout = Vin;
   Vout.fname = '/path/to/the/directory/mask.img';

6. Finally, write the new mask to the output file.

   spm_write_vol(Vout,mask);


Cheers,
Jan

SPM2 Gem 9: Masking w/ ImCalc

This is an email from Rik Henson which references John. It addresses the fact that ImCalc defaults to writing out images in 2-byte signed integer format, which is can be problematic if you’re masking statistic images (which should written with floating point precision).

Subject: Masking with ImCalc
From: Rik Henson 
Date: Fri, 14 May 2004 08:24:25 +0100 (03:24 EDT)
To: SPM@JISCMAIL.AC.UK

Several people have asked how to mask SPMs across different
analyses. This used to be possible using ImCalc in SPM99 (see Mailbase
archive). In SPM2 however, when ImCalc is called from the GUI, it
adopts various defaults, which include writing images as "uint16"
(rather than "float", which is the normal datatype for T/F imgs).
Thanks to John for pointing this out.

It is for this reason that the procedure of overwriting
spmT/F*imgs using ImCalc does not work from SPM2's GUI.

To override this default however, you can simply call
ImCalc from the command line instead, setting the
datatype to spm_type 'float':

    spm_imcalc_ui([],[],'i1.*(i2>3.09)',{[],[],'float',0})

The empty 1st and 2nd arguments mean that SPM will prompt
you to select the input images and the output filename, as usual.
You will of course need to change the third argument to your
particular ImCalc equation for evaluation. The 4th argument
is a cell array of defaults, which includes the 'float' enforcement.

For more info, type "help spm_imcalc_ui".

Rik

See also SPM2 Gem 8, and SPM99 Gems 3, 16, and 21.

SPM2 Gem 8: ImCalc Script

SPM99 Gem 21 is the same (with minor changes).

Subject: Re: a script to use ImaCal
From: John Ashburner 
Date: Thu, 16 Oct 2003 11:24:17 +0000 (07:24 EDT)
To: SPM@JISCMAIL.AC.UK

> Brain image for each subject to mask out CSF signal was generated by
> using MPR_seg1.img (i1) and MPR_seg2.img (i2) with (i1+i2)>0.5 in
> ImaCal.(called brainmpr.img for each subject)
>
> Then I have more than two-hundred maps, which need to mask out
> CSF. I think I can use ImaCal again with selecting brainmpr.img (i1)
> and FAmap.img (i1), and then calculating (i1.*i2) to generate a new
> image named as bFAmap.img.  Unfortunately, if I use the ImaCal, it
> take so long time to finish all subjects. Could anyone have a script
> to generate  a multiplication imaging with choosing a brain
> image(brainmpr.img, i1) and a map image (FAmap.img, i2) and writing
> an output image (bFAmap.img) from i1.*i2? 

You can do this with a script in Matlab.  Something along the lines of the
following should do it:

P1=spm_get(Inf,'*.IMAGE','Select i1');
P2=spm_get(size(P1,1),'*.IMAGE','Select i2');

for i=1:size(P1,1),
    P = strvcat(P1(i,:),P2(i,:)));
    Q = ['brainmpr_' num2str(i) '.img'];
    f = '(i1+i2)>0.5';
    flags = {[],[],[],[]};
    Q = spm_imcalc_ui(P,Q,f,flags);
end;

Note that I have not tested the above script.  I'm sure you can fix it if
it doesn't work.

Best regards,
-John