Contents

% cone_beam_ct_example.m
% Illustrate cone-beam X-ray CT image reconstruction via FDK and iterative.
% Uses a tiny system size because cone-beam projection can be slow.
% Copyright 2005-1-21, Jeff Fessler, University of Michigan

First run the FDK example. It generates the true image xtrue

and noiseless projection views "proj" and noisy data "yi" and generates (noisy) FDK recon "xfdk" for comparison / initialization.

if ~isvar('xfdk')
	bi = 1e6; % 1M photons / ray
	ri = 0; % no scatter etc. for now
	dfs = inf; % flat!
	feldkamp_example
prompt
end
feldkamp_example 6: cg: cone-beam CT geometry
feldkamp_example 17: fov rmax=255.318
feldkamp_example 21: ig: image geometry
feldkamp_example 31: ell: ellipsoid object
feldkamp_example 41: xtrue: true image volume
feldkamp_example 49: proj: analytical ellipsoid projection views
feldkamp_example 57: li_hat: projection view log data
feldkamp_example 71: fdk
fdk backprojection time: 0.085593
feldkamp_example 143: xtrue: min=0 max=0.02 
FDK error min=-0.00927172 max=0.00884152 
feldkamp_example 145: nrmse = 17.2666%

generate system matrix needed for iterative reconstruction

if ~isvar('A'), printm 'A'
	f.nz_pad = 18; % add this many slices to each side.  todo: how many?
	ig_pad = ig.expand_nz(f.nz_pad);
	A = Gcone(cg, ig_pad, 'type', 'sf2', 'class', 'Fatrix');
%	f.sys_type = aspire_pair(cg, ig_pad, 'system', '3l'); % old way
%	A = Gtomo3(f.sys_type, ig_pad.mask, ig.nx, ig.ny, ig_pad.nz, ...
%		'chat', 0, 'permute213', true, 'checkmask', im&0);
end


% block object for ordered-subsets iterations
if ~isvar('Ab'), printm 'Ab'
	f.nblock = 8;
	Ab = Gblock(A, f.nblock);
end


if 1 % padding functions to help with long object problem
	f.rep = @(x) cat(3, repmat(x(:,:,1), [1 1 f.nz_pad]), x, ...
		repmat(x(:,:,end), [1 1 f.nz_pad]));
	f.pad = @(x) cat(3, zeros(ig.nx, ig.ny, f.nz_pad), x, ...
		zeros(ig.nx, ig.ny, f.nz_pad));
end


% check discrete vs analytical projection (note long object problem)
if 0, printm 'proj check'
	for ii=1:1
		cpu etic
		pp = Ab * f.rep(xtrue);
		cpu etoc 'proj time'
	end
	nrms(pp, proj)
	im clf, im_toggle(proj(:,:,1:12:end), pp(:,:,1:12:end), [0 4.4])
prompt
end
cone_beam_ct_example 19: A
cone_beam_ct_example 30: Ab

regularization object

if ~isvar('R'), printm 'regularizer'
	f.l2b = 2^4.5;
	f.delta = 100/1000;
	R = Reg1(ig_pad.mask, 'type_denom', 'matlab', ...
                'pot_arg', {'hyper3', f.delta}, 'beta', 2^f.l2b);
	W = Gdiag(yi);
	if 1 % check spatial resolution (away from edges)
		psf = qpwls_psf(A, R, 1, ig_pad.mask, W, 'fwhmtype', 'profile');
	prompt
	end
end


if ~isvar('si'), printm 'log sinogram'
	si = log(bi ./ yi); % log sinogram
	xinit = ig_pad.maskit(f.rep(xfdk)); % initalize iterations
	im(ig_pad.embed(xinit))
end
cone_beam_ct_example 58: regularizer
Warn: form_ctc_strum 311: delta=0.1 but cgrad_delta=0.01.  BAD!
qpwls_psf(143): reale: imaginary fraction of  is 0.16445
qpwls_psf 149: gtg, negative FT: -1.14316%, n=78538
qpwls_psf 159: ctc, negative FT: -5.22115e-06%, n=1
qpwls_psf 188: approximate condition number 458.875
qpwls_psf_fwhm 441: fwhm = 1.34187 1.53845 1.094 [pixel]
cone_beam_ct_example 71: log sinogram

OS-SQS iterations for PWLS

if ~isvar('xos'), printm 'start os-sqs pwls iterations'
	f.niter_os = 10;
	xos = pwls_sqs_os(xinit, Ab, reshaper(si, '2d'), R, ...
			'wi', reshaper(W.arg.diag, '2d'), 'niter', f.niter_os);
	xos = ig_pad.embed(xos);
	im(xos), cbar
end
cone_beam_ct_example 79: start os-sqs pwls iterations
Warn: iter_saver 13: using default isave "last"
pwls_sqs_os: 2 of 10 0.1
pwls_sqs_os: 4 of 10 0.2
pwls_sqs_os: 6 of 10 0.2
pwls_sqs_os: 8 of 10 0.2
pwls_sqs_os: 10 of 10 0.3

CG iterations

if ~isvar('xpcg1'), printm 'PWLS reconstruction with CG (no preconditioner)'
	f.niter1 = 20;
	[xpcg1 cost_pcg1] = pwls_pcg1(xinit, A, W, si(:), R, ...
		'userfun', @userfun_cost1, ...
		'niter', f.niter1, 'stop_threshold', 1e-3);
	xpcg1 = ig_pad.embed(xpcg1);
	im(xpcg1), cbar
