Project: High Angular Resolution Diffusion Imaging Tools

Example to illustrate the Spherical Deconvolution reconstruction via the Lucy-Richardson (L-R) algorithm. The fiber ODF is defined by the multi-tensor diffusion model.

Contents

- Parameters for reconstruction: All this part is equal for each voxel

clear all; close all; clc;

% - Loading the measurement scheme

grad = load('/sampling_and_reconstruction_schemes/On_the_sphere/70_shell.txt');
% grad is a Nx3 matrix where grad(i,:) = [xi yi zi] and sqrt(xi^2 + yi^2 + zi^2) == 1 (unit vectors).
display(['The measurement scheme contains ' num2str(size(grad,1)) ' directions']);

% - Loading the reconstruction scheme (mesh where the fiber ODF will be reconstructed)
%   ---------------------------------
% unit vectors on the sphere
disp(' ')
V = load('/sampling_and_reconstruction_schemes/On_the_sphere/724_shell.txt');
% facets
display(['The reconstruction scheme contains ' num2str(size(V,1)) ' directions']);
F = load('/sampling_and_reconstruction_schemes/On_the_sphere/724_sphere_facets.txt');
disp(' ');

% - Creating the Kernel for the reconstruction.
%   ---------------------------------
% Kernel is a Dictionary of diffusion tensor signals oriented along several
% directions on the sphere (in our example V, which is the set of orientations where the fODF
% will be computed).
% It also includes two compartments with isotropic diffusion to fit real Brain data: CSF and GM

% diffusivities assumed for the Dictionary
lambda1 = 1.2*10^-3;% mm^2/s
lambda2 = 0.0*10^-3;% mm^2/s (Like the ball and stick model of T. Behrens)
% b-value and grad: equal to the experimental parameters used to adquire the data
b = 3*10^3;% s/mm^2
grad = [0 0 0; grad]; % Add a measurement with b-value = 0 into the gradient matrix
                      % (standard format in which the signal is measured in the scanner)

SNR = 20; % This is a parameter required for the "create_signal_multi_tensor" function,
          % however, in this case the signal will be generated without noise.
add_rician_noise = 0;

[phi, theta] = cart2sph(V(:,1),V(:,2),V(:,3)); % set of directions
Kernel = zeros(length(grad),length(V) );
S0 = 1; % The Dictionary is created under the assumption S0 = 1;
fi = 1; % volume fraction
for i=1:length(phi)
    anglesFi = [phi(i), theta(i)]*(180/pi); % in degrees
    Kernel(:,i) = create_signal_multi_tensor(anglesFi, fi, [lambda1, lambda2, lambda2], ...
                                             b, grad, S0, SNR, add_rician_noise);
end
% adding the isotropic compartment for CSF
lambda1 = 2.0*10^-3;% mm^2/s
Kernel(:,length(phi) + 1) = create_signal_multi_tensor([0 0], fi, [lambda1, lambda1, lambda1], ...
                                             b, grad, S0, SNR, add_rician_noise);
% adding the isotropic compartment for GM
lambda1 = 0.7*10^-3;% mm^2/s
Kernel(:,length(phi) + 2) = create_signal_multi_tensor([0 0], fi, [lambda1, lambda1, lambda1], ...
                                             b, grad, S0, SNR, add_rician_noise);
%   ---------------------------------
The measurement scheme contains 70 directions
 
The reconstruction scheme contains 724 directions
 

- Simulated diffusion MRI signal using a multi-tensor model

% --- Experimental parameters --- %
% new real diffusivities
lambda1 = 1.5*10^-3;% mm^2/s
lambda2 = 0.3*10^-3;% mm^2/s
% S0 is the image at b-value = 0
S0 = 100;
% Signal to noise ratio
SNR = 20;
display(['The signal to noise ratio in this experiment is SNR = ' num2str(SNR) '.']);

% --- Intravoxel properties --- %
% Using a mixture of two fibers (more fibers can be used)
% volume fraction for each fiber (the sum of this elemenst is 1 by definition)
f1 = 0.5; f2 = 0.5;
% angular orientation corresponding to each fiber (main eigenvector for each diffusion tensor)
anglesF = [0  0;
           55 0];
% [ang1 ang2]: -> ang1 is the rotation in degrees from x to y axis (rotation using the z-axis);
%                 ang2 is the rotation in degrees from the x-y plane to the z axis (rotation using the y axis)
% anglesF(i,:) defines the orientation of the fiber with volume fraction fi.

%  --- Computation of the diffusion signal using the above experimental parameters and intravoxel properties %
% *** Signal with realistic rician noise
add_rician_noise = 1;
S = create_signal_multi_tensor(anglesF, [f1  f2],...
    [lambda1, lambda2, lambda2], b, grad, S0, SNR, add_rician_noise);
The signal to noise ratio in this experiment is SNR = 20.

- Initial estimate of the S0 component from the signal

% --- find zeros
grad_sum = sum(abs(grad),2);
ind_0 = find( grad_sum == 0);
S0_est = mean(S(ind_0));

- fODF estimation via the standard and damped L-R algorithms

% --- parameters definition
%type_of_noise = 'Gaussian'; % (Default reconstruction published by Flavio Dell’Acqua!)
type_of_noise = 'Poisson'; % (Experimental, not extensively tested, it provides sharper solutions)
Niter = 200; % optimal value between 200 and 400 (for Gaussian noise)

