Code covered by the BSD License  

Highlights from
Compressed Sensing MRI Phantom (v1.1)

image thumbnail
from Compressed Sensing MRI Phantom (v1.1) by David Smith
A realistic phantom for compressed sensing MRI reconstruction.

csphantom (n, bandwidth, signalToNoiseRatio, b1scale, img_real)
function I = csphantom (n, bandwidth, signalToNoiseRatio, b1scale, img_real)
%%CSPHANTOM  Test image for compressive sensing reconstructions.
%
%  IMG = CSPHANTOM(N) returns an NxN phantom with various features designed
%  to fail a Fourier-based compressed sensing reconstruction.
%
%  IMG = CSPHANTOM(N,BW) returns an NxN phantom with sampling bandwidth
%  equal to BW*N. This more accurately simulates what occurs when a
%  physical phantom is sampled in an MRI scanner. The default value of BW
%  is Inf. BW can be thought of as the intrinsic phantom pixel size divided 
%  by the Fourier measurement resolution. This parameter affects the
%  sharpness of small features and the level of Gibbs ringing visible.
%
%  Copyright 2012 David S. Smith <david.smith@vanderbilt.edu>.
%
%  This file is part of CSPHANTOM.



if nargin < 1; n = 256; end  % default size if none given
if nargin < 2; bandwidth = Inf; end
if nargin < 3; signalToNoiseRatio = Inf; end
if nargin < 4; b1scale = 1; end
if nargin < 5; img_real = true; end


if ~isinf(bandwidth)
  n = n * bandwidth; % temporarily upsample the phantom creation
end

if n < 8, error('Smallest sensible phantom is 8 x 8.'); end

% quadrant sizes - slightly less than one fourth of the area, to give room 
% for a border
quadrant_size = round((n - round(0.1*n))/2);

% Set up background shading: low amplitude sinc, a la B1 inhomogeneities
bglevel = 0.5;  % central background gray level
[X, Y] = meshgrid(1:(2*quadrant_size), 1:(2*quadrant_size));
R = sqrt((X-quadrant_size).^2 + (Y-quadrant_size).^2);
BG = bglevel * cos(b1scale*R/(0.4*pi*quadrant_size));

% go back to quadrant coordinates
[X, Y] = meshgrid(1:quadrant_size, 1:quadrant_size);

% QUADRANT II: low-contrast circles
nc = 4;  % # rows and columns of circles
Q2 = BG(1:quadrant_size,1:quadrant_size);
inc = quadrant_size / (nc+1);
for j = 1:nc
	rad = quadrant_size / (11 + j^2);
	for k = 1:nc
		val  = 0.9*bglevel - 0.1/k;
		rctr = round(j*inc);
		cctr = round(k*inc);
		Z2 = (X-cctr).^2 + (Y-rctr).^2 <= rad^2;
		Q2(Z2) = val;
	end
end

% QUADRANT I: diagonal ramp
Q1 = BG(1:quadrant_size,(quadrant_size+1):(2*quadrant_size));
scale = quadrant_size/5;
ctr = round(quadrant_size/2);
Z = abs(X-ctr) + abs(Y-ctr);
Z1 = Z <= scale;
Z2 = Z > scale & Z <= 2*scale;
Q1(Z1) = Q1(Z1) * 2 .* (1 - Z(Z1) / scale);
Q1(Z2) = Q1(Z2) .* (Z(Z2) / scale - 1);
d = round(3*quadrant_size/16);
r = [d quadrant_size-d];
c = [d quadrant_size-d];
w = round(d/4);
for j = 1:2
	for k = 1:2
		Q1(r(j)-w:r(j)+w,c(k)-w:c(k)+w) = Q1(r(j)-w:r(j)+w,c(k)-w:c(k)+w)*(1+k*j/64);
	end
end

% QUADRANT III: Gaussian bumps and quadratic hole
ctr = round(quadrant_size/2);
scale = quadrant_size / 4;
R2 = (X-ctr).^2 + (Y-ctr).^2;
Q3 = BG((quadrant_size+1):(2*quadrant_size),1:quadrant_size);
Q3withHole = Q3 .* R2 / scale^2;
Q3 = min(Q3,Q3withHole);
ctr = round([quadrant_size/5 4*quadrant_size/5]);
scale = scale / 6;
for r = 1:2
	for c = 1:2
		Q3 = Q3 + (3-r)*c*0.1*bglevel * exp(-((X-ctr(r)).^2+(Y-ctr(c)).^2)/scale^2);
	end
end

