writeanalyze in Matlab

I am always look for different MRI file readers and writers for the myriad of formats that we use in MRI research. One of the relatively simple and common ones is the Analyze fileformat. Some of the large packages have writers (e.g., SPM) but I am typically wanting to do my own small processing and then write out the data. So, I wrote up my own writeanalyze.m function. It will do the basic formatting though the offsets etc don’t work. Try it out but I can’t guarantee anything.

[cc lang="matlab"]
function [] = writeanalyze(fname, data, ftype)

if( nargin == 2 )
ftype = ‘int16′;
end

if( strcmp( ftype, ‘int16′ ) == 1 )
file_type = 4; bpp = 16;
elseif( strcmp( ftype, ‘uint16′ ) == 1 )
file_type = 4; bpp = 16;
elseif( strcmp( ftype, ‘int32′ ) == 1 )
file_type = 8; bpp = 32;
elseif( strcmp( ftype, ‘float’ ) == 1 )
file_type = 16; bpp = 32;
elseif( strcmp( ftype, ‘double’ ) == 1 )
file_type = 64; bpp = 64;
else
error(sprintf(‘Unknown data type %s’, ftype));
end

fp = fopen([fname '.hdr'], ‘wb’, ‘b’);

%%
%% Write the header_key part
%%
fwrite(fp, 348, ‘int32′);
fwrite(fp, repmat(‘ ‘, 1, 10), ‘char’);
fwrite(fp, repmat(‘ ‘, 1, 18), ‘char’);
fwrite(fp, 16384, ‘int32′);
fwrite(fp, 0, ‘int16′);
fwrite(fp, ‘r ‘, ‘char’);

%%
%% Write the image_dimension part.
%%
fwrite(fp, length( size(data) ), ‘int16′);
for ii=1:length( size(data) )
fwrite(fp, size(data,ii), ‘int16′);
end

for ii=length( size(data) )+1:7
fwrite(fp, 1, ‘int16′);
end

fwrite(fp, 0, ‘int16′); % unused 8
fwrite(fp, 0, ‘int16′); % unused 9
fwrite(fp, 0, ‘int16′); % unused 10
fwrite(fp, 0, ‘int16′); % unused 11
fwrite(fp, 0, ‘int16′); % unused 12
fwrite(fp, 0, ‘int16′); % unused 13
fwrite(fp, 0, ‘int16′); % unused 14

% data type
fwrite(fp, file_type, ‘int16′); % 4 = signed short
fwrite(fp, bpp, ‘int16′); % bpp
fwrite(fp, 0, ‘int16′);
for ii=1:8
fwrite(fp, 1.0, ‘float32′);
end
fwrite(fp, 0, ‘float32′);
fwrite(fp, 0, ‘float32′); % funused 1
fwrite(fp, 0, ‘float32′); % funused 2
fwrite(fp, 0, ‘float32′); % funused 3

fwrite(fp, max(data(:)), ‘float32′);
fwrite(fp, min(data(:)), ‘float32′);
fwrite(fp, 0, ‘float32′);
fwrite(fp, 0, ‘float32′);

fwrite(fp, round(max(data(:))), ‘int32′); % glmax
fwrite(fp, round(min(data(:))), ‘int32′); % glmin

%%
%% Data history
%%
fwrite(fp, repmat(‘ ‘, 1, 80), ‘char’); % descrip
fwrite(fp, repmat(‘ ‘, 1, 24), ‘char’); % aux_file
fwrite(fp, ’3′, ‘char’); % aux_file
fwrite(fp, repmat(‘ ‘, 1, 10), ‘char’); % originator
fwrite(fp, repmat(‘ ‘, 1, 10), ‘char’); % originator
fwrite(fp, repmat(‘ ‘, 1, 10), ‘char’); % originator
fwrite(fp, repmat(‘ ‘, 1, 10), ‘char’); % originator
fwrite(fp, repmat(‘ ‘, 1, 10), ‘char’); % originator
fwrite(fp, repmat(‘ ‘, 1, 10), ‘char’); % originator
fwrite(fp, repmat(‘ ‘, 1, 3), ‘char’); % originator

