├── Sample_sinogram.sino ├── filterProjections.m ├── designFilter.m ├── fbp_sino_filter.m ├── README.md ├── SAMPLE_FFBP.m ├── fbp2_window.m └── FFBP_Weighted.m /Sample_sinogram.sino: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kk17m/CT-Fan-beam-FBP-reconstruction/HEAD/Sample_sinogram.sino -------------------------------------------------------------------------------- /filterProjections.m: -------------------------------------------------------------------------------- 1 | function [p,sino,Hk,hn] = filterProjections(p_in, filter, DTA, SOD, SDD, Fan_sensor_spacing) 2 | 3 | p_in = p_in.*(cos(DTA)*SOD); 4 | p = p_in; 5 | 6 | % Sinpgram filtering 7 | % (Jeffrey A Fessler, "Michigan Image Reconstruction Toolbox," 8 | % http://web.eecs.umich.edu/~fessler/code/index.html). 9 | [sino,Hk,hn,nn] = fbp_sino_filter(p,degtorad(Fan_sensor_spacing),... 10 | SDD, filter, 0); 11 | 12 | % Following functions are taken fron iradon function 13 | % Copyright 1993-2013 The MathWorks, Inc. 14 | % Design the filter 15 | len = size(p,1); 16 | H = designFilter(filter, len, 1); 17 | 18 | if strcmpi(filter, 'none') 19 | return; 20 | end 21 | 22 | p(length(H),1)=0; % Zero pad projections 23 | p = fft(p); % p holds fft of projections 24 | 25 | for i = 1:size(p,2) 26 | p(:,i) = p(:,i).*H.*0.5*... 27 | (Fan_sensor_spacing/... 28 | sin(Fan_sensor_spacing)^2); % frequency domain filtering 29 | end 30 | 31 | p = real(ifft(p)); % p is the filtered projections 32 | p(len+1:end,:) = []; % Truncate the filtered projections 33 | 34 | end 35 | -------------------------------------------------------------------------------- /designFilter.m: -------------------------------------------------------------------------------- 1 | function [filt,order] = designFilter(filter, len, d) 2 | 3 | % Returns the Fourier Transform of the filter which will be 4 | % used to filter the projections 5 | % 6 | % INPUT ARGS: filter - either the string specifying the filter 7 | % len - the length of the projections 8 | % d - the fraction of frequencies below the nyquist 9 | % which we want to pass 10 | % 11 | % OUTPUT ARGS: filt - the filter to use on the projections 12 | % Heinz, R. (2008). Implementation of a Cone-Beam Reconstruction Algorithm in Matlab. 13 | 14 | order = max(64,2^nextpow2(2*len)); 15 | if strcmpi(filter, 'none') 16 | filt = ones(1, order); 17 | return; 18 | end 19 | % First create a ramp filter - go up to the next highest 20 | % power of 2. 21 | filt = 2*( 0:(order/2) )./order; 22 | w = 2*pi*(0:size(filt,2)-1)/order; % frequency axis up to Nyquist 23 | switch filter 24 | case 'ram-lak' 25 | % Do nothing 26 | case 'shepp-logan' 27 | % be careful not to divide by 0: 28 | filt(2:end) = filt(2:end) .* (sin(w(2:end)/(2*d))./(w(2:end)/(2*d))); 29 | case 'cosine' 30 | filt(2:end) = filt(2:end) .* cos(w(2:end)/(2*d)); 31 | case 'hamming' 32 | filt(2:end) = filt(2:end) .* (.54 + .46 * cos(w(2:end)/d)); 33 | case 'hann' 34 | filt(2:end) = filt(2:end) .*(1+cos(w(2:end)./d)) / 2; 35 | otherwise 36 | eid = sprintf('Images:%s:invalidFilter',mfilename); 37 | msg = 'Invalid filter selected.'; 38 | error(eid,'%s',msg); 39 | end 40 | filt(w>pi*d) = 0; % Crop the frequency response 41 | filt = [filt' ; filt(end-1:-1:2)']; % Symmetry of the filter 42 | end -------------------------------------------------------------------------------- /fbp_sino_filter.m: -------------------------------------------------------------------------------- 1 | function [sino, Hk, hn, nn] = fbp_sino_filter(sino, ... 2 | ds, dsd, window, extra) 3 | 4 | [nb,na] = size(sino); 5 | 6 | npad = 2^ceil(log2(2*nb-1)); % padded size 7 | % printm('nb=%d npad=%d', nb, npad) 8 | 9 | sino = [sino; zeros(npad-nb,na)]; % padded sinogram 10 | 11 | %[hn,nn] = fbp_ramp(type, npad, ds, dsd); 12 | 13 | % ARC 14 | n=npad; 15 | nn = [-(n/2):(n/2-1)]'; 16 | h = zeros(size(nn)); 17 | h(nn==0) = 1 / (4 * ds^2); 18 | odd = mod(nn,2) == 1; 19 | h(odd) = -1 ./ (pi * dsd * sin(nn(odd) * ds / dsd)).^2; 20 | hn=h; 21 | 22 | % FLAT 23 | % nn = [-(n/2):(n/2-1)]'; 24 | % h = zeros(size(nn)); 25 | % h(n/2+1) = 1 / 4; 26 | % odd = mod(nn,2) == 1; 27 | % h(odd) = -1 ./ (pi * nn(odd)).^2; 28 | % hn = h / ds^2; 29 | 30 | 31 | Hk = real(fft(fftshift(hn))); 32 | 33 | Hk = Hk .* fbp2_window(npad, window); 34 | 35 | Hk = ds * Hk; % differential for discrete-space convolution vs integral 36 | 37 | % linear interpolation is like blur with a triangular response, 38 | % so we can compensate for this approximately in frequency domain 39 | % if decon1 40 | % Hk = Hk ./ fftshift(nufft_sinc(nn / npad).^2); 41 | % end 42 | 43 | sino = ifft( fft(sino, [], 1) .* repmat(Hk, [1 na]), [], 1, 'symmetric'); % apply filter 44 | 45 | % trick: possibly keep extra column(s) for zeros! 46 | sino = sino([1:(nb+extra)],:); 47 | sino([(nb+1):(nb+extra)],:) = 0; 48 | 49 | function y = nufft_sinc(x) 50 | iz = find(x == 0); % indices of zero arguments 51 | x(iz) = 1; 52 | y = sin(pi*x) ./ (pi*x); 53 | y(iz) = 1; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Computed-tomography Fan-beam FBP reconstruction 2 | 3 | This repository contains CT image reconstruction using Fan-beam Filtered back-projection. Appropriate weighting measures like Differential and Parker weighting can be applied. The reconstruction algorithm is applicable to short scan protocol as well. 4 | 5 | 6 | ## FILE CONTENTS 7 | 8 | * **FFBP_Weighted.m :** *Fan-beam Filtered back-projection function for two dimensional reconstruction.* 9 | 10 | * **SAMPLE_FFBP.m :** *Sample script to execute the Fan-beam FBP function with appropriate reconstruction parameters. This example script can be used to reconstruct the sample sinogram file " Sample_sinogram.sino ".* 11 | 12 | * **designFilter.m :** *Use it to design the filter with window of choice. Certain general windows are already incorporated like the shepp-logan, cosine, hann and hamming filter.* 13 | 14 | * **fbp2_window.m :** *Filter windows as used by Jeffrey A Fessler, "Michigan Image Reconstruction Toolbox,"* 15 | 16 | * **fbp_sino_filter.m :** *Filtering (Curved/ Arc detector) as used by Jeffrey A Fessler, "Michigan Image Reconstruction Toolbox,"* 17 | 18 | * **filterProjections.m :** *Filtering as in iradon function (Copyright 1993-2013 The MathWorks, Inc.).* 19 | 20 | ## References 21 | 22 | * Taguchi, K. Image Reconstruction Algorithms for X-Ray CT, 189–216. Medical Imaging Technology and Applications Troy Farncombe and Kris Iniewski CRC Press 2013. 23 | 24 | * D. L. Parker, Optimal short scan convolution reconstruction for fanbeam CT, Medical Physics, 25 | 9, 254–257, 1982. 26 | -------------------------------------------------------------------------------- /SAMPLE_FFBP.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % SAMPLE SCRIPT (FAN-BEAM FILTERED BACK-PROJECTION) % 3 | % % 4 | % INPUT PARAMETERS: % 5 | % Log: Sinogram input. % 6 | % start_angle: Starting angular position in degrees. % 7 | % SOD: Source Object Distance in mm. % 8 | % SDD: Source detector distance. % 9 | % Fan_angle: Fan beam opening angle in degrees. % 10 | % Norg: Original radial sample size. % 11 | % weighting: Sinogram weighting (Parker or Differential) % 12 | % OutputSize: Output size of image in number of pixels % 13 | % total_angle: Coverage angle of the source % 14 | % Filt: Filter choce (ramp, shepp-logan, cosine, % 15 | % hann, hanning etc.) % 16 | % % 17 | % OUTPUT: % 18 | % Reconstruction: Reconstruction output. % 19 | % Bp_RotationIncrement: Angular increments of projections. % 20 | % Fan_sensor_spacing: Detector Sensor angular spacing. % 21 | % % 22 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 23 | % AUTHOR: % 24 | % Kunal Kumar, % 25 | % Copyright, 2016 % 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | 28 | %% Read Sinogram 29 | 30 | clear 31 | clc 32 | fileid = fopen('Sample_sinogram.sino','rb'); 33 | proj = fread(fileid,[140,inf],'float32'); 34 | fclose(fileid); 35 | %proj=fliplr(proj); % Apply this as per requirement to flip the sinogram 36 | 37 | %% Reconstruction Parameters 38 | 39 | detector_rows = 1; 40 | SOD = 100; 41 | SDD = 200; 42 | Fan_angle = 28; 43 | start_angle = -14; 44 | total_angle = 180 + Fan_angle; 45 | OutputSize = 256; 46 | Filter = 'shepp-logan'; 47 | weighting = 'parker'; % Parker or differential weighting can be applied. 48 | Norg=size(proj,1); 49 | 50 | %% Projection data from logarithmic transformation of intensity ratio 51 | 52 | [Max_Intensity,Index] = max(proj(:,:)); 53 | X =log(max(ind2sub(size(proj),Max_Intensity))); 54 | Y=log(proj); 55 | Log=X-Y; 56 | 57 | % Log=proj; % For Pre-Transformed data 58 | 59 | %% FFBP Reconstruction 60 | 61 | [Reconstruction, Bp_RotationIncrement, Fan_sensor_spacing,Bp_spacing] = ... 62 | FFBP_Weighted(Log, start_angle, SOD, SDD, Fan_angle, Norg,... 63 | weighting, OutputSize, total_angle,Filter); 64 | Reconstruction(isnan(Reconstruction)) = 0; 65 | Reconstruction = Reconstruction*Fan_sensor_spacing*Bp_spacing; 66 | 67 | %% Write to file 68 | 69 | fileid = fopen('Reconstruction.raw','w+'); 70 | wrt = fwrite(fileid,Reconstruction,'float32'); 71 | fclose(fileid); 72 | -------------------------------------------------------------------------------- /fbp2_window.m: -------------------------------------------------------------------------------- 1 | function window = fbp2_window(n, window) 2 | %|function window = fbp2_window(n, window) 3 | %| compute an apodizing window of length n and fft shift it 4 | 5 | if nargin == 1 && streq(n, 'test'), fbp2_window_test, return, end 6 | if nargin < 2, help(mfilename), error(mfilename), end 7 | 8 | if ischar(window) 9 | if isempty(window) || streq(window, 'boxcar') || streq(window, 'ramp') 10 | window = ones(n,1); 11 | 12 | elseif streq(window, 'hamming,', 8) 13 | cut = sscanf(window, 'hamming,%g'); 14 | window = my_hamming(n, cut); 15 | 16 | elseif streq(window, 'shepp-logan') 17 | window = my_shepp(n, 1.0); 18 | 19 | % elseif streq(window, 'ramp') 20 | % window = my_ramp(n, 1.0); 21 | 22 | elseif streq(window, 'cosine') 23 | window = my_cosine(n, 1.0); 24 | 25 | elseif streq(window, 'hanning,', 8) 26 | cut = sscanf(window, 'hanning,%g'); 27 | window = my_hann(n, cut); 28 | 29 | elseif streq(window, 'hann') 30 | window = my_hann(n, 1.0); 31 | elseif streq(window, 'hann50') 32 | window = my_hann(n, 0.5); 33 | elseif streq(window, 'hann75') 34 | window = my_hann(n, 0.75); 35 | elseif streq(window, 'hann80') 36 | window = my_hann(n, 0.80); 37 | 38 | else 39 | fail('unknown window %s', window) 40 | end 41 | 42 | elseif length(window) ~= n 43 | error 'bad window length' 44 | end 45 | 46 | window = fftshift(window); 47 | 48 | function tf = streq(a,b,n) 49 | %function tf = streq(a, b [,n]) 50 | % return 1 if two strings are equal (optionally only up to 1st n chars) 51 | if nargin == 1 && strcmp(a, 'test'), streq_test, return, end 52 | if nargin < 2, help(mfilename), error(mfilename), end 53 | 54 | if ~ischar(a) | ~ischar(b), tf = 0; return, end 55 | if nargin == 2 56 | tf = strcmp(a,b); 57 | elseif nargin == 3 58 | tf = strncmp(a,b,n); 59 | else 60 | error(mfilename) 61 | end 62 | 63 | % my_hann() 64 | function window = my_hann(n, cutoff) 65 | w = round(cutoff * n); 66 | ii = [0:n-1]'-n/2; 67 | window = 0.5 * (1 + cos(2*pi*ii/w)) .* (abs(ii) < w/2); 68 | 69 | function window = my_shepp(n, cutoff) 70 | % sh = round(cutoff * n); 71 | % ii = [0:order-1]'-order/2; 72 | % window = (sin(2*pi*ii/(2*sh))./(2*pi*ii/(2*sh))) .* (abs(ii) < sh/2); 73 | order = n; 74 | fi = 2*( 0:(order/2) )./order; 75 | w = 2*pi*(0:size(fi,2)-1)/order; 76 | shp=(sin(w(2:end)/(2*cutoff))./(w(2:end)/(2*cutoff))); 77 | % window = [shp(end-1:-1:2)' ;shp']; 78 | window = [shp(end:-1:1)' ;shp']; 79 | 80 | % function window = my_ramp(n, cutoff) 81 | % % sh = round(cutoff * n); 82 | % % ii = [0:order-1]'-order/2; 83 | % % window = (sin(2*pi*ii/(2*sh))./(2*pi*ii/(2*sh))) .* (abs(ii) < sh/2); 84 | % order = n; 85 | % fi = 2*( 0:(order/2) )./order; 86 | % w = 2*pi*(0:size(fi,2)-1)/order; 87 | % window = [w(end:-1:1)' ;w']; 88 | 89 | function window = my_cosine(n, cutoff) 90 | order = n; 91 | fi = 2*( 0:(order/2) )./order; 92 | w = 2*pi*(0:size(fi,2)-1)/order; 93 | shp= cos(w(2:end)/(2*cutoff)); 94 | % window = [shp(end-1:-1:2)' ;shp']; 95 | window = [shp(end:-1:1)' ;shp']; 96 | 97 | % my_hamming() 98 | function window = my_hamming(n, cutoff) 99 | w = round(cutoff * n); 100 | ii = [0:n-1]'-n/2; 101 | window = (0.54 + 0.46 * cos(2*pi*ii/w)) .* (abs(ii) < w/2); 102 | -------------------------------------------------------------------------------- /FFBP_Weighted.m: -------------------------------------------------------------------------------- 1 | function [Reconstruction, Bp_RotationIncrement, Fan_sensor_spacing,Bp_spacing] = ... 2 | FFBP_Weighted(Log, start_angle, SOD, SDD, Fan_angle, Norg,... 3 | weighting,OutputSize,total_angle,Filt) 4 | 5 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 6 | % FAN-BEAM FILTERED BACK-PROJECTION (SHORT SCAN) % 7 | % % 8 | % INPUT: % 9 | % Log: Sinogram input. % 10 | % start_angle: Starting angular position in degrees. % 11 | % SOD: Source Object Distance in mm. % 12 | % SDD: Source detector distance. % 13 | % Fan_angle: Fan beam opening angle in degrees. % 14 | % Norg: Original radial sample size. % 15 | % weighting: Sinogram weighting (Parker or Differential) % 16 | % OutputSize: Output size of image in number of pixels % 17 | % total_angle: Coverage angle of the source % 18 | % Filt: Filter choce (ramp, shepp-logan, cosine, % 19 | % hann, hanning etc.) % 20 | % % 21 | % OUTPUT: % 22 | % Reconstruction: Reconstruction output. % 23 | % Bp_RotationIncrement: Angular increments of projections. % 24 | % Fan_sensor_spacing: Detector Sensor angular spacing. % 25 | % % 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | % AUTHOR: % 28 | % Kunal Kumar, % 29 | % Copyright, 2016 % 30 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 31 | 32 | Log = Log(:,end:-1:1); 33 | out_x = OutputSize; 34 | out_y = size(Log,2); 35 | [in_x,in_y] = size(Log); 36 | SIZE = padarray(Log, [floor((out_x - in_x)/2) floor((out_y - in_y)/2)], 0, 'post'); 37 | Log = padarray(SIZE, [ceil((out_x - in_x)/2) ceil((out_y - in_y)/2)], 0, 'pre'); 38 | 39 | [Npix,Nproj] = size(Log); 40 | Fan_sensor_spacing = (degtorad(Fan_angle)/Norg); 41 | Bp_RotationIncrement = ((degtorad(total_angle))/Nproj); 42 | thetaI = degtorad(0 + start_angle); 43 | thetaF = degtorad(total_angle + start_angle); 44 | Bp_angles = linspace(thetaI, thetaF, Nproj); 45 | Bp_park_ang = linspace(0, degtorad(total_angle), Nproj); 46 | Bp_spacing = abs(Bp_angles(1) - Bp_angles(2)); 47 | Det_size = Norg; 48 | Det_angles = Fan_sensor_spacing*linspace(-Det_size/2, Det_size/2, Norg)'; 49 | dif = diff(Det_angles(1:2)); 50 | 51 | if (mod(Npix - Norg, 2) == 0) 52 | N = (Npix - Norg) / 2; 53 | dt_in = (fliplr(Det_angles(1) - dif + (0:N-1)*(-dif)))'; 54 | dt_fi = (Det_angles(end) + dif + (0:N-1)*(dif))'; 55 | else 56 | N1 = floor((Npix-Norg)/2); 57 | N2 = ceil((Npix - Norg)/2); 58 | dt_in = (fliplr(Det_angles(1) - dif + (0:N1-1)*(-dif)))'; 59 | dt_fi = (Det_angles(end) + dif + (0:N2-1)*(dif))'; 60 | end 61 | Det_angles = [dt_in; Det_angles; dt_fi;]; 62 | 63 | pad(1,1:Nproj) = 1; 64 | DTA = Det_angles*pad; 65 | 66 | if strcmp(weighting,'differential') 67 | 68 | % Differential weighting 69 | mat = zeros(Npix,Nproj); 70 | for i = 1:Nproj 71 | for j = 1:Npix 72 | if Bp_angles(i) >= 0 && Bp_angles(i) < 2*(degtorad(Fan_angle/2) + Det_angles(j)) 73 | mat(j,i) = Bp_angles(i)/(2*(degtorad(Fan_angle/2) + Det_angles(j))); 74 | elseif Bp_angles(i) >= (2*(Det_angles(j) + degtorad(Fan_angle/2))) && Bp_angles(i) < (pi + 2*Det_angles(j)) 75 | mat(j,i) = 1; 76 | elseif Bp_angles(i) >= (pi + 2*Det_angles(j)) && Bp_angles(i) <= (pi+2*degtorad(Fan_angle/2)) 77 | mat(j,i) = (pi + 2*degtorad(Fan_angle/2) - Bp_angles(i))/(2*(degtorad(Fan_angle/2) - Det_angles(j))); 78 | else 79 | mat(j,i) = 0; 80 | end 81 | end 82 | end 83 | mat=3.*mat.^2-2.*mat.^2; 84 | 85 | elseif strcmp(weighting,'parker') 86 | 87 | % Parker weighting 88 | mat = zeros(Npix,Nproj); 89 | for i = 1:Nproj 90 | for j = 1:Npix 91 | if Bp_park_ang(i) >= 0 && Bp_park_ang(i) <= 2*(degtorad(Fan_angle/2) - Det_angles(j)) 92 | mat(j,i) = (sin((pi/4)*(Bp_park_ang(i)/((degtorad(Fan_angle/2) - Det_angles(j))))))^2; 93 | elseif Bp_park_ang(i) >= 2*(degtorad(Fan_angle/2) - Det_angles(j)) && Bp_park_ang(i) <= (pi - 2*Det_angles(j)) 94 | mat(j,i) = 1; 95 | elseif Bp_park_ang(i) >= (pi - 2*Det_angles(j)) && Bp_park_ang(i) <= (pi + 2*degtorad(Fan_angle/2)) 96 | mat(j,i) = (sin((pi/4)*((pi + 2*degtorad(Fan_angle/2) - Bp_park_ang(i))/((degtorad(Fan_angle/2) + Det_angles(j))))))^2; 97 | else 98 | mat(j,i) = 0; 99 | end 100 | end 101 | end 102 | 103 | else 104 | disp('Incorrect weighting choice, choose either parker or differential') 105 | 106 | end 107 | 108 | mat(isnan(mat)) = 0; 109 | mat = flipud(mat); 110 | 111 | % Gain correction and filtering 112 | [N_req,Filter] = filterProjections(Log, Filt, DTA, SOD, SDD, Fan_sensor_spacing); 113 | Filter = Filter.*mat; 114 | 115 | Reconstruction(Npix,Npix) = 0; 116 | RC = (1 + Reconstruction(:,1))*linspace(1, Nproj, Nproj); 117 | MRC = 1 + Reconstruction; 118 | srad = sin(degtorad(Fan_angle/2))*SOD; 119 | Reg_DFOV = linspace(-srad,srad,Npix); 120 | HDFOV = zeros(length(Reg_DFOV),length(Reg_DFOV)); 121 | VDFOV = HDFOV; 122 | 123 | for i = 1:length(Reg_DFOV) 124 | HDFOV(:,i) = Reg_DFOV(i); 125 | VDFOV(:,i) = Reg_DFOV; 126 | end 127 | 128 | for n = 1:Nproj 129 | [H,V] = pol2cart(Bp_angles(n),SOD); 130 | Hr = V - HDFOV; 131 | Vr = H - VDFOV; 132 | Interp = (Bp_angles(n) - (pi/2 - atan2(Vr,Hr))); 133 | Interp(Interp < -pi) = rem(Interp(Interp < -pi) + 2*pi, 2*pi); 134 | Interp(Interp >= pi) = rem(Interp(Interp >= pi) - 2*pi, 2*pi); 135 | Interpolate = interp2(RC, DTA, Filter, MRC*n,Interp); 136 | Reconstruction = Reconstruction + Interpolate./((Hr).^2 + (Vr).^2); 137 | figure(1) 138 | axis image 139 | colormap(gray(256)) 140 | imagesc(Reconstruction) 141 | drawnow 142 | end 143 | 144 | end 145 | --------------------------------------------------------------------------------