% QUADRANT IV: line pairs
Q4 = BG((quadrant_size+1):(2*quadrant_size),(quadrant_size+1):(2*quadrant_size));
border_thick = round(quadrant_size/8); % define a border around the line pair region
line_length = max(1,border_thick); 
Q4floor = 0.1;
%
% HORIZONTAL LINES
%
line_sep = 1;
line_thick = 1;
nlines = 3;
sep_incr = 1;
if ~isinf(bandwidth)
  line_sep = line_sep*round(bandwidth);
  line_thick = line_thick*round(bandwidth);
  sep_incr = sep_incr*round(bandwidth);
end
col = 1;
row = 1 + round(0.5*border_thick) + line_length;
while (row + line_thick) <= quadrant_size - border_thick
  for t = 1:nlines
    Q4(row:row+line_thick-1,col:col+line_length-1) = Q4floor;
    row = row + line_thick + line_sep;
    if (row + line_thick) > quadrant_size - border_thick, break; end
  end
  row = row + line_sep + line_thick;
	line_sep = line_sep + sep_incr;
  line_thick = line_thick + sep_incr;
end
%
% VERTICAL LINES
%
line_sep = 1;
line_thick = 1;
sep_incr = 1;
if ~isinf(bandwidth)
  line_sep = line_sep*round(bandwidth);
  line_thick = line_thick*round(bandwidth);
  sep_incr = sep_incr*round(bandwidth);
end
col = 1 + round(0.5*border_thick) + line_length;
row = 1;
while (col + line_thick) <= quadrant_size - border_thick  % vertical lines
  %Q4(row:row+line_length-1,col:col+line_thick-1) = Q4floor;
  for t = 1:nlines
    Q4(row:row+line_length-1,col:col+line_thick-1) = Q4floor;
    col = col + line_thick + line_sep;
    if (col + line_thick) > quadrant_size - border_thick, break; end
  end
	col = col + line_sep + line_thick;
	line_sep = line_sep + sep_incr;
  line_thick = line_thick + sep_incr;
end
%
% CIRCLES
%
nlines = 3;
ctr = round(quadrant_size/2);  % concentric circles
R = sqrt((X-ctr).^2 + (Y-ctr).^2);
circ_thick = 1;
circ_gap = 3;
circ_incr = 1;
if ~isinf(bandwidth)
  circ_thick = circ_thick*round(bandwidth);
  circ_gap = circ_gap*round(bandwidth);
  circ_incr = circ_incr*round(bandwidth);
end
r = circ_thick + circ_gap;
white = false;
while (ctr + r) < (quadrant_size - 1.5*border_thick)
  for t = 1:nlines
    rim = abs(R - r) <= 2*circ_thick;
    if white
      Q4(rim) = Q4(rim).*(1+Q4floor*exp(-2*(R(rim)-r).^2/circ_thick^2));
    else
      Q4(rim) = Q4(rim).*(1-exp(-2*(R(rim)-r).^2/circ_thick^2));
    end
    r = r + circ_thick + circ_gap;
    if (ctr + r) > (quadrant_size - 1.2*border_thick), break; end
  end
  if white
    white = false;
  else
    white = true;
  end
  r = r + circ_gap;
end

% pad area of signal with 'air'
npadf = (n - 2*quadrant_size) / 2;
npad = round(npadf);
I = zeros(n);
I(npad+1:npad+2*quadrant_size,npad+1:npad+2*quadrant_size) = [Q2 Q1; Q3 Q4];

% downsample to specified sampling bandwidth
if ~isinf(bandwidth)
  F = fft2(I);
  n = n / bandwidth;  % truncate Fourier data
  F = F([1:ceil(n/2) end-floor(n/2)+1:end],[1:ceil(n/2) end-floor(n/2)+1:end]);
  % ringing filter
  W = ifftshift(tukey(n)*tukey(n)');
  F = F .* W; % apply window
  I = abs(ifft2(F)) / bandwidth^2;
end

% finally add some Gaussian complex noise, if SNR .ne. Inf
if ~isinf(signalToNoiseRatio)
  sigma = sqrt(0.125 / (signalToNoiseRatio^2 + 1));
  I = I + sigma*randn(size(I)) + 1i*sigma*randn((size(I)));
end

if img_real, I = abs(I); end


function w = tukey (N)
%%WINDOW  Tukey window
alpha = 0.8;
w = ones(N,1);
n = -(N-1)/2 : -alpha*N/2;
L = length(n);
w(1:L) = 0.5*(1+cos(pi*(abs(n)-alpha*N/2)/((1-alpha)*N/2)));
w(N : -1 : N-L+1) = w(1:L);

Contact us