fwrite(fp, 0, ‘int32′); % views
fwrite(fp, 0, ‘int32′); % vols_added
fwrite(fp, 0, ‘int32′); % start_fiedl
fwrite(fp, 0, ‘int32′); % field_skip
fwrite(fp, 0, ‘int32′); % omax
fwrite(fp, 0, ‘int32′); % omin
fwrite(fp, 0, ‘int32′); % small_max
fwrite(fp, 0, ‘int32′); % small_min

fclose(fp);

%%
%% Write the data
%%
fp = fopen([fname '.img'], ‘wb’, ‘b’);
fwrite(fp, data, ftype);
fclose(fp);
[/cc]

Anisotropic Diffusion Image Filtering in MRI

Background

Magnetic resonance imaging has the tradeoff of signal-to-noise vs time vs resolution.  You can only choose two. For some applications it may be better to get higher temporal and spatial resolution than signal-to-noise and then one may do some spatial filtering.  Simple filtering would be applying a median filter or Gaussian smoothing over the image (or volume).  But there are better techniques.

Smarter Filtering

One option for a smarter filter is the anisotropic diffusion filter which was first introduced to MRI in 1992 ((G. Gerig et al., “Nonlinear anisotropic filtering of MRI data,” Medical Imaging, IEEE Transactions on 11, no. 2 (1992): 221-232. )).  The basic idea is given a central voxel in a kernel and an estimation of noise the surrounding voxels are included in the smoothing based on the difference in signal to the central voxel relative to the estimation of noise.

I wrote a paper on this technique applied to multi-echo data ((Craig K Jones, Kenneth P Whittall, and Alex L MacKay, “Robust myelin water quantification: averaging vs. spatial filtering,” Magnetic Resonance in Medicine: Official Journal of the Society of Magnetic Resonance in Medicine / Society of Magnetic Resonance in Medicine 50, no. 1 (July 2003): 206-209)).

There is a fine line between filtering and over-filtering. That is a whole separate discussion.

The images below are a single slice of an MPRAGE image without filtering (left) and with anisotropic diffusion filtering (right). The bottom set are just zoomed in versions of the top. The filtered data might be slightly over filtered but was done to show the affect of the filter.

Code

Matlab

The version below is for a 3D dataset:
[cc lang="matlab"]

function [filt_vol] = aniso3d(orig_vol, kappa, niters)

if( nargin < 3 )
error(‘aniso3d: Need more parameters’);
end

filt_vol = orig_vol;

for iters = 1:niters

dE = convn(filt_vol, [0 -1 1], ‘full’); dE=dE(:,2:ncols(dE)-1,:);
dW = convn(filt_vol, [-1 1 0], ‘full’); dW=dW(:,2:ncols(dW)-1,:);
dN = convn(filt_vol, [0; -1; 1], ‘full’); dN=dN(2:nrows(dN)-1,:,:);
dS = convn(filt_vol, [-1; 1; 0], ‘full’); dS=dS(2:nrows(dS)-1,:,:);
kernel = zeros(1,1,3); kernel(2) = -1; kernel(3) = 1;
dU = convn(filt_vol, kernel, ‘full’); dU=dU(:,:,2:size(dU,3)-1);
kernel = zeros(1,1,3); kernel(1) = -1; kernel(2) = 1;
dD = convn(filt_vol, kernel, ‘full’); dD=dD(:,:,2:size(dD,3)-1);

filt_vol = filt_vol +  …
3/28 * ((double(exp(- (abs(dE) / kappa).^2 )) .* double(dE)) – (double(exp(- (abs(dW) / kappa).^2 )) .* double(dW))) + …
3/28 * ((double(exp(- (abs(dN) / kappa).^2 )) .* double(dN)) – (double(exp(- (abs(dS) / kappa).^2 )) .* double(dS))) + …
1/28 * ((double(exp(- (abs(dU) / kappa).^2 )) .* double(dU)) – (double(exp(- (abs(dD) / kappa).^2 )) .* double(dD)));
end
[/cc]

For 4D data one can also smooth across the 4th dimension (whether it is time, diffusion etc).
[cc lang="matlab"]
function [filt_vol] = aniso3d_chan(orig_vol, kappa, niters)
%
% aniso3d_chan – Run the anisotropic diffusion filter in 3D
% and over the multiple channels.
%