prompt
end


% circulant preconditioner based on middle voxel and quadratic regularizer
if ~isvar('pre2'), printm 'circulant preconditioner'
	Rq = Reg1(ig_pad.mask, 'beta', 2^f.l2b); % quadratic regularizer
	pre2 = qpwls_precon('circ0', {A, W}, Rq.C, ig_pad.mask); % preconditioner
end
cone_beam_ct_example 89: PWLS reconstruction with CG (no preconditioner)
pwls_pcg1: 3 of 20 0.1
pwls_pcg1: 5 of 20 0.1
pwls_pcg1: 7 of 20 0.1
pwls_pcg1: 9 of 20 0.1
pwls_pcg1: 11 of 20 0.1
pwls_pcg1: 13 of 20 0.2
pwls_pcg1: 15 of 20 0.2
pwls_pcg1: 17 of 20 0.2
pwls_pcg1: 19 of 20 0.2
cone_beam_ct_example 101: circulant preconditioner
qpwls_precon_circ_Mdft(169): reale: imaginary fraction of  is 0.16445
qpwls_precon_circ_Mdft 171: setting -1.14316% to zero

PCG

if 0 | ~isvar('xpcg2'), printm 'PWLS with PCG and circulant preconditioner'
	[xpcg2 cost_pcg2] = pwls_pcg1(xinit, A, W, si(:), R, ...
		'userfun', @userfun_cost1, ...
		'niter', f.niter1, 'stop_threshold', 1e-3, 'precon', pre2);
	xpcg2 = ig_pad.embed(xpcg2);
	im(xpcg2), cbar
prompt
end


if 0 % compare "convergence rate" (of cost) without and with preconditioning
	if im
		clf
		plot(1:f.niter1, cost_pcg1, '-o', 1:f.niter1, cost_pcg2, '-x')
		xlabel 'iteration', ylabel 'cost'
		legend('CG (no precon)', 'PCG2 (circulant precon)')
	end
return
end


% reshape data to be "2d arrays" for OS iterations (subset over last dim)
if ~isvar('os_data'), printm 'os_data'
	if isscalar(bi) && isscalar(ri)
		os_data = reshaper(yi, '2d');
		os_data = {os_data, ...
			bi * ones(size(os_data)), ri * ones(size(os_data))};
	else
		os_data = {reshaper(yi, '2d'), reshaper(bi, '2d'), ...
			reshaper(ri, '2d')}; % all data as 2d arrays
	end
end
cone_beam_ct_example 108: PWLS with PCG and circulant preconditioner
pwls_pcg1: 3 of 20 0.1
pwls_pcg1: 5 of 20 0.1
pwls_pcg1: 7 of 20 0.1
pwls_pcg1: 9 of 20 0.1
pwls_pcg1: 11 of 20 0.2
pwls_pcg1: 13 of 20 0.2
pwls_pcg1: 15 of 20 0.2
pwls_pcg1: 17 of 20 0.2
pwls_pcg1: 19 of 20 0.3
cone_beam_ct_example 130: os_data

OS-SPS iterations for transmission penalized likelihood

if ~isvar('xpl'), printm 'start tpl os-sqs iterations'
	f.niter = 20;
	xs = tpl_os_sps(xinit, Ab, os_data{:}, R, 1+f.niter);
	xs = ig_pad.embed(xs);
	xpl = xs(:,:,:,end);
	im(xpl)
end
cone_beam_ct_example 143: start tpl os-sqs iterations
tpl_os_sps: 3 of 21 0.1
tpl_os_sps: 4 of 21 0.1
tpl_os_sps: 5 of 21 0.1
tpl_os_sps: 6 of 21 0.1
tpl_os_sps: 7 of 21 0.1
tpl_os_sps: 8 of 21 0.1
tpl_os_sps: 9 of 21 0.2
tpl_os_sps: 10 of 21 0.2
tpl_os_sps: 11 of 21 0.2
tpl_os_sps: 12 of 21 0.2
tpl_os_sps: 13 of 21 0.2
tpl_os_sps: 14 of 21 0.3
tpl_os_sps: 15 of 21 0.3
tpl_os_sps: 16 of 21 0.3
tpl_os_sps: 17 of 21 0.3
tpl_os_sps: 18 of 21 0.3
tpl_os_sps: 19 of 21 0.4
tpl_os_sps: 20 of 21 0.4
tpl_os_sps: 21 of 21 0.4

compare FDK vs iterative results

if 1
	iz_good = f.nz_pad + [1:ig.nz]; % original slices
	xpcg1_good = xpcg1(:,:,iz_good);
	xpl_good = xpl(:,:,iz_good);
	xos_good = xos(:,:,iz_good);
	show = @(x) xlabelf('rms = %.1f HU', rms(col(xtrue-x))/0.01*1000);
	clim = [0 0.02];
	im plc 2 3
	im(1, xtrue, 'true', clim), cbar
	im(2, xfdk, 'FDK', clim), cbar
	show(xfdk)
	im(4, xpcg1_good, 'PWLS-PCG', clim), cbar
	show(xpcg1_good)
	im(5, xpl_good, 'PL-OS', clim), cbar
	show(xpl_good)
	im(6, xos_good, 'PWLS-OS', clim), cbar
	show(xos_good)
end