% -- Initial solution: Constant value on the sphere, non-negative iso-probable spherical function
fODF0 = ones(size(Kernel,2));
fODF0 = fODF0/sum(fODF0);

% --- standard L-R reconstruction
display(' ')
display('This is the real/effective time required for computing the fODF at each voxel')
display(' via the standard L-R algorithm:')
%
tic
[fODF_std, fgm1, fcsf1] = sph_deconv_RL(S/S0_est, Kernel, fODF0, Niter, type_of_noise);
% fgm is the fraction of the isotropic component in GM tissues.
% fcsf is the fraction of the isotropic component in CSF.
toc

% --- damped L-R reconstruction
display(' ')
display('This is the real/effective time required for computing the fODF at each voxel')
display(' via the damped L-R algorithm:')
%
tic
[fODF_d, fgm2, fcsf2] = sph_deconv_RLdamp(S/S0_est, Kernel, fODF0, Niter, type_of_noise);
% fgm is the fraction of the isotropic component in GM tissues.
% fcsf is the fraction of the isotropic component in CSF.
toc
 
This is the real/effective time required for computing the fODF at each voxel
 via the standard L-R algorithm:
Elapsed time is 0.027782 seconds.
 
This is the real/effective time required for computing the fODF at each voxel
 via the damped L-R algorithm:
Elapsed time is 0.069130 seconds.

- fODF estimation via non-negative least squares

display(' ')
display('This is the real/effective time required for computing the fODF at each voxel')
display(' via the linear least squares algorithm with nonnegativity constraints:')
%
options = optimset('lsqnonneg');
options = optimset(options,'TolX',1e-14);
tic
fODF_pos = lsqnonneg(Kernel, S/S0_est,options);
fgm3  = fODF_pos(end);       % fraction of the isotropic component for GM
fcsf3 = fODF_pos(end-1);     % fraction of the isotropic component for CSF
fODF_pos(end-1:end) = [];    % purely anisotropic part
toc
 
This is the real/effective time required for computing the fODF at each voxel
 via the linear least squares algorithm with nonnegativity constraints:
Elapsed time is 0.024180 seconds.

- Plots

% --- opengl('software');
feature('UseMesaSoftwareOpenGL',1);

set(gcf, 'color', 'white');
screen_size = get(0, 'ScreenSize');
f1 = figure(1); hold on;
set(f1, 'Position', [0 0 screen_size(3) screen_size(4) ] );
cameramenu;
title('Spherical deconvolution methods')
% -----
Origen = [0 0 0.25]; % Voxel coordinates [x y z] where the ODF will be displayed
angle = 0:.01:2*pi;
R = ones(1,length(angle));
R1 = R.*f2/f1;

subplot(2,2,1)
polarm(angle,R,'k'); hold on; polarm(angle, R1,'k');
% ---
anglesF = anglesF*(pi/180);
[Vx, Vy, Vz] = sph2cart(anglesF(:,1), anglesF(:,2), ones(size(anglesF,1),1));
for i=1:size(Vx,1)
    hold on
    Color_i = [Vx(i) Vy(i) Vz(i)]/sqrt( Vx(i)^2 + Vy(i)^2  + Vz(i)^2 );
    Color_i = Color_i.*sign(Color_i);
    line([-Vx(i) Vx(i)], [-Vy(i) Vy(i)], [-Vz(i) Vz(i)], 'LineWidth', 10, 'Color', Color_i);
end
% ---
title('\fontsize{14} True fODF', 'FontWeight','bold')
axis equal; axis off;

subplot(2,2,2)
polarm(angle,R,'k'); hold on; polarm(angle, R1,'k');
% ---
% the fODF is scaled only for the display
fODF_pos = fODF_pos./max(fODF_pos);
ind0 = find(fODF_pos == 0);
fODF_pos(ind0) = [];
Vaux = V; Vaux(ind0,:) = [];
Vx = Vaux(:,1); Vy = Vaux(:,2); Vz = Vaux(:,3);
for i=1:size(Vx,1)
    hold on
    Color_i = [Vx(i) Vy(i) Vz(i)]/sqrt( Vx(i)^2 + Vy(i)^2  + Vz(i)^2 );
    Color_i = Color_i.*sign(Color_i);
    line(fODF_pos(i)*[-Vx(i) Vx(i)], fODF_pos(i)*[-Vy(i) Vy(i)], fODF_pos(i)*[-Vz(i) Vz(i)], 'LineWidth', 10, 'Color', Color_i);
end
% ---
title(['\fontsize{14} fODF via the "lsqnonneg" function , SNR = ' num2str(SNR) ], 'FontWeight','bold')
axis equal; axis off;

subplot(2,2,3)
polarm(angle,R,'k'); hold on; polarm(angle, R1,'k');
% the fODF is scaled only for the display
plot_ODF(fODF_std./max(fODF_std), V, F, Origen);
title(['\fontsize{14} fODF via the standard L-R algorithm , SNR = ' num2str(SNR), ' Niter = ' num2str(Niter) ], 'FontWeight','bold')
axis equal; axis off;

subplot(2,2,4)
polarm(angle,R,'k'); hold on; polarm(angle, R1,'k');
% the fODF is scaled only for the display
plot_ODF(fODF_d./max(fODF_d),V, F, Origen);
title(['\fontsize{14} fODF via the damped L-R algorithm , SNR = ' num2str(SNR), ' Niter = ' num2str(Niter) ], 'FontWeight','bold')
axis equal; axis off;