if( nargin < 3 )
error(‘aniso3d: Need more parameters’);
end

filt_vol = float(squeeze(orig_vol));

for iters = 1:niters
dE = convn(filt_vol, [0 -1 1], ‘full’); dE=dE(:,2:ncols(dE)-1,:,:);
cE = repmat(sqrt(sum(dE.^2, 4)), [1 1 1 size(dE,4)]);
filt_vol = filt_vol + 3/28 * ((exp(- (cE / kappa).^2 )) .* (dE));
clear cE;
clear dE;

dW = convn(filt_vol, [-1 1 0], ‘full’); dW=dW(:,2:ncols(dW)-1,:,:);
cW = repmat(sqrt(sum(dW.^2, 4)), [1 1 1 size(dW,4)]);
filt_vol = filt_vol – 3/28 * ((exp(- (cW / kappa).^2 )) .* (dW));
clear dW;
clear cW;

dN = convn(filt_vol, [0; -1; 1], ‘full’); dN=dN(2:nrows(dN)-1,:,:,:);
cN = repmat(sqrt(sum(dN.^2, 4)), [1 1 1 size(dN,4)]);
filt_vol = filt_vol + 3/28 * ((exp(- (cN / kappa).^2 )) .* (dN));
clear dN;
clear cN;

dS = convn(filt_vol, [-1; 1; 0], ‘full’); dS=dS(2:nrows(dS)-1,:,:,:);
cS = repmat(sqrt(sum(dS.^2, 4)), [1 1 1 size(dS,4)]);
filt_vol = filt_vol – 3/28 * ((exp(- (cS / kappa).^2 )) .* (dS));
clear cS;
clear dS;

kernel = zeros(1,1,3); kernel(2) = -1; kernel(3) = 1;
dU = convn(filt_vol, kernel, ‘full’); dU=dU(:,:,2:size(dU,3)-1,:);
cU = repmat(sqrt(sum(dU.^2, 4)), [1 1 1 size(dS,4)]);
filt_vol = filt_vol + 1/28 * ((exp(- (cU / kappa).^2 )) .* (dU));
clear dU;
clear cU;

kernel = zeros(1,1,3); kernel(1) = -1; kernel(2) = 1;
dD = convn(filt_vol, kernel, ‘full’); dD=dD(:,:,2:size(dD,3)-1,:);
cD = repmat(sqrt(sum(dD.^2, 4)), [1 1 1 size(dS,4)]);
filt_vol = filt_vol – 1/28 * ((exp(- (cD / kappa).^2 )) .* (dD));
clear dD;
clear cD;
end
[/cc]

Python

The Python code is very similar to the Matlab code above. It does 2D images or 3D volumes, but I have not coded the smoothing across the 4th dimension. That will have to be done later.
[cc lang="python"]
def aniso(v, kappa=-1, N=1):

if kappa == -1:
kappa = prctile(v, 40)

vf = v.copy()

for ii in range(N):
dE = -vf + roll(vf,-1,0)
dW = vf – roll(vf,1,0)

dN = -vf + roll(vf,-1,1)
dS = vf – roll(vf,1,1)

if len(v.shape) > 2:
dU = -vf + roll(vf,-1,2)
dD = vf – roll(vf,1,2)

vf = vf + \
3./28. * ((exp(- (abs(dE) / kappa)**2 ) * dE) – (exp(- (abs(dW) / kappa)**2 ) * dW)) + \
3./28. * ((exp(- (abs(dN) / kappa)**2 ) * dN) – (exp(- (abs(dS) / kappa)**2 ) * dS))
if len(v.shape) > 2:
vf += 1./28. * ((exp(- (abs(dU) / kappa)**2 ) * dU) – (exp(- (abs(dD) / kappa)**2 ) * dD))

return vf
[/cc]

Pulse Sequence Diagrammer

Overview

The matlab code in this directory should facilitate creating publication quality PSDs (pulse sequence diagrams) using Matlab. Look at the example files (cse.m, cpmg.m and fse.m) to see how to use the code. All files are script files so this should run on any machine that Matlab runs on.

Code: mrpsd_12.tar.gz

Matlab Version …

I know that it worked under version 5.x of Matlab, but it should work under any newer version as well.

Why under Matlab?

Ahh.. good question. There are many reasons:

1) Many people use Matlab for their data analysis and general coding.

2) All of the print facilities are built in (so you can print to JPEG, Postscript, BMP, TIFF etc etc).

3) Many things come free with the way that it is designed, for example, if you want to look at only one temporal section of your PSD, all you have to do is plot it up and then do: set(gca, ‘xlim’, [50 100]) (if you want to look at between 50ms and 100ms). USE YOUR IMAGINATION HERE. There are potentially lots of little things like the previous example that I have not even thought of.

E-mail me

I would be very interested in any suggestions, fixes (!) that you can send along to make this toolbox better. I would also like any more example files that plot up other pulse sequences (spectroscopy, EPI etc etc). My e-mail is craig@mri.jhu.edu. It is free software, I will not restrict use in any way, shape or form (other than don’t sell it). I would appreciate, though, any enhancements that you can. I will try to make available updates as often as possible.

Standard Disclaimer

By using the software, I accept absolute no responsibility for anything. Use it at your own risk. It is absolutely GPL‘ed software.

Have fun with it.

Noise in MRI (magnitude) data

Background

Magnitude MRI data has Rician noise distribution by definition ((Hákon Gudbjartsson and Samuel Patz, “The Rician Distribution of Noisy MRI Data,” Magnetic resonance in medicine : official journal of the Society of Magnetic Resonance in Medicine / Society of Magnetic Resonance in Medicine 34, no. 6 (December 1995): 910-914)). It comes about because two channels each with Gaussian noise are squared and added together ((R M Henkelman, “Measurement of signal intensities in the presence of noise in MR images,” Medical Physics 12, no. 2 (April 1985): 232-233.)).  There is a longer description here.

Modeling

The Rician noise is created as $latex y_e(t_i) = \sqrt{ \left[y(t_i) + e_1 \right]^2 + e_2^2 }$, where $latex y$ is the true signal, and $latex e_1$ and $latex e_2$ are random numbers from a Gaussian distribution with zero mean and standard deviation $latex \sigma$.  The standard deviation, $latex \sigma$, for the Gaussian distribution is related to the signal to noise ratio and is typically on the order of 1% – 10% of the signal $latex y$.

Code

It is relatively easy to model this using Matlab or Python. For the code here I am modeling a T2 decay curve and then the noise.

Matlab

[cc lang="matlab"]

%  Setup the initial variables
rho = 100;
t2 = 80; % in ms
te = 10:10:320;  % in ms

%  Create a T2 decay curve
y = rho * exp(-te / t2 );

%  Define the noise to be 5% of the signal
s = 5;

%  Create the two Gaussian random variable vectors
e1 = s * randn(size(y));
e2 = s * randn(size(y));

%  Now create the new, noisy decay curve.
y_e = sqrt( (y+e1).^2 + (e2).^2 );

[/cc]

Python

The Python version is quite similar.

[cc lang="python"]

from __future__ import division

#  Setup the initial variables
rho = 100
t2 = 80 # in ms
te = r_[10:330:10] # in ms

#  Create a T2 decay curve
y = rho * exp( -te / t2 )

#  Define the noise to be 5% of the signal
s = 5;

#  Create the two Gaussian random variable vectors
e1 = normal(0, 5, y.shape)
e2 = normal(0, 5, y.shape)

#  Now create the new, noisy decay curve.
y_e = sqrt( (y+e1)**2 + (e2)**2 );

[/cc]

There are a couple of small gotcha’s that at least tripped me up as I am still relatively new to Python.

  1. The first is that under Python 2.x all data is processed as integer (not doubles, as the default is in Matlab).  Supposedly this is going to change in Python 3, but to get around it for now, the best thing to do is to add the [cci lang="python"]from __future__ import divison[/cci].
  2. To define [cci lang="python"]te[/cci] I had to go to 330, rather than 320 as the generator is an open set on the higher end so it does not include the number.
  3. There are several options for creating the random numbers.  There is a Python module called [cci lang="python"]random[/cci] that could be used.  Instead I used the Numpy [cci lang="python"]normal[/cci] instead as I can pass in the shape parameter.