From 9ade333a11e31e3a3eaf8b7d735e353ec9f4f60e Mon Sep 17 00:00:00 2001 From: Malena Sabate Landman Date: Thu, 18 Aug 2022 18:34:09 +0100 Subject: [PATCH 01/42] Added LSQR and updated example --- MATLAB/Algorithms/LSQR.m | 274 ++++++++++++++++++++++++++++++++ MATLAB/Demos/d08_Algorithms03.m | 11 +- 2 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 MATLAB/Algorithms/LSQR.m diff --git a/MATLAB/Algorithms/LSQR.m b/MATLAB/Algorithms/LSQR.m new file mode 100644 index 00000000..31f511d3 --- /dev/null +++ b/MATLAB/Algorithms/LSQR.m @@ -0,0 +1,274 @@ +function [x,errorL2,qualMeasOut]= LSQR(proj,geo,angles,niter,varargin) + +% LSQR_CBCT solves the CBCT problem using the conjugate gradient least +% squares. This is mathematically equivalent to CGLS_CBCT. +% +% LSQR_CBCT(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% using the projection data PROJ taken over ALPHA angles, corresponding +% to the geometry descrived in GEO, using NITER iterations. +% +% LSQR_CBCT(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% possible options in OPT are: +% +% +% 'Init' Describes diferent initialization techniques. +% * 'none' : Initializes the image to zeros (default) +% * 'FDK' : intializes image to FDK reconstrucition +% * 'multigrid': Initializes image by solving the problem in +% small scale and increasing it when relative +% convergence is reached. +% * 'image' : Initialization using a user specified +% image. Not recomended unless you really +% know what you are doing. +% 'InitImg' an image for the 'image' initialization. Avoid. +% 'redundancy_weighting': true or false. Default is true. Applies data +% redundancy weighting to projections in the update step +% (relevant for offset detector geometry) +%-------------------------------------------------------------------------- +%-------------------------------------------------------------------------- +% This file is part of the TIGRE Toolbox +% +% Copyright (c) 2015, University of Bath and +% CERN-European Organization for Nuclear Research +% All rights reserved. +% +% License: Open Source under BSD. +% See the full license at +% https://github.com/CERN/TIGRE/blob/master/LICENSE +% +% Contact: tigre.toolbox@gmail.com +% Codes: https://github.com/CERN/TIGRE/ +% Coded by: Ander Biguri +%-------------------------------------------------------------------------- + +%% + +% msl: I assume this x is either an array of 0 of the appropriate +% dimensions or an initial guess + +[verbose,x,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); + +% msl: no idea of what this is. Should I check? +measurequality=~isempty(QualMeasOpts); +qualMeasOut=zeros(length(QualMeasOpts),niter); +if redundancy_weights + % Data redundancy weighting, W_r implemented using Wang weighting + % reference: https://iopscience.iop.org/article/10.1088/1361-6560/ac16bc + + num_frames = size(proj,3); + W_r = redundancy_weighting(geo); + W_r = repmat(W_r,[1,1,num_frames]); + % disp('Size of redundancy weighting matrix'); + % disp(size(W_r)); +end + +% Paige and Saunders //doi.org/10.1145/355984.355989 + +% Enumeration as given in the paper for 'Algorithm LSQR' +% (1) Initialize +u=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); +normr = norm(u(:),2); +u = u/normr; +% msl: why is r not notmalized in CGLS? Check. + +beta = normr; +phibar = beta; + +% msl: I have not checked 'redundancy_weights', do they have to do with +% mismatched transpose? +if redundancy_weights + v=Atb(W_r .* u,geo,angles,'matched','gpuids',gpuids); +else + v=Atb(u,geo,angles,'matched','gpuids',gpuids); +end + +alpha = norm(v(:),2); % msl: do we want to check if it is 0? +v = v/alpha; +rhobar = alpha; +w = v; + +normAtr = beta*alpha; % Do we want this? ||A^T r_k|| +% msl: do we want to check for convergence? In well posed problems it would +% make sense, not sure now. + +errorL2=zeros(1,niter); % msl: is this error the residual norm ? + +% (2) Start iterations +for ii=1:niter + x0 = x; + if (ii==1 && verbose);tic;end + + % (3)(a) + u = Ax(v,geo,angles,'Siddon','gpuids',gpuids) - alpha*u; + beta = norm(u(:),2); + u = u / beta; + + % (3)(b) + if redundancy_weights % msl: Is this right here? + v = Atb(W_r .* u,geo,angles,'matched','gpuids',gpuids) - beta*v; + else + v = Atb(u,geo,angles,'matched','gpuids',gpuids) - beta*v; + end + alpha = norm(v(:),2); + v = v / alpha; + + % (4)(a-g) + rho = sqrt(rhobar^2 + beta^2); + c = rhobar / rho; + s = beta / rho; + theta = s * alpha; + rhobar = - c * alpha; % msl: I am overwriting a matlab function, maybe change name + phi = c * phibar; + phibar = s * phibar; + + % (5) Update x, w + x = x + (phi / rho) * w; + w = v - (theta / rho) * w; + + % Update estimated quantities of interest. + % msl: We can also compute cheaply estimates of ||x||, ||A||, cond(A) + normr = normr*abs(s); % ||r_k|| = ||b - A x_k|| + % Only exact if we do not have orth. loss + normAtr = phibar * alpha * abs(c); % msl: Do we want this? ||A^T r_k|| + errorL2(ii)=normr; + + % (6) Test for convergence. + % msl: I still need to implement this. + % msl: There are suggestions on the original paper. Let's talk about it! + + if measurequality % msl: what is this?? + qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); + end + + if ii>1 && errorL2(ii)>errorL2(ii-1) % msl: not checked + % OUT! + x=x-alpha*v; + if verbose + disp(['CGLS stoped in iteration N', num2str(ii),' due to divergence.']) + end + return; + end + + if (ii==1 && verbose) + expected_time=toc*niter; + disp('LSQR'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end +end + +end + + +%% parse inputs' +function [verbose,x,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','redundancy_weighting'}; +defaults=ones(length(opts),1); + +% Check inputs +nVarargs = length(argin); +if mod(nVarargs,2) + error('TIGRE:CGLS:InvalidInput','Invalid number of inputs') +end + +% check if option has been passed as input +for ii=1:2:nVarargs + ind=find(ismember(opts,lower(argin{ii}))); + if ~isempty(ind) + defaults(ind)=0; + else + error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + end +end + +for ii=1:length(opts) + opt=opts{ii}; + default=defaults(ii); + % if one option isnot default, then extranc value from input + if default==0 + ind=double.empty(0,1);jj=1; + while isempty(ind) + ind=find(isequal(opt,lower(argin{jj}))); + jj=jj+1; + end + if isempty(ind) + error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + end + val=argin{jj}; + end + + switch opt + case 'init' + x=[]; + if default || strcmp(val,'none') + x=zeros(geo.nVoxel','single'); + continue; + end + if strcmp(val,'FDK') + x=FDK(proj,geo,angles); + continue; + end + if strcmp(val,'multigrid') + x=init_multigrid(proj,geo,angles); + continue; + end + if strcmp(val,'image') + initwithimage=1; + continue; + end + if isempty(x) + error('TIGRE:CGLS:InvalidInput','Invalid Init option') + end + % % % % % % % ERROR + case 'initimg' + if default + continue; + end + if exist('initwithimage','var') + if isequal(size(val),geo.nVoxel') + x=single(val); + else + error('TIGRE:CGLS:InvalidInput','Invalid image for initialization'); + end + end + % ========================================================================= + case 'qualmeas' + if default + QualMeasOpts={}; + else + if iscellstr(val) + QualMeasOpts=val; + else + error('TIGRE:CGLS:InvalidInput','Invalid quality measurement parameters'); + end + end + case 'verbose' + if default + verbose=1; + else + verbose=val; + end + if ~is2014bOrNewer + warning('TIGRE:Verbose mode not available for older versions than MATLAB R2014b'); + verbose=false; + end + case 'gpuids' + if default + gpuids = GpuIds(); + else + gpuids = val; + end + case 'redundancy_weighting' + if default + redundancy_weights = true; + else + redundancy_weights = val; + end + otherwise + error('TIGRE:CGLS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); + end +end + + +end \ No newline at end of file diff --git a/MATLAB/Demos/d08_Algorithms03.m b/MATLAB/Demos/d08_Algorithms03.m index 027a5cb1..5c67dcfe 100644 --- a/MATLAB/Demos/d08_Algorithms03.m +++ b/MATLAB/Demos/d08_Algorithms03.m @@ -63,6 +63,8 @@ % use CGLS [imgCGLS, errL2CGLS]=CGLS(noise_projections,geo,angles,60); +% use LSQR +[imgLSQR, errL2LSQR]=LSQR(noise_projections,geo,angles,60); % SIRT for comparison. [imgSIRT,errL2SIRT]=SIRT(noise_projections,geo,angles,60); @@ -71,11 +73,12 @@ % We can see that CGLS gets to the same L2 error in less amount of % iterations. -plot([errL2SIRT;[errL2CGLS nan(1,length(errL2SIRT)-length(errL2CGLS))]]'); +% +plot([errL2SIRT;[errL2CGLS nan(1,length(errL2SIRT)-length(errL2CGLS))];[errL2LSQR nan(1,length(errL2SIRT)-length(errL2LSQR))]]'); title('L2 error') -legend('SIRT','CGLS') +legend('SIRT','CGLS','LSQR') % plot images -plotImg([imgCGLS imgSIRT],'Dim','Z','Step',2) +plotImg([imgLSQR imgCGLS imgSIRT],'Dim','Z','Step',2) %plot errors -plotImg(abs([head-imgCGLS head-imgSIRT]),'Dim','Z','Slice',64) +plotImg(abs([head-imgLSQR head-imgCGLS head-imgSIRT]),'Dim','Z','Slice',64) From a8099403b4e7368c21853c260100a044f89b39ba Mon Sep 17 00:00:00 2001 From: Malena Sabate Landman Date: Thu, 18 Aug 2022 19:15:36 +0100 Subject: [PATCH 02/42] Update LSQR small changes in LSQR --- MATLAB/Algorithms/LSQR.m | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/MATLAB/Algorithms/LSQR.m b/MATLAB/Algorithms/LSQR.m index 31f511d3..ff387887 100644 --- a/MATLAB/Algorithms/LSQR.m +++ b/MATLAB/Algorithms/LSQR.m @@ -1,7 +1,7 @@ function [x,errorL2,qualMeasOut]= LSQR(proj,geo,angles,niter,varargin) -% LSQR_CBCT solves the CBCT problem using the conjugate gradient least -% squares. This is mathematically equivalent to CGLS_CBCT. +% LSQR_CBCT solves the CBCT problem using LSQR. +% This is mathematically equivalent to CGLS_CBCT. % % LSQR_CBCT(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem % using the projection data PROJ taken over ALPHA angles, corresponding @@ -69,7 +69,6 @@ u=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); normr = norm(u(:),2); u = u/normr; -% msl: why is r not notmalized in CGLS? Check. beta = normr; phibar = beta; From b5b9d38fb727a860afebbfeea0dd553b60a12dd2 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Fri, 19 Aug 2022 11:59:13 +0100 Subject: [PATCH 03/42] Tidy code a bit, remove unnecessary features from CGLS/LSQR --- MATLAB/Algorithms/CGLS.m | 41 +++++----------------- MATLAB/Algorithms/LSQR.m | 76 ++++++++++++---------------------------- 2 files changed, 32 insertions(+), 85 deletions(-) diff --git a/MATLAB/Algorithms/CGLS.m b/MATLAB/Algorithms/CGLS.m index 30162b99..f5d8dca1 100644 --- a/MATLAB/Algorithms/CGLS.m +++ b/MATLAB/Algorithms/CGLS.m @@ -1,12 +1,12 @@ function [x,errorL2,qualMeasOut]= CGLS(proj,geo,angles,niter,varargin) -% CGLS_CBCT solves the CBCT problem using the conjugate gradient least +% CGLS solves the CBCT problem using the conjugate gradient least % squares % -% CGLS_CBCT(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem -% using the projection data PROJ taken over ALPHA angles, corresponding +% CGLS(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% using the projection data PROJ taken over ANGLES angles, corresponding % to the geometry descrived in GEO, using NITER iterations. % -% CGLS_CBCT(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% CGLS(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The % possible options in OPT are: % % @@ -20,9 +20,6 @@ % image. Not recomended unless you really % know what you are doing. % 'InitImg' an image for the 'image' initialization. Avoid. -% 'redundancy_weighting': true or false. Default is true. Applies data -% redundancy weighting to projections in the update step -% (relevant for offset detector geometry) %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -42,21 +39,11 @@ %% -[verbose,x,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); +[verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); +measurequality=~isempty(QualMeasOpts); qualMeasOut=zeros(length(QualMeasOpts),niter); -if redundancy_weights - % Data redundancy weighting, W_r implemented using Wang weighting - % reference: https://iopscience.iop.org/article/10.1088/1361-6560/ac16bc - - num_frames = size(proj,3); - W_r = redundancy_weighting(geo); - W_r = repmat(W_r,[1,1,num_frames]); - % disp('Size of redundancy weighting matrix'); - % disp(size(W_r)); -end % //doi: 10.1088/0031-9155/56/13/004 @@ -94,12 +81,8 @@ end % If step is adecuatem, then continue withg CGLS r=r-alpha*q; + s=Atb(r,geo,angles,'matched','gpuids',gpuids); - if redundancy_weights - s=Atb(W_r .* r,geo,angles,'matched','gpuids',gpuids); - else - s=Atb(r,geo,angles,'matched','gpuids',gpuids); - end gamma1=norm(s(:),2)^2; beta=gamma1/gamma; gamma=gamma1; @@ -119,8 +102,8 @@ %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids','redundancy_weighting'}; +function [verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids'}; defaults=ones(length(opts),1); % Check inputs @@ -216,12 +199,6 @@ else gpuids = val; end - case 'redundancy_weighting' - if default - redundancy_weights = true; - else - redundancy_weights = val; - end otherwise error('TIGRE:CGLS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); end diff --git a/MATLAB/Algorithms/LSQR.m b/MATLAB/Algorithms/LSQR.m index ff387887..5e299abb 100644 --- a/MATLAB/Algorithms/LSQR.m +++ b/MATLAB/Algorithms/LSQR.m @@ -1,13 +1,13 @@ function [x,errorL2,qualMeasOut]= LSQR(proj,geo,angles,niter,varargin) -% LSQR_CBCT solves the CBCT problem using LSQR. -% This is mathematically equivalent to CGLS_CBCT. +% LSQR solves the CBCT problem using LSQR. +% This is mathematically equivalent to CGLS. % -% LSQR_CBCT(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem -% using the projection data PROJ taken over ALPHA angles, corresponding +% LSQR(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% using the projection data PROJ taken over ANGLES angles, corresponding % to the geometry descrived in GEO, using NITER iterations. % -% LSQR_CBCT(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% LSQR(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The % possible options in OPT are: % % @@ -21,9 +21,6 @@ % image. Not recomended unless you really % know what you are doing. % 'InitImg' an image for the 'image' initialization. Avoid. -% 'redundancy_weighting': true or false. Default is true. Applies data -% redundancy weighting to projections in the update step -% (relevant for offset detector geometry) %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -38,29 +35,17 @@ % % Contact: tigre.toolbox@gmail.com % Codes: https://github.com/CERN/TIGRE/ -% Coded by: Ander Biguri +% Coded by: Malena Sabate Landman, Ander Biguri %-------------------------------------------------------------------------- %% -% msl: I assume this x is either an array of 0 of the appropriate -% dimensions or an initial guess - -[verbose,x,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); +[verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); % msl: no idea of what this is. Should I check? measurequality=~isempty(QualMeasOpts); qualMeasOut=zeros(length(QualMeasOpts),niter); -if redundancy_weights - % Data redundancy weighting, W_r implemented using Wang weighting - % reference: https://iopscience.iop.org/article/10.1088/1361-6560/ac16bc - - num_frames = size(proj,3); - W_r = redundancy_weighting(geo); - W_r = repmat(W_r,[1,1,num_frames]); - % disp('Size of redundancy weighting matrix'); - % disp(size(W_r)); -end + % Paige and Saunders //doi.org/10.1145/355984.355989 @@ -73,13 +58,8 @@ beta = normr; phibar = beta; -% msl: I have not checked 'redundancy_weights', do they have to do with -% mismatched transpose? -if redundancy_weights - v=Atb(W_r .* u,geo,angles,'matched','gpuids',gpuids); -else - v=Atb(u,geo,angles,'matched','gpuids',gpuids); -end +v=Atb(u,geo,angles,'matched','gpuids',gpuids); + alpha = norm(v(:),2); % msl: do we want to check if it is 0? v = v/alpha; @@ -103,11 +83,7 @@ u = u / beta; % (3)(b) - if redundancy_weights % msl: Is this right here? - v = Atb(W_r .* u,geo,angles,'matched','gpuids',gpuids) - beta*v; - else - v = Atb(u,geo,angles,'matched','gpuids',gpuids) - beta*v; - end + v = Atb(u,geo,angles,'matched','gpuids',gpuids) - beta*v; alpha = norm(v(:),2); v = v / alpha; @@ -116,7 +92,7 @@ c = rhobar / rho; s = beta / rho; theta = s * alpha; - rhobar = - c * alpha; % msl: I am overwriting a matlab function, maybe change name + rhobar = - c * alpha; phi = c * phibar; phibar = s * phibar; @@ -135,7 +111,7 @@ % msl: I still need to implement this. % msl: There are suggestions on the original paper. Let's talk about it! - if measurequality % msl: what is this?? + if measurequality qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); end @@ -161,14 +137,14 @@ %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids','redundancy_weighting'}; +function [verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); if mod(nVarargs,2) - error('TIGRE:CGLS:InvalidInput','Invalid number of inputs') + error('TIGRE:LSQR:InvalidInput','Invalid number of inputs') end % check if option has been passed as input @@ -177,7 +153,7 @@ if ~isempty(ind) defaults(ind)=0; else - error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); end end @@ -192,7 +168,7 @@ jj=jj+1; end if isempty(ind) - error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); end val=argin{jj}; end @@ -217,7 +193,7 @@ continue; end if isempty(x) - error('TIGRE:CGLS:InvalidInput','Invalid Init option') + error('TIGRE:LSQR:InvalidInput','Invalid Init option') end % % % % % % % ERROR case 'initimg' @@ -228,7 +204,7 @@ if isequal(size(val),geo.nVoxel') x=single(val); else - error('TIGRE:CGLS:InvalidInput','Invalid image for initialization'); + error('TIGRE:LSQR:InvalidInput','Invalid image for initialization'); end end % ========================================================================= @@ -239,7 +215,7 @@ if iscellstr(val) QualMeasOpts=val; else - error('TIGRE:CGLS:InvalidInput','Invalid quality measurement parameters'); + error('TIGRE:LSQR:InvalidInput','Invalid quality measurement parameters'); end end case 'verbose' @@ -249,7 +225,7 @@ verbose=val; end if ~is2014bOrNewer - warning('TIGRE:Verbose mode not available for older versions than MATLAB R2014b'); + warning('TIGRE:LSQR:Verbose mode not available for older versions than MATLAB R2014b'); verbose=false; end case 'gpuids' @@ -258,14 +234,8 @@ else gpuids = val; end - case 'redundancy_weighting' - if default - redundancy_weights = true; - else - redundancy_weights = val; - end otherwise - error('TIGRE:CGLS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); + error('TIGRE:LSQR:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); end end From 14535dd151bcac6e11da6919a6d0e7b26ee9ea15 Mon Sep 17 00:00:00 2001 From: Malena Sabate Landman Date: Mon, 5 Sep 2022 16:33:27 +0100 Subject: [PATCH 04/42] finished removing unnecessary features --- MATLAB/Algorithms/CGLS.m | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/MATLAB/Algorithms/CGLS.m b/MATLAB/Algorithms/CGLS.m index f5d8dca1..63336fc2 100644 --- a/MATLAB/Algorithms/CGLS.m +++ b/MATLAB/Algorithms/CGLS.m @@ -48,11 +48,9 @@ % //doi: 10.1088/0031-9155/56/13/004 r=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); -if redundancy_weights - p=Atb(W_r .* r,geo,angles,'matched','gpuids',gpuids); -else - p=Atb(r,geo,angles,'matched','gpuids',gpuids); -end + +p=Atb(r,geo,angles,'matched','gpuids',gpuids); + gamma=norm(p(:),2)^2; errorL2=zeros(1,niter); From 72771e098ae95f70c34be5b260603732b867f80a Mon Sep 17 00:00:00 2001 From: Malena Sabate Landman Date: Fri, 16 Sep 2022 19:59:40 +0100 Subject: [PATCH 05/42] Add some new codes First version, needs polishing! --- MATLAB/Algorithms/IRN_TV_CGLS.m | 325 ++++++++++++++++++++++++++++++++ MATLAB/Algorithms/LSMR.m | 297 +++++++++++++++++++++++++++++ MATLAB/Algorithms/LSMR_lambda.m | 316 +++++++++++++++++++++++++++++++ 3 files changed, 938 insertions(+) create mode 100644 MATLAB/Algorithms/IRN_TV_CGLS.m create mode 100644 MATLAB/Algorithms/LSMR.m create mode 100644 MATLAB/Algorithms/LSMR_lambda.m diff --git a/MATLAB/Algorithms/IRN_TV_CGLS.m b/MATLAB/Algorithms/IRN_TV_CGLS.m new file mode 100644 index 00000000..c495228e --- /dev/null +++ b/MATLAB/Algorithms/IRN_TV_CGLS.m @@ -0,0 +1,325 @@ +function [x_out,errorL2,qualMeasOut]= IRN_TV_CGLS(proj,geo,angles,niter,varargin) +% CGLS solves the CBCT problem using the conjugate gradient least +% squares +% +% CGLS(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% using the projection data PROJ taken over ANGLES angles, corresponding +% to the geometry descrived in GEO, using NITER iterations. +% +% CGLS(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% possible options in OPT are: +% +% +% 'Init' Describes diferent initialization techniques. +% * 'none' : Initializes the image to zeros (default) +% * 'FDK' : intializes image to FDK reconstrucition +% * 'multigrid': Initializes image by solving the problem in +% small scale and increasing it when relative +% convergence is reached. +% * 'image' : Initialization using a user specified +% image. Not recomended unless you really +% know what you are doing. +% 'InitImg' an image for the 'image' initialization. Avoid. +%-------------------------------------------------------------------------- +%-------------------------------------------------------------------------- +% This file is part of the TIGRE Toolbox +% +% Copyright (c) 2015, University of Bath and +% CERN-European Organization for Nuclear Research +% All rights reserved. +% +% License: Open Source under BSD. +% See the full license at +% https://github.com/CERN/TIGRE/blob/master/LICENSE +% +% Contact: tigre.toolbox@gmail.com +% Codes: https://github.com/CERN/TIGRE/ +% Coded by: Ander Biguri +%-------------------------------------------------------------------------- + +%% + +% % An Iteratively Reweighted Norm Algorithm for Total Variation Regularization +% % Paul Rodriguez and Brendt Wohlberg +% % 10.1109/ACSSC.2006.354879 +% +% errorL2 = []; +% for i = 1:10 % Needs to change +% % Update weight +% Build L = W * D as an efficient handle. Think about this. +% +% % Call CGLS (Maybe try with LSQR just for fun) +% [x ,errorL2_i,qualMeasOut] = CGLS(proj,geo,angles,niter,varargin); +% +% errorL2 = [errorL2, errorL2_i]; +% % Malena: not sure what qualMeasOut is, so I do not know if it should be stored or +% % updated after every inner cycle +% end + +% % Start coding with an L just to test dimensions +% +% sizeD3_1 = (geo.nVoxel(1)-1)*geo.nVoxel(1)*geo.nVoxel(1) ... +% + geo.nVoxel(1)*(geo.nVoxel(1)-1)*geo.nVoxel(1) ... +% + geo.nVoxel(1)*geo.nVoxel(1)*(geo.nVoxel(1)-1); +% sizeD3_2 = geo.nVoxel(1)*geo.nVoxel(1)*geo.nVoxel(1); + +lambda = 10; %% ||Ax-b|| + lambda R(x) + +[verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); + +niter_outer = 15; + +measurequality=~isempty(QualMeasOpts); +qualMeasOut=zeros(length(QualMeasOpts),niter*niter_outer); +errorL2=zeros(1,niter*niter_outer); + +for iii = 1:niter_outer + + % weights are N1 x N2 x N3 + W = build_weights (x); + + % Malena: We could avoid re-doing this, saving the initialisation + % Just remember that we need cold restarts + [verbose,x,QualMeasOpts,gpuids] = parse_inputs(proj,geo,angles,varargin); + + prox_aux_1 = Ax(x,geo,angles,'Siddon','gpuids',gpuids); + prox_aux_2 = Lx(W, x); + prox_aux_2 = cellfun(@(x) x*(sqrt(lambda)),prox_aux_2,'un',0); + + r_aux_1 = proj - prox_aux_1; + r_aux_2 = cellfun(@(x) x*(-1),prox_aux_2,'un',0); + + % Malena: changed the format, r_aux_2 is 3 + % r = cat(3,r_aux_1, r_aux_2); % Malena: size guide, erase later, N x N x (100 + N-1) + + p_aux_1 = Atb(r_aux_1 ,geo,angles,'matched','gpuids',gpuids); + p_aux_2 = sqrt(lambda)*Ltx (W, r_aux_2); + p = p_aux_1 + p_aux_2; + + gamma=norm(p(:),2)^2; + + for ii=1:niter + x0 = x; + if (ii==1 && verbose);tic;end + + q_aux_1 = Ax(p ,geo,angles,'Siddon','gpuids',gpuids); + q_aux_2 = Lx (W, p); + q_aux_2 = cellfun(@(x) x*sqrt(lambda),q_aux_2,'un',0); + + % q = cat(3, q_aux_1, q_aux_2{1},q_aux_2{2},q_aux_2{3}); % Probably never need to actually do this + % alpha=gamma/norm(q(:),2)^2; + alpha=gamma/(norm(q_aux_1(:))^2 + norm(q_aux_2{1}(:))^2 + norm(q_aux_2{2}(:))^2 + norm(q_aux_2{3}(:))^2); + + + x=x+alpha*p; + x_out{iii}=x; + aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); % expensive, is there any way to check this better? + % residual norm or the original least squares (not Tikhonov). + % Think if that is what we want of the NE residual + + errorL2((iii-1)*niter+ii)=im3Dnorm(aux,'L2'); + + if measurequality + qualMeasOut(:,(iii-1)*niter+ii)=Measure_Quality(x0,x,QualMeasOpts); + end + + % This might happen now, we are minimizing the normal eqns residual +% if ii>1 && errorL2(ii)>errorL2(ii-1) +% % OUT! +% x=x-alpha*p; +% if verbose +% disp(['CGLS stoped in iteration N', num2str(ii),' due to divergence.']) +% end +% return; +% end + + % If step is adecuate, then continue withg CGLS + r_aux_1 = r_aux_1-alpha*q_aux_1; + r_aux_2 = cellfun(@(a,b)a-alpha*b, r_aux_2, q_aux_2, 'uni',0); + + s_aux_1 = Atb(r_aux_1 ,geo,angles,'matched','gpuids',gpuids); + s_aux_2 = sqrt(lambda) * Ltx (W, r_aux_2); + s = s_aux_1 + s_aux_2; + + gamma1=norm(s(:),2)^2; + beta=gamma1/gamma; + gamma=gamma1; + p=s+beta*p; + + if (iii==1 && ii ==1 && verbose) + expected_time=toc*niter*niter_outer; + disp('CGLS'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end + + end + +end +end +% % % Non-sense now, just giving output of the right dimensions +% % function out = Lx (sizeD3_1, sizeD3_2, x) +% % % L = speye(3*sizeD3_1,sizeD3_2 ); +% % if prod(size(x)) == sizeD3_2 && 3*prod(size(x(:,:,1:end-1))) == sizeD3_1 +% % out = cat(3,x(:,:,1:end-1),x(:,:,1:end-1),x(:,:,1:end-1)); +% % else +% % error('wrong dimensions') +% % end +% % end +% % +% % function out = Ltx (sizeD3_1, sizeD3_2, x) +% % % L = speye(3*sizeD3_1,sizeD3_2 ); +% % if prod(size(x)) == sizeD3_1 +% % out = x(:,:,1:512); +% % else +% % error('wrong dimensions') +% % end +% % end + +% How to make this efficient? + +function W = build_weights ( x) + % Directional discrete derivatives + % Reflective boundary conditions + Dxx=cat(1,x(1:end-1,:,:)-x(2:end,:,:),zeros(1,size(x,2),size(x,3))); + Dyx=cat(2,x(:,1:end-1,:)-x(:,2:end,:),zeros(size(x,1),1,size(x,3))); + Dzx=cat(3,x(:,:,1:end-1)-x(:,:,2:end),zeros(size(x,1),size(x,2),1)); + + W = (Dxx.^2+Dyx.^2+Dzx.^2+1e-6).^(-1/4); % Fix this... + +end + +function out = Lx (W, x) + % Directional discrete derivatives + % Reflective boundary conditions + Dxx=cat(1,x(1:end-1,:,:)-x(2:end,:,:),zeros(1,size(x,2),size(x,3))); + Dyx=cat(2,x(:,1:end-1,:)-x(:,2:end,:),zeros(size(x,1),1,size(x,3))); + Dzx=cat(3,x(:,:,1:end-1)-x(:,:,2:end),zeros(size(x,1),size(x,2),1)); + + % Build weights - is it better to find the right rotation and add + % tensors? + WDxx = W .* Dxx; + WDyx = W .* Dyx; + WDzx = W .* Dzx; + + out = {WDxx WDyx WDzx}; +end + +function out = Ltx (W, x) % Input is a cell + Wx_1 = W .* x{1}; + Wx_2 = W .* x{2}; + Wx_3 = W .* x{3}; + + % Left here, but this is how to make a transpose + DxtWx_1=cat(1,Wx_1(1,:,:),Wx_1(2:end-1,:,:)-Wx_1(1:end-2,:,:),-Wx_1(end-1,:,:)); + DytWx_2=cat(2,Wx_2(:,1,:),Wx_2(:,2:end-1,:)-Wx_2(:,1:end-2,:),-Wx_2(:,end-1,:)); + DztWx_3=cat(3,Wx_3(:,:,1),Wx_3(:,:,2:end-1)-Wx_3(:,:,1:end-2),-Wx_3(:,:,end-1)); + + out = DxtWx_1 + DytWx_2 + DztWx_3; +end +%% parse inputs' +function [verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids'}; +defaults=ones(length(opts),1); + +% Check inputs +nVarargs = length(argin); +if mod(nVarargs,2) + error('TIGRE:CGLS:InvalidInput','Invalid number of inputs') +end + +% check if option has been passed as input +for ii=1:2:nVarargs + ind=find(ismember(opts,lower(argin{ii}))); + if ~isempty(ind) + defaults(ind)=0; + else + error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + end +end + +for ii=1:length(opts) + opt=opts{ii}; + default=defaults(ii); + % if one option isnot default, then extranc value from input + if default==0 + ind=double.empty(0,1);jj=1; + while isempty(ind) + ind=find(isequal(opt,lower(argin{jj}))); + jj=jj+1; + end + if isempty(ind) + error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + end + val=argin{jj}; + end + + switch opt + case 'init' + x=[]; + if default || strcmp(val,'none') + x=zeros(geo.nVoxel','single'); + continue; + end + if strcmp(val,'FDK') + x=FDK(proj,geo,angles); + continue; + end + if strcmp(val,'multigrid') + x=init_multigrid(proj,geo,angles); + continue; + end + if strcmp(val,'image') + initwithimage=1; + continue; + end + if isempty(x) + error('TIGRE:CGLS:InvalidInput','Invalid Init option') + end + % % % % % % % ERROR + case 'initimg' + if default + continue; + end + if exist('initwithimage','var') + if isequal(size(val),geo.nVoxel') + x=single(val); + else + error('TIGRE:CGLS:InvalidInput','Invalid image for initialization'); + end + end + % ========================================================================= + case 'qualmeas' + if default + QualMeasOpts={}; + else + if iscellstr(val) + QualMeasOpts=val; + else + error('TIGRE:CGLS:InvalidInput','Invalid quality measurement parameters'); + end + end + case 'verbose' + if default + verbose=1; + else + verbose=val; + end + if ~is2014bOrNewer + warning('TIGRE:Verbose mode not available for older versions than MATLAB R2014b'); + verbose=false; + end + case 'gpuids' + if default + gpuids = GpuIds(); + else + gpuids = val; + end + otherwise + error('TIGRE:CGLS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); + end +end + + +end \ No newline at end of file diff --git a/MATLAB/Algorithms/LSMR.m b/MATLAB/Algorithms/LSMR.m new file mode 100644 index 00000000..c2a37d19 --- /dev/null +++ b/MATLAB/Algorithms/LSMR.m @@ -0,0 +1,297 @@ +function [x,errorL2,qualMeasOut]= LSMR(proj,geo,angles,niter,varargin) + +% LSMR_CBCT solves the CBCT problem using LSMR. +% +% LSMR_CBCT(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% using the projection data PROJ taken over ALPHA angles, corresponding +% to the geometry descrived in GEO, using NITER iterations. +% +% LSMR_CBCT(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% possible options in OPT are: +% +% +% 'Init' Describes diferent initialization techniques. +% * 'none' : Initializes the image to zeros (default) +% * 'FDK' : intializes image to FDK reconstrucition +% * 'multigrid': Initializes image by solving the problem in +% small scale and increasing it when relative +% convergence is reached. +% * 'image' : Initialization using a user specified +% image. Not recomended unless you really +% know what you are doing. +% 'InitImg' an image for the 'image' initialization. Avoid. +% 'redundancy_weighting': true or false. Default is true. Applies data +% redundancy weighting to projections in the update step +% (relevant for offset detector geometry) +%-------------------------------------------------------------------------- +%-------------------------------------------------------------------------- +% This file is part of the TIGRE Toolbox +% +% Copyright (c) 2015, University of Bath and +% CERN-European Organization for Nuclear Research +% All rights reserved. +% +% License: Open Source under BSD. +% See the full license at +% https://github.com/CERN/TIGRE/blob/master/LICENSE +% +% Contact: tigre.toolbox@gmail.com +% Codes: https://github.com/CERN/TIGRE/ +% Coded by: Malena Sabate Landman, Ander Biguri +%-------------------------------------------------------------------------- +%% + +[verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); + +measurequality=~isempty(QualMeasOpts); +qualMeasOut=zeros(length(QualMeasOpts),niter); + + +% David Chin-Lung Fong and Michael Saunders //doi.org/10.1137/10079687X + +% Enumeration as given in the paper for 'Algorithm LSMR' +% (1) Initialize +u=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); +normr = norm(u(:),2); +u = u/normr; + +beta = normr; + +v=Atb(u,geo,angles,'matched','gpuids',gpuids); + + +alpha = norm(v(:),2); % msl: do we want to check if it is 0? +v = v/alpha; + +alphabar = alpha; +zetabar = alpha * beta; +rho = 1; +rhobar = 1; +cbar = 1; +sbar = 0; +h = v; +hbar = 0; + +% Compute the residual norm ||r_k|| +betadd = beta; +betad = 0; +rhod = 1; +tautilda = 0; +thetatilda = 0; +zeta = 0; + + +% msl: is this error the residual norm ? +errorL2=zeros(1,niter); + +% (2) Start iterations +for ii=1:niter + x0 = x; + if (ii==1 && verbose);tic;end + + % (3) Continue the bidiagonalization + u = Ax(v,geo,angles,'Siddon','gpuids',gpuids) - alpha*u; + beta = norm(u(:),2); + u = u / beta; + + v = Atb(u,geo,angles,'matched','gpuids',gpuids) - beta*v; + + alpha = norm(v(:),2); + v = v / alpha; + + % (4) Construct and apply rotation Pk + rhopre = rho; + rho = sqrt(alphabar^2 + beta^2); + c = alphabar / rho; + s = beta / rho; + theta = s * alpha; + alphabar = c * alpha; + + % Computing ||r_k||, eqn (3.4) in paper + betahat = c * betadd; + betadd = -s * betadd; + + % (5) Construct and apply rotation Pkbar + thetabar = sbar * rho; + rhobarpre = rhobar; + rhobar = sqrt((cbar*rho)^2 + theta^2); + cbar = cbar * rho / rhobar; + sbar = theta / rhobar; + zetapre = zeta; + zeta = cbar * zetabar; + zetabar = -sbar * zetabar; + + % (6) Update h, hbar, x + hbar = h - (thetabar*rho/(rhopre*rhobarpre))*hbar; + x = x + (zeta / (rho*rhobar)) * hbar; + h = v - (theta / rho) * h; + + % Update estimated quantities of interest. + % If k >= 2, construct and apply Ptilda to compute ||r_k|| + if ii >= 1 + % eqn (3.5) in paper + rhotilda = sqrt(rhod^2 + thetabar^2); + ctilda = rhod / rhotilda; + stilda = thetabar / rhotilda; + thetatildapre = thetatilda; + thetatilda = stilda * rhobar; + rhod = ctilda * rhobar; + betatilda = ctilda * betad + stilda * betahat; % msl: in the orinal paper, but not used + betad = -stilda * betad + ctilda * betahat; + % Update ttilda by forward substitution + tautilda = (zetapre - thetatildapre*tautilda) / rhotilda; + taud = (zeta - thetatilda*tautilda) / rhod; + % Form ||r_k|| + gamma_var = (betad - taud)^2 + betadd^2; + errorL2(ii) = sqrt(gamma_var); + % ||A^T r_k || is just |zetabar| + else + % Not sure what to say about errorL2(1) ... + errorL2(ii)=NaN; + end + + aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); %expensive, is there any way to check this better? + errorL2(ii)=im3Dnorm(aux,'L2'); + + + % (6) Test for convergence. + % msl: I still need to implement this. + % msl: There are suggestions on the original paper. Let's talk about it! + + if measurequality % msl: what is this?? + qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); + end + +% Does not make sense here, we are minimizing ||A^T rk|| not ||r_k|| +% if ii>1 && errorL2(ii)>errorL2(ii-1) % msl: not checked +% % OUT! +% x=x-alpha*v; +% if verbose +% disp(['CGLS stoped in iteration N', num2str(ii),' due to divergence.']) +% end +% return; +% end + + if (ii==1 && verbose) + expected_time=toc*niter; + disp('LSMR'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end +end + +end + + +%% parse inputs' +function [verbose,x,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','redundancy_weighting'}; +defaults=ones(length(opts),1); + +% Check inputs +nVarargs = length(argin); +if mod(nVarargs,2) + error('TIGRE:CGLS:InvalidInput','Invalid number of inputs') +end + +% check if option has been passed as input +for ii=1:2:nVarargs + ind=find(ismember(opts,lower(argin{ii}))); + if ~isempty(ind) + defaults(ind)=0; + else + error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + end +end + +for ii=1:length(opts) + opt=opts{ii}; + default=defaults(ii); + % if one option isnot default, then extranc value from input + if default==0 + ind=double.empty(0,1);jj=1; + while isempty(ind) + ind=find(isequal(opt,lower(argin{jj}))); + jj=jj+1; + end + if isempty(ind) + error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + end + val=argin{jj}; + end + + switch opt + case 'init' + x=[]; + if default || strcmp(val,'none') + x=zeros(geo.nVoxel','single'); + continue; + end + if strcmp(val,'FDK') + x=FDK(proj,geo,angles); + continue; + end + if strcmp(val,'multigrid') + x=init_multigrid(proj,geo,angles); + continue; + end + if strcmp(val,'image') + initwithimage=1; + continue; + end + if isempty(x) + error('TIGRE:CGLS:InvalidInput','Invalid Init option') + end + % % % % % % % ERROR + case 'initimg' + if default + continue; + end + if exist('initwithimage','var') + if isequal(size(val),geo.nVoxel') + x=single(val); + else + error('TIGRE:CGLS:InvalidInput','Invalid image for initialization'); + end + end + % ========================================================================= + case 'qualmeas' + if default + QualMeasOpts={}; + else + if iscellstr(val) + QualMeasOpts=val; + else + error('TIGRE:CGLS:InvalidInput','Invalid quality measurement parameters'); + end + end + case 'verbose' + if default + verbose=1; + else + verbose=val; + end + if ~is2014bOrNewer + warning('TIGRE:Verbose mode not available for older versions than MATLAB R2014b'); + verbose=false; + end + case 'gpuids' + if default + gpuids = GpuIds(); + else + gpuids = val; + end + case 'redundancy_weighting' + if default + redundancy_weights = true; + else + redundancy_weights = val; + end + otherwise + error('TIGRE:CGLS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); + end +end + + +end diff --git a/MATLAB/Algorithms/LSMR_lambda.m b/MATLAB/Algorithms/LSMR_lambda.m new file mode 100644 index 00000000..09f56215 --- /dev/null +++ b/MATLAB/Algorithms/LSMR_lambda.m @@ -0,0 +1,316 @@ +function [x,errorL2,qualMeasOut]= LSMR_lambda(proj,geo,angles,niter,varargin) + +% LSMR_CBCT solves the CBCT problem using LSMR. +% +% LSMR_CBCT(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% using the projection data PROJ taken over ALPHA angles, corresponding +% to the geometry descrived in GEO, using NITER iterations. +% +% LSMR_CBCT(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% possible options in OPT are: +% +% +% 'Init' Describes diferent initialization techniques. +% * 'none' : Initializes the image to zeros (default) +% * 'FDK' : intializes image to FDK reconstrucition +% * 'multigrid': Initializes image by solving the problem in +% small scale and increasing it when relative +% convergence is reached. +% * 'image' : Initialization using a user specified +% image. Not recomended unless you really +% know what you are doing. +% 'InitImg' an image for the 'image' initialization. Avoid. +% 'redundancy_weighting': true or false. Default is true. Applies data +% redundancy weighting to projections in the update step +% (relevant for offset detector geometry) +%-------------------------------------------------------------------------- +%-------------------------------------------------------------------------- +% This file is part of the TIGRE Toolbox +% +% Copyright (c) 2015, University of Bath and +% CERN-European Organization for Nuclear Research +% All rights reserved. +% +% License: Open Source under BSD. +% See the full license at +% https://github.com/CERN/TIGRE/blob/master/LICENSE +% +% Contact: tigre.toolbox@gmail.com +% Codes: https://github.com/CERN/TIGRE/ +% Coded by: Malena Sabate Landman, Ander Biguri +%-------------------------------------------------------------------------- +%% + +[verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); + +lambda = 10; + +measurequality=~isempty(QualMeasOpts); +qualMeasOut=zeros(length(QualMeasOpts),niter); + + +% David Chin-Lung Fong and Michael Saunders //doi.org/10.1137/10079687X + +% Enumeration as given in the paper for 'Algorithm LSMR' +% (1) Initialize +u=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); +normr = norm(u(:),2); +beta = normr; +u = u/beta; + + +v=Atb(u,geo,angles,'matched','gpuids',gpuids); +alpha = norm(v(:),2); % msl: do we want to check if it is 0? +v = v/alpha; + +alphabar = alpha; +zetabar = alpha * beta; +rho = 1; +rhobar = 1; +cbar = 1; +sbar = 0; +h = v; +hbar = 0; + +% Compute the residual norm ||r_k|| +betadd = beta; +betad = 0; +rhod = 1; +tautilda = 0; +thetatilda = 0; +zeta = 0; +d = 0; + +% msl: is this error the residual norm ? +errorL2 = zeros(1,niter); + +% (2) Start iterations +for ii=1:niter + x0 = x; + if (ii==1 && verbose);tic;end + + % (3) Continue the bidiagonalization + u = Ax(v,geo,angles,'Siddon','gpuids',gpuids) - alpha*u; + beta = norm(u(:),2); + u = u / beta; + + v = Atb(u,geo,angles,'matched','gpuids',gpuids) - beta*v; + + alpha = norm(v(:),2); + v = v / alpha; + + % (4) Construct and apply rotation \hat{P}_k + alphahat = sqrt(alphabar^2 + lambda^2); + chat = alphabar/alphahat; + shat = lambda/alphahat; + + % (5) Construct and apply rotation P_k + rhopre = rho; + rho = sqrt(alphahat^2 + beta^2); + c = alphahat / rho; + s = beta / rho; + theta = s * alpha; + alphabar = c * alpha; + + % (6) Construct and apply rotation \bar{P}_k + thetabar = sbar * rho; + rhobarpre = rhobar; + rhobar = sqrt((cbar*rho)^2 + theta^2); + cbar = cbar * rho / rhobar; + sbar = theta / rhobar; + zetapre = zeta; + zeta = cbar * zetabar; + zetabar = -sbar * zetabar; + + % (7) Update \bar{h}, x, h + hbar = h - (thetabar*rho/(rhopre*rhobarpre))*hbar; + x = x + (zeta / (rho*rhobar)) * hbar; + h = v - (theta / rho) * h; + + % (8) Apply rotation \hat{P}_k, P_k + betaacute = chat* betadd; + betacheck = - shat* betadd; + + % Computing ||r_k|| + + betahat = c * betaacute; + betadd = -s * betaacute; + + % Update estimated quantities of interest. + % (9) If k >= 2, construct and apply \tilde{P} to compute ||r_k|| + % if ii >= 1 decided against it. Malena. + rhotilda = sqrt(rhod^2 + thetabar^2); + ctilda = rhod / rhotilda; + stilda = thetabar / rhotilda; + thetatildapre = thetatilda; + thetatilda = stilda * rhobar; + rhod = ctilda * rhobar; + % betatilda = ctilda * betad + stilda * betahat; % msl: in the orinal paper, but not used + betad = -stilda * betad + ctilda * betahat; + % else + % errorL2(ii)=NaN; + % end + + % (10) Update \tilde{t}_k by forward substitution + tautilda = (zetapre - thetatildapre*tautilda) / rhotilda; + taud = (zeta - thetatilda*tautilda) / rhod; + + % (11) Compute ||r_k|| + d = d + betacheck^2; + gamma_var = d + (betad - taud)^2 + betadd^2; + errorL2(ii) = sqrt(gamma_var); + + % ||A^T r_k || is just |zetabar| + + + % aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); %expensive, is there any way to check this better? + % errorL2_2(ii)=im3Dnorm(aux,'L2'); + + + % (6) Test for convergence. + % msl: I still need to implement this. + % msl: There are suggestions on the original paper. Let's talk about it! + + if measurequality % msl: what is this?? + qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); + end + +% Does not make sense here, we are minimizing ||A^T rk|| not ||r_k|| +% if ii>1 && errorL2(ii)>errorL2(ii-1) % msl: not checked +% % OUT! +% x=x-alpha*v; +% if verbose +% disp(['CGLS stoped in iteration N', num2str(ii),' due to divergence.']) +% end +% return; +% end + + if (ii==1 && verbose) + expected_time=toc*niter; + disp('LSMR'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end +end + +end + + +%% parse inputs' +function [verbose,x,QualMeasOpts,gpuids,redundancy_weights, lambda]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','redundancy_weighting','lambda'}; +defaults=ones(length(opts),1); + +% Check inputs +nVarargs = length(argin); +if mod(nVarargs,2) + error('TIGRE:CGLS:InvalidInput','Invalid number of inputs') +end + +% check if option has been passed as input +for ii=1:2:nVarargs + ind=find(ismember(opts,lower(argin{ii}))); + if ~isempty(ind) + defaults(ind)=0; + else + error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + end +end + +for ii=1:length(opts) + opt=opts{ii}; + default=defaults(ii); + % if one option isnot default, then extranc value from input + if default==0 + ind=double.empty(0,1);jj=1; + while isempty(ind) + ind=find(isequal(opt,lower(argin{jj}))); + jj=jj+1; + end + if isempty(ind) + error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + end + val=argin{jj}; + end + + switch opt + case 'init' + x=[]; + if default || strcmp(val,'none') + x=zeros(geo.nVoxel','single'); + continue; + end + if strcmp(val,'FDK') + x=FDK(proj,geo,angles); + continue; + end + if strcmp(val,'multigrid') + x=init_multigrid(proj,geo,angles); + continue; + end + if strcmp(val,'image') + initwithimage=1; + continue; + end + if isempty(x) + error('TIGRE:CGLS:InvalidInput','Invalid Init option') + end + % % % % % % % ERROR + case 'initimg' + if default + continue; + end + if exist('initwithimage','var') + if isequal(size(val),geo.nVoxel') + x=single(val); + else + error('TIGRE:CGLS:InvalidInput','Invalid image for initialization'); + end + end + % ========================================================================= + case 'qualmeas' + if default + QualMeasOpts={}; + else + if iscellstr(val) + QualMeasOpts=val; + else + error('TIGRE:CGLS:InvalidInput','Invalid quality measurement parameters'); + end + end + case 'verbose' + if default + verbose=1; + else + verbose=val; + end + if ~is2014bOrNewer + warning('TIGRE:Verbose mode not available for older versions than MATLAB R2014b'); + verbose=false; + end + case 'gpuids' + if default + gpuids = GpuIds(); + else + gpuids = val; + end + case 'redundancy_weighting' + if default + redundancy_weights = true; + else + redundancy_weights = val; + end + case 'lambda' + if default + lambda = 0; + else + lambda = val; + end + otherwise + error('TIGRE:CGLS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); + end +end + + +end From 292b9116b8bf96eeec7ab9e13758279b63bd8dde Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Thu, 29 Sep 2022 14:08:02 +0100 Subject: [PATCH 06/42] Make algorithms TIGRE-like --- MATLAB/Algorithms/LSMR.m | 133 +++++++------- MATLAB/Algorithms/LSMR_lambda.m | 316 -------------------------------- MATLAB/Demos/d08_Algorithms03.m | 32 +++- 3 files changed, 85 insertions(+), 396 deletions(-) delete mode 100644 MATLAB/Algorithms/LSMR_lambda.m diff --git a/MATLAB/Algorithms/LSMR.m b/MATLAB/Algorithms/LSMR.m index c2a37d19..ea627cf7 100644 --- a/MATLAB/Algorithms/LSMR.m +++ b/MATLAB/Algorithms/LSMR.m @@ -1,15 +1,15 @@ -function [x,errorL2,qualMeasOut]= LSMR(proj,geo,angles,niter,varargin) +function [x,residual,qualMeasOut]= LSMR(proj,geo,angles,niter,varargin) -% LSMR_CBCT solves the CBCT problem using LSMR. +% LSMR solves the CBCT problem using LSMR. % -% LSMR_CBCT(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% LSMR(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem % using the projection data PROJ taken over ALPHA angles, corresponding % to the geometry descrived in GEO, using NITER iterations. % -% LSMR_CBCT(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% LSMR(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The % possible options in OPT are: % -% +% 'lambda' Value of parameter lambda, default 0. % 'Init' Describes diferent initialization techniques. % * 'none' : Initializes the image to zeros (default) % * 'FDK' : intializes image to FDK reconstrucition @@ -20,9 +20,6 @@ % image. Not recomended unless you really % know what you are doing. % 'InitImg' an image for the 'image' initialization. Avoid. -% 'redundancy_weighting': true or false. Default is true. Applies data -% redundancy weighting to projections in the update step -% (relevant for offset detector geometry) %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -41,7 +38,7 @@ %-------------------------------------------------------------------------- %% -[verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); +[verbose,x,QualMeasOpts,gpuids,lambda]=parse_inputs(proj,geo,angles,varargin); measurequality=~isempty(QualMeasOpts); qualMeasOut=zeros(length(QualMeasOpts),niter); @@ -53,13 +50,11 @@ % (1) Initialize u=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); normr = norm(u(:),2); -u = u/normr; - beta = normr; - -v=Atb(u,geo,angles,'matched','gpuids',gpuids); +u = u/beta; +v=Atb(u,geo,angles,'matched','gpuids',gpuids); alpha = norm(v(:),2); % msl: do we want to check if it is 0? v = v/alpha; @@ -79,10 +74,10 @@ tautilda = 0; thetatilda = 0; zeta = 0; - +d = 0; % msl: is this error the residual norm ? -errorL2=zeros(1,niter); +residual = zeros(1,niter); % (2) Start iterations for ii=1:niter @@ -99,19 +94,20 @@ alpha = norm(v(:),2); v = v / alpha; - % (4) Construct and apply rotation Pk - rhopre = rho; - rho = sqrt(alphabar^2 + beta^2); - c = alphabar / rho; + % (4) Construct and apply rotation \hat{P}_k + alphahat = sqrt(alphabar^2 + lambda^2); + chat = alphabar/alphahat; + shat = lambda/alphahat; + + % (5) Construct and apply rotation P_k + rhopre = rho; + rho = sqrt(alphahat^2 + beta^2); + c = alphahat / rho; s = beta / rho; theta = s * alpha; alphabar = c * alpha; - - % Computing ||r_k||, eqn (3.4) in paper - betahat = c * betadd; - betadd = -s * betadd; - % (5) Construct and apply rotation Pkbar + % (6) Construct and apply rotation \bar{P}_k thetabar = sbar * rho; rhobarpre = rhobar; rhobar = sqrt((cbar*rho)^2 + theta^2); @@ -121,39 +117,44 @@ zeta = cbar * zetabar; zetabar = -sbar * zetabar; - % (6) Update h, hbar, x + % (7) Update \bar{h}, x, h hbar = h - (thetabar*rho/(rhopre*rhobarpre))*hbar; x = x + (zeta / (rho*rhobar)) * hbar; h = v - (theta / rho) * h; + % (8) Apply rotation \hat{P}_k, P_k + betaacute = chat* betadd; + betacheck = - shat* betadd; + + % Computing ||r_k|| + + betahat = c * betaacute; + betadd = -s * betaacute; + % Update estimated quantities of interest. - % If k >= 2, construct and apply Ptilda to compute ||r_k|| - if ii >= 1 - % eqn (3.5) in paper - rhotilda = sqrt(rhod^2 + thetabar^2); - ctilda = rhod / rhotilda; - stilda = thetabar / rhotilda; - thetatildapre = thetatilda; - thetatilda = stilda * rhobar; - rhod = ctilda * rhobar; - betatilda = ctilda * betad + stilda * betahat; % msl: in the orinal paper, but not used - betad = -stilda * betad + ctilda * betahat; - % Update ttilda by forward substitution - tautilda = (zetapre - thetatildapre*tautilda) / rhotilda; - taud = (zeta - thetatilda*tautilda) / rhod; - % Form ||r_k|| - gamma_var = (betad - taud)^2 + betadd^2; - errorL2(ii) = sqrt(gamma_var); - % ||A^T r_k || is just |zetabar| - else - % Not sure what to say about errorL2(1) ... - errorL2(ii)=NaN; - end + % (9) If k >= 2, construct and apply \tilde{P} to compute ||r_k|| + rhotilda = sqrt(rhod^2 + thetabar^2); + ctilda = rhod / rhotilda; + stilda = thetabar / rhotilda; + thetatildapre = thetatilda; + thetatilda = stilda * rhobar; + rhod = ctilda * rhobar; + % betatilda = ctilda * betad + stilda * betahat; % msl: in the orinal paper, but not used + betad = -stilda * betad + ctilda * betahat; - aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); %expensive, is there any way to check this better? - errorL2(ii)=im3Dnorm(aux,'L2'); + % (10) Update \tilde{t}_k by forward substitution + tautilda = (zetapre - thetatildapre*tautilda) / rhotilda; + taud = (zeta - thetatilda*tautilda) / rhod; + % (11) Compute ||r_k|| + d = d + betacheck^2; + gamma_var = d + (betad - taud)^2 + betadd^2; + residual(ii) = sqrt(gamma_var); + % ||A^T r_k || is just |zetabar| + + + % (6) Test for convergence. % msl: I still need to implement this. % msl: There are suggestions on the original paper. Let's talk about it! @@ -161,16 +162,6 @@ if measurequality % msl: what is this?? qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); end - -% Does not make sense here, we are minimizing ||A^T rk|| not ||r_k|| -% if ii>1 && errorL2(ii)>errorL2(ii-1) % msl: not checked -% % OUT! -% x=x-alpha*v; -% if verbose -% disp(['CGLS stoped in iteration N', num2str(ii),' due to divergence.']) -% end -% return; -% end if (ii==1 && verbose) expected_time=toc*niter; @@ -185,14 +176,14 @@ %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids','redundancy_weighting'}; +function [verbose,x,QualMeasOpts,gpuids, lambda]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','lambda'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); if mod(nVarargs,2) - error('TIGRE:CGLS:InvalidInput','Invalid number of inputs') + error('TIGRE:LSMR:InvalidInput','Invalid number of inputs') end % check if option has been passed as input @@ -201,7 +192,7 @@ if ~isempty(ind) defaults(ind)=0; else - error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + error('TIGRE:LSMR:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); end end @@ -216,7 +207,7 @@ jj=jj+1; end if isempty(ind) - error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + error('TIGRE:LSMR:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); end val=argin{jj}; end @@ -241,7 +232,7 @@ continue; end if isempty(x) - error('TIGRE:CGLS:InvalidInput','Invalid Init option') + error('TIGRE:LSMR:InvalidInput','Invalid Init option') end % % % % % % % ERROR case 'initimg' @@ -252,7 +243,7 @@ if isequal(size(val),geo.nVoxel') x=single(val); else - error('TIGRE:CGLS:InvalidInput','Invalid image for initialization'); + error('TIGRE:LSMR:InvalidInput','Invalid image for initialization'); end end % ========================================================================= @@ -263,7 +254,7 @@ if iscellstr(val) QualMeasOpts=val; else - error('TIGRE:CGLS:InvalidInput','Invalid quality measurement parameters'); + error('TIGRE:LSMR:InvalidInput','Invalid quality measurement parameters'); end end case 'verbose' @@ -282,14 +273,14 @@ else gpuids = val; end - case 'redundancy_weighting' + case 'lambda' if default - redundancy_weights = true; + lambda = 0; else - redundancy_weights = val; + lambda = val; end otherwise - error('TIGRE:CGLS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); + error('TIGRE:LSMR:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in LSMR()']); end end diff --git a/MATLAB/Algorithms/LSMR_lambda.m b/MATLAB/Algorithms/LSMR_lambda.m deleted file mode 100644 index 09f56215..00000000 --- a/MATLAB/Algorithms/LSMR_lambda.m +++ /dev/null @@ -1,316 +0,0 @@ -function [x,errorL2,qualMeasOut]= LSMR_lambda(proj,geo,angles,niter,varargin) - -% LSMR_CBCT solves the CBCT problem using LSMR. -% -% LSMR_CBCT(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem -% using the projection data PROJ taken over ALPHA angles, corresponding -% to the geometry descrived in GEO, using NITER iterations. -% -% LSMR_CBCT(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The -% possible options in OPT are: -% -% -% 'Init' Describes diferent initialization techniques. -% * 'none' : Initializes the image to zeros (default) -% * 'FDK' : intializes image to FDK reconstrucition -% * 'multigrid': Initializes image by solving the problem in -% small scale and increasing it when relative -% convergence is reached. -% * 'image' : Initialization using a user specified -% image. Not recomended unless you really -% know what you are doing. -% 'InitImg' an image for the 'image' initialization. Avoid. -% 'redundancy_weighting': true or false. Default is true. Applies data -% redundancy weighting to projections in the update step -% (relevant for offset detector geometry) -%-------------------------------------------------------------------------- -%-------------------------------------------------------------------------- -% This file is part of the TIGRE Toolbox -% -% Copyright (c) 2015, University of Bath and -% CERN-European Organization for Nuclear Research -% All rights reserved. -% -% License: Open Source under BSD. -% See the full license at -% https://github.com/CERN/TIGRE/blob/master/LICENSE -% -% Contact: tigre.toolbox@gmail.com -% Codes: https://github.com/CERN/TIGRE/ -% Coded by: Malena Sabate Landman, Ander Biguri -%-------------------------------------------------------------------------- -%% - -[verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); - -lambda = 10; - -measurequality=~isempty(QualMeasOpts); -qualMeasOut=zeros(length(QualMeasOpts),niter); - - -% David Chin-Lung Fong and Michael Saunders //doi.org/10.1137/10079687X - -% Enumeration as given in the paper for 'Algorithm LSMR' -% (1) Initialize -u=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); -normr = norm(u(:),2); -beta = normr; -u = u/beta; - - -v=Atb(u,geo,angles,'matched','gpuids',gpuids); -alpha = norm(v(:),2); % msl: do we want to check if it is 0? -v = v/alpha; - -alphabar = alpha; -zetabar = alpha * beta; -rho = 1; -rhobar = 1; -cbar = 1; -sbar = 0; -h = v; -hbar = 0; - -% Compute the residual norm ||r_k|| -betadd = beta; -betad = 0; -rhod = 1; -tautilda = 0; -thetatilda = 0; -zeta = 0; -d = 0; - -% msl: is this error the residual norm ? -errorL2 = zeros(1,niter); - -% (2) Start iterations -for ii=1:niter - x0 = x; - if (ii==1 && verbose);tic;end - - % (3) Continue the bidiagonalization - u = Ax(v,geo,angles,'Siddon','gpuids',gpuids) - alpha*u; - beta = norm(u(:),2); - u = u / beta; - - v = Atb(u,geo,angles,'matched','gpuids',gpuids) - beta*v; - - alpha = norm(v(:),2); - v = v / alpha; - - % (4) Construct and apply rotation \hat{P}_k - alphahat = sqrt(alphabar^2 + lambda^2); - chat = alphabar/alphahat; - shat = lambda/alphahat; - - % (5) Construct and apply rotation P_k - rhopre = rho; - rho = sqrt(alphahat^2 + beta^2); - c = alphahat / rho; - s = beta / rho; - theta = s * alpha; - alphabar = c * alpha; - - % (6) Construct and apply rotation \bar{P}_k - thetabar = sbar * rho; - rhobarpre = rhobar; - rhobar = sqrt((cbar*rho)^2 + theta^2); - cbar = cbar * rho / rhobar; - sbar = theta / rhobar; - zetapre = zeta; - zeta = cbar * zetabar; - zetabar = -sbar * zetabar; - - % (7) Update \bar{h}, x, h - hbar = h - (thetabar*rho/(rhopre*rhobarpre))*hbar; - x = x + (zeta / (rho*rhobar)) * hbar; - h = v - (theta / rho) * h; - - % (8) Apply rotation \hat{P}_k, P_k - betaacute = chat* betadd; - betacheck = - shat* betadd; - - % Computing ||r_k|| - - betahat = c * betaacute; - betadd = -s * betaacute; - - % Update estimated quantities of interest. - % (9) If k >= 2, construct and apply \tilde{P} to compute ||r_k|| - % if ii >= 1 decided against it. Malena. - rhotilda = sqrt(rhod^2 + thetabar^2); - ctilda = rhod / rhotilda; - stilda = thetabar / rhotilda; - thetatildapre = thetatilda; - thetatilda = stilda * rhobar; - rhod = ctilda * rhobar; - % betatilda = ctilda * betad + stilda * betahat; % msl: in the orinal paper, but not used - betad = -stilda * betad + ctilda * betahat; - % else - % errorL2(ii)=NaN; - % end - - % (10) Update \tilde{t}_k by forward substitution - tautilda = (zetapre - thetatildapre*tautilda) / rhotilda; - taud = (zeta - thetatilda*tautilda) / rhod; - - % (11) Compute ||r_k|| - d = d + betacheck^2; - gamma_var = d + (betad - taud)^2 + betadd^2; - errorL2(ii) = sqrt(gamma_var); - - % ||A^T r_k || is just |zetabar| - - - % aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); %expensive, is there any way to check this better? - % errorL2_2(ii)=im3Dnorm(aux,'L2'); - - - % (6) Test for convergence. - % msl: I still need to implement this. - % msl: There are suggestions on the original paper. Let's talk about it! - - if measurequality % msl: what is this?? - qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); - end - -% Does not make sense here, we are minimizing ||A^T rk|| not ||r_k|| -% if ii>1 && errorL2(ii)>errorL2(ii-1) % msl: not checked -% % OUT! -% x=x-alpha*v; -% if verbose -% disp(['CGLS stoped in iteration N', num2str(ii),' due to divergence.']) -% end -% return; -% end - - if (ii==1 && verbose) - expected_time=toc*niter; - disp('LSMR'); - disp(['Expected duration : ',secs2hms(expected_time)]); - disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); - disp(''); - end -end - -end - - -%% parse inputs' -function [verbose,x,QualMeasOpts,gpuids,redundancy_weights, lambda]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids','redundancy_weighting','lambda'}; -defaults=ones(length(opts),1); - -% Check inputs -nVarargs = length(argin); -if mod(nVarargs,2) - error('TIGRE:CGLS:InvalidInput','Invalid number of inputs') -end - -% check if option has been passed as input -for ii=1:2:nVarargs - ind=find(ismember(opts,lower(argin{ii}))); - if ~isempty(ind) - defaults(ind)=0; - else - error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); - end -end - -for ii=1:length(opts) - opt=opts{ii}; - default=defaults(ii); - % if one option isnot default, then extranc value from input - if default==0 - ind=double.empty(0,1);jj=1; - while isempty(ind) - ind=find(isequal(opt,lower(argin{jj}))); - jj=jj+1; - end - if isempty(ind) - error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); - end - val=argin{jj}; - end - - switch opt - case 'init' - x=[]; - if default || strcmp(val,'none') - x=zeros(geo.nVoxel','single'); - continue; - end - if strcmp(val,'FDK') - x=FDK(proj,geo,angles); - continue; - end - if strcmp(val,'multigrid') - x=init_multigrid(proj,geo,angles); - continue; - end - if strcmp(val,'image') - initwithimage=1; - continue; - end - if isempty(x) - error('TIGRE:CGLS:InvalidInput','Invalid Init option') - end - % % % % % % % ERROR - case 'initimg' - if default - continue; - end - if exist('initwithimage','var') - if isequal(size(val),geo.nVoxel') - x=single(val); - else - error('TIGRE:CGLS:InvalidInput','Invalid image for initialization'); - end - end - % ========================================================================= - case 'qualmeas' - if default - QualMeasOpts={}; - else - if iscellstr(val) - QualMeasOpts=val; - else - error('TIGRE:CGLS:InvalidInput','Invalid quality measurement parameters'); - end - end - case 'verbose' - if default - verbose=1; - else - verbose=val; - end - if ~is2014bOrNewer - warning('TIGRE:Verbose mode not available for older versions than MATLAB R2014b'); - verbose=false; - end - case 'gpuids' - if default - gpuids = GpuIds(); - else - gpuids = val; - end - case 'redundancy_weighting' - if default - redundancy_weights = true; - else - redundancy_weights = val; - end - case 'lambda' - if default - lambda = 0; - else - lambda = val; - end - otherwise - error('TIGRE:CGLS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); - end -end - - -end diff --git a/MATLAB/Demos/d08_Algorithms03.m b/MATLAB/Demos/d08_Algorithms03.m index 5c67dcfe..788b6463 100644 --- a/MATLAB/Demos/d08_Algorithms03.m +++ b/MATLAB/Demos/d08_Algorithms03.m @@ -62,23 +62,37 @@ % 'InitImg' an image for the 'image' initialization. Avoid. % use CGLS -[imgCGLS, errL2CGLS]=CGLS(noise_projections,geo,angles,60); +[imgCGLS, residual_CGLS]=CGLS(noise_projections,geo,angles,60); % use LSQR -[imgLSQR, errL2LSQR]=LSQR(noise_projections,geo,angles,60); +[imgLSQR, residual_LSQR]=LSQR(noise_projections,geo,angles,60); +% use LSMR +[imgLSMR, residual_LSMR]=LSMR(noise_projections,geo,angles,60); +% use LSMR with a lambda value +[imgLSMR_lambda, residual_LSMR_lambda]=LSMR(noise_projections,geo,angles,60,'lambda',10); % SIRT for comparison. -[imgSIRT,errL2SIRT]=SIRT(noise_projections,geo,angles,60); +[imgSIRT, residual_SIRT]=SIRT(noise_projections,geo,angles,60); %% plot results % % We can see that CGLS gets to the same L2 error in less amount of % iterations. -% -plot([errL2SIRT;[errL2CGLS nan(1,length(errL2SIRT)-length(errL2CGLS))];[errL2LSQR nan(1,length(errL2SIRT)-length(errL2LSQR))]]'); -title('L2 error') -legend('SIRT','CGLS','LSQR') +len=max([length(residual_LSQR), + length(residual_CGLS), + length(residual_SIRT), + length(residual_LSMR), + length(residual_LSMR_lambda)]); + + +plot([[residual_SIRT nan(1,len-length(residual_SIRT))]; + [residual_CGLS nan(1,len-length(residual_CGLS))]; + [residual_LSQR nan(1,len-length(residual_LSQR))]; + [residual_LSMR nan(1,len-length(residual_LSMR))]; + [residual_LSMR_lambda nan(1,len-length(residual_LSMR_lambda))]]'); +title('Residual') +legend('SIRT','CGLS','LSQR','LSMR','LSMR lambda') % plot images -plotImg([imgLSQR imgCGLS imgSIRT],'Dim','Z','Step',2) +plotImg([imgLSQR imgCGLS, imgLSMR, imgLSMR_lambda, imgSIRT],'Dim','Z','Step',2) %plot errors -plotImg(abs([head-imgLSQR head-imgCGLS head-imgSIRT]),'Dim','Z','Slice',64) +plotImg(abs([head-imgLSQR head-imgCGLS head-imgLSMR head-imgLSMR_lambda head-imgSIRT]),'Dim','Z','Slice',64,'clims',[0, 0.3]) From 212c2122ff7c370807046ee86a9b626f8ea7aa13 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Thu, 29 Sep 2022 14:11:34 +0100 Subject: [PATCH 07/42] errL2->resL2 --- MATLAB/Algorithms/CGLS.m | 8 ++++---- MATLAB/Algorithms/LSMR.m | 6 +++--- MATLAB/Algorithms/LSQR.m | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/MATLAB/Algorithms/CGLS.m b/MATLAB/Algorithms/CGLS.m index 63336fc2..b1e5b4ad 100644 --- a/MATLAB/Algorithms/CGLS.m +++ b/MATLAB/Algorithms/CGLS.m @@ -1,4 +1,4 @@ -function [x,errorL2,qualMeasOut]= CGLS(proj,geo,angles,niter,varargin) +function [x,resL2,qualMeasOut]= CGLS(proj,geo,angles,niter,varargin) % CGLS solves the CBCT problem using the conjugate gradient least % squares % @@ -53,7 +53,7 @@ gamma=norm(p(:),2)^2; -errorL2=zeros(1,niter); +resL2=zeros(1,niter); for ii=1:niter x0 = x; if (ii==1 && verbose);tic;end @@ -63,13 +63,13 @@ x=x+alpha*p; aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); %expensive, is there any way to check this better? - errorL2(ii)=im3Dnorm(aux,'L2'); + resL2(ii)=im3Dnorm(aux,'L2'); if measurequality qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); end - if ii>1 && errorL2(ii)>errorL2(ii-1) + if ii>1 && resL2(ii)>resL2(ii-1) % OUT! x=x-alpha*p; if verbose diff --git a/MATLAB/Algorithms/LSMR.m b/MATLAB/Algorithms/LSMR.m index ea627cf7..47750133 100644 --- a/MATLAB/Algorithms/LSMR.m +++ b/MATLAB/Algorithms/LSMR.m @@ -1,4 +1,4 @@ -function [x,residual,qualMeasOut]= LSMR(proj,geo,angles,niter,varargin) +function [x,resL2,qualMeasOut]= LSMR(proj,geo,angles,niter,varargin) % LSMR solves the CBCT problem using LSMR. % @@ -77,7 +77,7 @@ d = 0; % msl: is this error the residual norm ? -residual = zeros(1,niter); +resL2 = zeros(1,niter); % (2) Start iterations for ii=1:niter @@ -149,7 +149,7 @@ % (11) Compute ||r_k|| d = d + betacheck^2; gamma_var = d + (betad - taud)^2 + betadd^2; - residual(ii) = sqrt(gamma_var); + resL2(ii) = sqrt(gamma_var); % ||A^T r_k || is just |zetabar| diff --git a/MATLAB/Algorithms/LSQR.m b/MATLAB/Algorithms/LSQR.m index 5e299abb..fb047d66 100644 --- a/MATLAB/Algorithms/LSQR.m +++ b/MATLAB/Algorithms/LSQR.m @@ -1,4 +1,4 @@ -function [x,errorL2,qualMeasOut]= LSQR(proj,geo,angles,niter,varargin) +function [x,resL2,qualMeasOut]= LSQR(proj,geo,angles,niter,varargin) % LSQR solves the CBCT problem using LSQR. % This is mathematically equivalent to CGLS. @@ -70,7 +70,7 @@ % msl: do we want to check for convergence? In well posed problems it would % make sense, not sure now. -errorL2=zeros(1,niter); % msl: is this error the residual norm ? +resL2=zeros(1,niter); % msl: is this error the residual norm ? % (2) Start iterations for ii=1:niter @@ -105,7 +105,7 @@ normr = normr*abs(s); % ||r_k|| = ||b - A x_k|| % Only exact if we do not have orth. loss normAtr = phibar * alpha * abs(c); % msl: Do we want this? ||A^T r_k|| - errorL2(ii)=normr; + resL2(ii)=normr; % (6) Test for convergence. % msl: I still need to implement this. @@ -115,7 +115,7 @@ qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); end - if ii>1 && errorL2(ii)>errorL2(ii-1) % msl: not checked + if ii>1 && resL2(ii)>resL2(ii-1) % msl: not checked % OUT! x=x-alpha*v; if verbose From 4baf9b47b46dcf2f08790da97d39c57abdccbddc Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Thu, 29 Sep 2022 18:11:00 +0100 Subject: [PATCH 08/42] tessorize IRN_TV_CGLS for speed and memory --- MATLAB/Algorithms/IRN_TV_CGLS.m | 148 +++++++++++++++----------------- 1 file changed, 67 insertions(+), 81 deletions(-) diff --git a/MATLAB/Algorithms/IRN_TV_CGLS.m b/MATLAB/Algorithms/IRN_TV_CGLS.m index c495228e..7ef191a6 100644 --- a/MATLAB/Algorithms/IRN_TV_CGLS.m +++ b/MATLAB/Algorithms/IRN_TV_CGLS.m @@ -1,15 +1,15 @@ -function [x_out,errorL2,qualMeasOut]= IRN_TV_CGLS(proj,geo,angles,niter,varargin) -% CGLS solves the CBCT problem using the conjugate gradient least -% squares +function [x_out,resL2,qualMeasOut]= IRN_TV_CGLS(proj,geo,angles,niter,varargin) +% IRN_TV_CGLS solves the IRN_TV_CGLS problem using the conjugate gradient least +% squares with Total Variation regularization, using an inner outer scheme % -% CGLS(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% IRN_TV_CGLS(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem % using the projection data PROJ taken over ANGLES angles, corresponding % to the geometry descrived in GEO, using NITER iterations. % -% CGLS(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% IRN_TV_CGLS(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The % possible options in OPT are: % -% +% 'lambda' Value of regularization parameter lambda, default 10. % 'Init' Describes diferent initialization techniques. % * 'none' : Initializes the image to zeros (default) % * 'FDK' : intializes image to FDK reconstrucition @@ -34,7 +34,7 @@ % % Contact: tigre.toolbox@gmail.com % Codes: https://github.com/CERN/TIGRE/ -% Coded by: Ander Biguri +% Coded by: Malena Sabate Landman, Ander Biguri %-------------------------------------------------------------------------- %% @@ -42,36 +42,15 @@ % % An Iteratively Reweighted Norm Algorithm for Total Variation Regularization % % Paul Rodriguez and Brendt Wohlberg % % 10.1109/ACSSC.2006.354879 -% -% errorL2 = []; -% for i = 1:10 % Needs to change -% % Update weight -% Build L = W * D as an efficient handle. Think about this. -% -% % Call CGLS (Maybe try with LSQR just for fun) -% [x ,errorL2_i,qualMeasOut] = CGLS(proj,geo,angles,niter,varargin); -% -% errorL2 = [errorL2, errorL2_i]; -% % Malena: not sure what qualMeasOut is, so I do not know if it should be stored or -% % updated after every inner cycle -% end - -% % Start coding with an L just to test dimensions -% -% sizeD3_1 = (geo.nVoxel(1)-1)*geo.nVoxel(1)*geo.nVoxel(1) ... -% + geo.nVoxel(1)*(geo.nVoxel(1)-1)*geo.nVoxel(1) ... -% + geo.nVoxel(1)*geo.nVoxel(1)*(geo.nVoxel(1)-1); -% sizeD3_2 = geo.nVoxel(1)*geo.nVoxel(1)*geo.nVoxel(1); -lambda = 10; %% ||Ax-b|| + lambda R(x) - -[verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); +[verbose,x0,QualMeasOpts,gpuids,lambda]=parse_inputs(proj,geo,angles,varargin); +x=x0; niter_outer = 15; measurequality=~isempty(QualMeasOpts); qualMeasOut=zeros(length(QualMeasOpts),niter*niter_outer); -errorL2=zeros(1,niter*niter_outer); +resL2=zeros(1,niter*niter_outer); for iii = 1:niter_outer @@ -80,14 +59,13 @@ % Malena: We could avoid re-doing this, saving the initialisation % Just remember that we need cold restarts - [verbose,x,QualMeasOpts,gpuids] = parse_inputs(proj,geo,angles,varargin); - + x=x0; + prox_aux_1 = Ax(x,geo,angles,'Siddon','gpuids',gpuids); - prox_aux_2 = Lx(W, x); - prox_aux_2 = cellfun(@(x) x*(sqrt(lambda)),prox_aux_2,'un',0); - + prox_aux_2 = Lx(W, x)*sqrt(lambda); + r_aux_1 = proj - prox_aux_1; - r_aux_2 = cellfun(@(x) x*(-1),prox_aux_2,'un',0); + r_aux_2 = -prox_aux_2; % Malena: changed the format, r_aux_2 is 3 % r = cat(3,r_aux_1, r_aux_2); % Malena: size guide, erase later, N x N x (100 + N-1) @@ -103,39 +81,29 @@ if (ii==1 && verbose);tic;end q_aux_1 = Ax(p ,geo,angles,'Siddon','gpuids',gpuids); - q_aux_2 = Lx (W, p); - q_aux_2 = cellfun(@(x) x*sqrt(lambda),q_aux_2,'un',0); + q_aux_2 = Lx (W, p)*sqrt(lambda); % q = cat(3, q_aux_1, q_aux_2{1},q_aux_2{2},q_aux_2{3}); % Probably never need to actually do this % alpha=gamma/norm(q(:),2)^2; - alpha=gamma/(norm(q_aux_1(:))^2 + norm(q_aux_2{1}(:))^2 + norm(q_aux_2{2}(:))^2 + norm(q_aux_2{3}(:))^2); + alpha=gamma/(norm(q_aux_1(:))^2 + im3Dnorm(q_aux_2(:,:,:,1),'L2')^2 + im3Dnorm(q_aux_2(:,:,:,2),'L2')^2 + im3Dnorm(q_aux_2(:,:,:,3),'L2')^2); x=x+alpha*p; - x_out{iii}=x; + x_out=x; aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); % expensive, is there any way to check this better? % residual norm or the original least squares (not Tikhonov). % Think if that is what we want of the NE residual - errorL2((iii-1)*niter+ii)=im3Dnorm(aux,'L2'); + resL2((iii-1)*niter+ii)=im3Dnorm(aux,'L2'); if measurequality qualMeasOut(:,(iii-1)*niter+ii)=Measure_Quality(x0,x,QualMeasOpts); end - % This might happen now, we are minimizing the normal eqns residual -% if ii>1 && errorL2(ii)>errorL2(ii-1) -% % OUT! -% x=x-alpha*p; -% if verbose -% disp(['CGLS stoped in iteration N', num2str(ii),' due to divergence.']) -% end -% return; -% end % If step is adecuate, then continue withg CGLS r_aux_1 = r_aux_1-alpha*q_aux_1; - r_aux_2 = cellfun(@(a,b)a-alpha*b, r_aux_2, q_aux_2, 'uni',0); + r_aux_2=r_aux_2-alpha*q_aux_2; s_aux_1 = Atb(r_aux_1 ,geo,angles,'matched','gpuids',gpuids); s_aux_2 = sqrt(lambda) * Ltx (W, r_aux_2); @@ -148,7 +116,7 @@ if (iii==1 && ii ==1 && verbose) expected_time=toc*niter*niter_outer; - disp('CGLS'); + disp('ISN_TV_CGLS'); disp(['Expected duration : ',secs2hms(expected_time)]); disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); disp(''); @@ -182,9 +150,13 @@ function W = build_weights ( x) % Directional discrete derivatives % Reflective boundary conditions - Dxx=cat(1,x(1:end-1,:,:)-x(2:end,:,:),zeros(1,size(x,2),size(x,3))); - Dyx=cat(2,x(:,1:end-1,:)-x(:,2:end,:),zeros(size(x,1),1,size(x,3))); - Dzx=cat(3,x(:,:,1:end-1)-x(:,:,2:end),zeros(size(x,1),size(x,2),1)); + Dxx=zeros(size(x),'single'); + Dyx=zeros(size(x),'single'); + Dzx=zeros(size(x),'single'); + + Dxx(1:end-1,:,:)=x(1:end-1,:,:)-x(2:end,:,:); + Dyx(:,1:end-1,:)=x(:,1:end-1,:)-x(:,2:end,:); + Dzx(:,:,1:end-1)=x(:,:,1:end-1)-x(:,:,2:end); W = (Dxx.^2+Dyx.^2+Dzx.^2+1e-6).^(-1/4); % Fix this... @@ -193,40 +165,48 @@ function out = Lx (W, x) % Directional discrete derivatives % Reflective boundary conditions - Dxx=cat(1,x(1:end-1,:,:)-x(2:end,:,:),zeros(1,size(x,2),size(x,3))); - Dyx=cat(2,x(:,1:end-1,:)-x(:,2:end,:),zeros(size(x,1),1,size(x,3))); - Dzx=cat(3,x(:,:,1:end-1)-x(:,:,2:end),zeros(size(x,1),size(x,2),1)); - + Dxx=zeros(size(x),'single'); + Dyx=zeros(size(x),'single'); + Dzx=zeros(size(x),'single'); + + Dxx(1:end-1,:,:)=x(1:end-1,:,:)-x(2:end,:,:); + Dyx(:,1:end-1,:)=x(:,1:end-1,:)-x(:,2:end,:); + Dzx(:,:,1:end-1)=x(:,:,1:end-1)-x(:,:,2:end); % Build weights - is it better to find the right rotation and add % tensors? - WDxx = W .* Dxx; - WDyx = W .* Dyx; - WDzx = W .* Dzx; - - out = {WDxx WDyx WDzx}; + out = cat(4,W .* Dxx, W .* Dyx, W .* Dzx); end -function out = Ltx (W, x) % Input is a cell - Wx_1 = W .* x{1}; - Wx_2 = W .* x{2}; - Wx_3 = W .* x{3}; +function out = Ltx (W, x) + Wx_1 = W .* x(:,:,:,1); + Wx_2 = W .* x(:,:,:,2); + Wx_3 = W .* x(:,:,:,3); % Left here, but this is how to make a transpose - DxtWx_1=cat(1,Wx_1(1,:,:),Wx_1(2:end-1,:,:)-Wx_1(1:end-2,:,:),-Wx_1(end-1,:,:)); - DytWx_2=cat(2,Wx_2(:,1,:),Wx_2(:,2:end-1,:)-Wx_2(:,1:end-2,:),-Wx_2(:,end-1,:)); - DztWx_3=cat(3,Wx_3(:,:,1),Wx_3(:,:,2:end-1)-Wx_3(:,:,1:end-2),-Wx_3(:,:,end-1)); + DxtWx_1=Wx_1; + DytWx_2=Wx_2; + DztWx_3=Wx_3; + + DxtWx_1(2:end-1,:,:)=Wx_1(2:end-1,:,:)-Wx_1(1:end-2,:,:); + DxtWx_1(end,:,:)=-Wx_2(end-1,:,:); + + DytWx_2(:,2:end-1,:)=Wx_2(:,2:end-1,:)-Wx_2(:,1:end-2,:); + DytWx_2(:,end,:)=-Wx_2(:,end-1,:); + + DztWx_3(:,:,2:end-1)=Wx_3(:,:,2:end-1)-Wx_3(:,:,1:end-2); + DztWx_3(:,:,end)=-Wx_3(:,:,end-1); out = DxtWx_1 + DytWx_2 + DztWx_3; end %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids'}; +function [verbose,x,QualMeasOpts,gpuids,lambda]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','lambda'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); if mod(nVarargs,2) - error('TIGRE:CGLS:InvalidInput','Invalid number of inputs') + error('TIGRE:IRN_TV_CGLS:InvalidInput','Invalid number of inputs') end % check if option has been passed as input @@ -235,7 +215,7 @@ if ~isempty(ind) defaults(ind)=0; else - error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + error('TIGRE:IRN_TV_CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); end end @@ -250,7 +230,7 @@ jj=jj+1; end if isempty(ind) - error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + error('TIGRE:IRN_TV_CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); end val=argin{jj}; end @@ -275,7 +255,7 @@ continue; end if isempty(x) - error('TIGRE:CGLS:InvalidInput','Invalid Init option') + error('TIGRE:IRN_TV_CGLS:InvalidInput','Invalid Init option') end % % % % % % % ERROR case 'initimg' @@ -286,7 +266,7 @@ if isequal(size(val),geo.nVoxel') x=single(val); else - error('TIGRE:CGLS:InvalidInput','Invalid image for initialization'); + error('TIGRE:IRN_TV_CGLS:InvalidInput','Invalid image for initialization'); end end % ========================================================================= @@ -297,7 +277,7 @@ if iscellstr(val) QualMeasOpts=val; else - error('TIGRE:CGLS:InvalidInput','Invalid quality measurement parameters'); + error('TIGRE:IRN_TV_CGLS:InvalidInput','Invalid quality measurement parameters'); end end case 'verbose' @@ -316,8 +296,14 @@ else gpuids = val; end + case 'lambda' + if default + lambda=10; + else + lambda=val; + end otherwise - error('TIGRE:CGLS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); + error('TIGRE:IRN_TV_CGLS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in IRN_TV_CGLS()']); end end From df25b2843c355046232f91ecab0f5be489c2eaca Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Fri, 30 Sep 2022 16:11:50 +0100 Subject: [PATCH 09/42] Add LSQR to python --- Python/tigre/algorithms/__init__.py | 1 + .../algorithms/krylov_subspace_algorithms.py | 91 +++++++++++++++---- 2 files changed, 74 insertions(+), 18 deletions(-) diff --git a/Python/tigre/algorithms/__init__.py b/Python/tigre/algorithms/__init__.py index 1d53f0de..6aef9813 100644 --- a/Python/tigre/algorithms/__init__.py +++ b/Python/tigre/algorithms/__init__.py @@ -11,6 +11,7 @@ from .ista_algorithms import ista from .iterative_recon_alg import iterativereconalg from .krylov_subspace_algorithms import cgls +from .krylov_subspace_algorithms import lsqr from .pocs_algorithms import asd_pocs from .pocs_algorithms import os_asd_pocs from .pocs_algorithms import awasd_pocs diff --git a/Python/tigre/algorithms/krylov_subspace_algorithms.py b/Python/tigre/algorithms/krylov_subspace_algorithms.py index fd78c90c..367543f1 100644 --- a/Python/tigre/algorithms/krylov_subspace_algorithms.py +++ b/Python/tigre/algorithms/krylov_subspace_algorithms.py @@ -30,20 +30,9 @@ def __init__(self, proj, geo, angles, niter, **kwargs): # Don't precompute V and W. kwargs.update(dict(W=None, V=None)) kwargs.update(dict(blocksize=angles.shape[0])) - self.log_parameters = False self.re_init_at_iteration = 0 IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) - if self.log_parameters: - parameter_history = {} - iterations = self.niter - parameter_history["alpha"] = np.zeros([iterations], dtype=np.float32) - parameter_history["beta"] = np.zeros([iterations], dtype=np.float32) - parameter_history["gamma"] = np.zeros([iterations], dtype=np.float32) - parameter_history["q_norm"] = np.zeros([iterations], dtype=np.float32) - parameter_history["s_norm"] = np.zeros([iterations], dtype=np.float32) - self.parameter_history = parameter_history - self.__r__ = self.proj - Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids) self.__p__ = Atb(self.__r__, self.geo, self.angles, backprojection_type="matched", gpuids=self.gpuids) p_norm = np.linalg.norm(self.__p__.ravel(), 2) @@ -100,12 +89,6 @@ def run_main_iter(self): gamma1 = s_norm * s_norm beta = gamma1 / self.__gamma__ - if self.log_parameters: - self.parameter_history["alpha"][i] = alpha - self.parameter_history["beta"][i] = beta - self.parameter_history["gamma"][i] = self.__gamma__ - self.parameter_history["q_norm"][i] = q_norm - self.parameter_history["s_norm"][i] = s_norm self.__gamma__ = gamma1 self.__p__ = s + beta * self.__p__ @@ -119,5 +102,77 @@ def run_main_iter(self): + "(s)" ) - cgls = decorator(CGLS, name="cgls") + +class LSQR(IterativeReconAlg): + __doc__ = ( + " LSQR solves the CBCT problem using the least squares\n" + " LSQR(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem\n" + " using the projection data PROJ taken over ALPHA angles, corresponding\n" + " to the geometry descrived in GEO, using NITER iterations." + ) + IterativeReconAlg.__doc__ + + def __init__(self, proj, geo, angles, niter, **kwargs): + # Don't precompute V and W. + kwargs.update(dict(W=None, V=None)) + kwargs.update(dict(blocksize=angles.shape[0])) + self.re_init_at_iteration = 0 + IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) + # Paige and Saunders //doi.org/10.1145/355984.355989 + + # Enumeration as given in the paper for 'Algorithm LSQR' + # (1) Initialize + self.__u__=self.proj - Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids) + + normr = np.linalg.norm(self.__u__.ravel(), 2) + self.__u__ = self.__u__/normr + + self.__beta__ = normr + self.__phibar__ = normr + self.__v__ = Atb(self.__u__, self.geo, self.angles, backprojection_type="matched", gpuids=self.gpuids) + + self.__alpha__ =np.linalg.norm(self.__v__.ravel(), 2) + self.__v__ = self.__v__/self.__alpha__ + self.__rhobar__ = self.__alpha__ + self.__w__ = np.copy(self.__v__) + + def run_main_iter(self): + self.l2l = np.zeros((1, self.niter), dtype=np.float32) + avgtime = [] + for i in range(self.niter): + if self.verbose: + self._estimate_time_until_completion(i) + if self.Quameasopts is not None: + res_prev = copy.deepcopy(self.res) + avgtic = default_timer() + + #% (3)(a) + self.__u__ = tigre.Ax(self.__v__, self.geo, self.angles, "Siddon", gpuids=self.gpuids) - self.__alpha__*self.__u__ + self.__beta__ = np.linalg.norm(self.__u__.ravel(),2) + self.__u__ = self.__u__ / self.__beta__ + + #% (3)(b) + self.__v__ = tigre.Atb(self.__u__, self.geo, self.angles, backprojection_type="matched", gpuids=self.gpuids) - self.__beta__*self.__v__ + self.__alpha__ = np.linalg.norm(self.__v__.ravel(),2) + self.__v__ = self.__v__ / self.__alpha__ + + #% (4)(a-g) + rho = np.sqrt(self.__rhobar__**2 + self.__beta__**2) + c = self.__rhobar__ / rho + s = self.__beta__ / rho + theta = s * self.__alpha__ + self.__rhobar__ = - c * self.__alpha__ + phi = c * self.__phibar__ + self.__phibar__ = s * self.__phibar__ + + #% (5) Update x, w + self.res = self.res + (phi / rho) * self.__w__ + self.__w__ = self.__v__ - (theta / rho) * self.__w__ + + avgtoc = default_timer() + avgtime.append(abs(avgtic - avgtoc)) + + if self.Quameasopts is not None: + self.error_measurement(res_prev, i) + +lsqr = decorator(LSQR, name="lsqr") From c1db6fdfa621fcdc3ae8aff41f50cf12ca1f7003 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Fri, 30 Sep 2022 16:49:58 +0100 Subject: [PATCH 10/42] Add LSMR to python --- Python/tigre/algorithms/__init__.py | 4 + .../algorithms/krylov_subspace_algorithms.py | 131 +++++++++++++++++- 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/Python/tigre/algorithms/__init__.py b/Python/tigre/algorithms/__init__.py index 6aef9813..e19036d7 100644 --- a/Python/tigre/algorithms/__init__.py +++ b/Python/tigre/algorithms/__init__.py @@ -12,6 +12,8 @@ from .iterative_recon_alg import iterativereconalg from .krylov_subspace_algorithms import cgls from .krylov_subspace_algorithms import lsqr +from .krylov_subspace_algorithms import lsmr + from .pocs_algorithms import asd_pocs from .pocs_algorithms import os_asd_pocs from .pocs_algorithms import awasd_pocs @@ -42,6 +44,8 @@ "os_aw_pcsd", "fbp", "cgls", + "lsqr", + "lsmr", "fista", "ista", "mlem", diff --git a/Python/tigre/algorithms/krylov_subspace_algorithms.py b/Python/tigre/algorithms/krylov_subspace_algorithms.py index 367543f1..34fcd0b7 100644 --- a/Python/tigre/algorithms/krylov_subspace_algorithms.py +++ b/Python/tigre/algorithms/krylov_subspace_algorithms.py @@ -171,8 +171,137 @@ def run_main_iter(self): avgtoc = default_timer() avgtime.append(abs(avgtic - avgtoc)) - + if self.Quameasopts is not None: self.error_measurement(res_prev, i) lsqr = decorator(LSQR, name="lsqr") + + +class LSMR(IterativeReconAlg): + __doc__ = ( + " LSMR solves the CBCT problem using LSMR\n" + " LSMR(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem\n" + " using the projection data PROJ taken over ALPHA angles, corresponding\n" + " to the geometry descrived in GEO, using NITER iterations." + ) + IterativeReconAlg.__doc__ + + def __init__(self, proj, geo, angles, niter, **kwargs): + # Don't precompute V and W. + kwargs.update(dict(W=None, V=None)) + kwargs.update(dict(blocksize=angles.shape[0])) + self.re_init_at_iteration = 0 + IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) + #% David Chin-Lung Fong and Michael Saunders //doi.org/10.1137/10079687X + #% Enumeration as given in the paper for 'Algorithm LSMR' + #% (1) Initialize + self.__u__=self.proj - Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids) + normr = np.linalg.norm(self.__u__.ravel(), 2) + self.__beta__ = normr + self.__u__ = self.__u__/normr + + self.__v__ = Atb(self.__u__, self.geo, self.angles, backprojection_type="matched", gpuids=self.gpuids) + self.__alpha__ =np.linalg.norm(self.__v__.ravel(), 2) + self.__v__ = self.__v__/self.__alpha__ + + self.__alphabar__ = self.__alpha__ + self.__zetabar__ = self.__alpha__ * self.__beta__ + self.__rho__ = 1 + self.__rhobar__ = 1 + self.__cbar__ = 1 + self.__sbar__ = 0 + self.__h__ = self.__v__ + self.__hbar__ = 0 + + #% Compute the residual norm ||r_k|| + self.__betadd__ = self.__beta__ + self.__betad__ = 0 + self.__rhod__ = 1 + self.__tautilda__ = 0 + self.__thetatilda__ = 0 + self.__zeta__ = 0 + self.__d__ = 0 + + def run_main_iter(self): + self.l2l = np.zeros((1, self.niter), dtype=np.float32) + avgtime = [] + for i in range(self.niter): + if self.verbose: + self._estimate_time_until_completion(i) + if self.Quameasopts is not None: + res_prev = copy.deepcopy(self.res) + avgtic = default_timer() + + #% (3) Continue the bidiagonalization + self.__u__ = tigre.Ax(self.__v__, self.geo, self.angles, "Siddon", gpuids=self.gpuids) - self.__alpha__*self.__u__ + self.__beta__ = np.linalg.norm(self.__u__.ravel(),2) + self.__u__ = self.__u__ / self.__beta__ + + self.__v__ = tigre.Atb(self.__u__, self.geo, self.angles, backprojection_type="matched", gpuids=self.gpuids) - self.__beta__*self.__v__ + self.__alpha__ = np.linalg.norm(self.__v__.ravel(),2) + self.__v__ = self.__v__ / self.__alpha__ + + #% (4) Construct and apply rotation \hat{P}_k + alphahat = np.sqrt(self.__alphabar__**2 + self.lmbda**2) + chat = self.__alphabar__/alphahat + shat = self.lmbda/alphahat + + #% (5) Construct and apply rotation P_k + rhopre = self.__rho__; + self.__rho__ = np.sqrt(alphahat**2 + self.__beta__**2) + c = alphahat / self.__rho__ + s = self.__beta__ / self.__rho__ + theta = s * self.__alpha__ + self.__alphabar__ = c * self.__alpha__ + + #% (6) Construct and apply rotation \bar{P}_k + thetabar = self.__sbar__ * self.__rho__ + rhobarpre = self.__rhobar__ + self.__rhobar__ = np.sqrt((self.__cbar__ *self.__rho__)**2 + theta**2) + self.__cbar__ = self.__cbar__ * self.__rho__ / self.__rhobar__ + self.__sbar__ = theta / self.__rhobar__ + zetapre = self.__zeta__ + self.__zeta__ = self.__cbar__ * self.__zetabar__ + self.__zetabar__ = -self.__sbar__ * self.__zetabar__ + + #% (7) Update \bar{h}, x, h + self.__hbar__ = self.__h__ - (thetabar*self.__rho__/(rhopre*rhobarpre))*self.__hbar__ + self.res = self.res + (self.__zeta__ / (self.__rho__*self.__rhobar__)) * self.__hbar__ + self.__h__ = self.__v__ - (theta / self.__rho__) * self.__h__ + + #% (8) Apply rotation \hat{P}_k, P_k + betaacute = chat* self.__betadd__ + betacheck = - shat* self.__betadd__ + + #% Computing ||r_k|| + + betahat = c * betaacute + betadd = -s * betaacute + + #% Update estimated quantities of interest. + #% (9) If k >= 2, construct and apply \tilde{P} to compute ||r_k|| + rhotilda = np.sqrt(self.__rhod__**2 + thetabar**2) + ctilda = self.__rhod__ / rhotilda + stilda = thetabar / rhotilda + thetatildapre = self.__thetatilda__ + self.__thetatilda__ = stilda * self.__rhobar__ + self.__rhod__ = ctilda * self.__rhobar__ + #% betatilda = ctilda * betad + stilda * betahat; % msl: in the orinal paper, but not used + self.__betad__ = -stilda * self.__betad__ + ctilda * betahat + + #% (10) Update \tilde{t}_k by forward substitution + self.__tautilda__ = (zetapre - thetatildapre* self.__tautilda__) / rhotilda + taud = (self.__zeta__ - self.__thetatilda__*self.__tautilda__) / self.__rhod__ + + #% (11) Compute ||r_k|| + self.__d__ = self.__d__ + betacheck**2 + gamma_var = self.__d__ + (self.__betad__ - taud)**2 + betadd**2 + self.l2l[0, i] = np.sqrt(gamma_var) + + avgtoc = default_timer() + avgtime.append(abs(avgtic - avgtoc)) + + if self.Quameasopts is not None: + self.error_measurement(res_prev, i) + +lsmr = decorator(LSMR, name="lsmr") From e9423324480b0d05f30cfcaae2f68948632e1719 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Sat, 1 Oct 2022 11:20:29 +0100 Subject: [PATCH 11/42] Add IRN_TV_CGLS to python --- MATLAB/Algorithms/IRN_TV_CGLS.m | 31 +++-- Python/tigre/algorithms/__init__.py | 2 +- .../algorithms/krylov_subspace_algorithms.py | 125 ++++++++++++++++++ 3 files changed, 144 insertions(+), 14 deletions(-) diff --git a/MATLAB/Algorithms/IRN_TV_CGLS.m b/MATLAB/Algorithms/IRN_TV_CGLS.m index 7ef191a6..14e97aba 100644 --- a/MATLAB/Algorithms/IRN_TV_CGLS.m +++ b/MATLAB/Algorithms/IRN_TV_CGLS.m @@ -43,16 +43,16 @@ % % Paul Rodriguez and Brendt Wohlberg % % 10.1109/ACSSC.2006.354879 -[verbose,x0,QualMeasOpts,gpuids,lambda]=parse_inputs(proj,geo,angles,varargin); +[verbose,x0,QualMeasOpts,gpuids,lambda,niter_outer]=parse_inputs(proj,geo,angles,varargin); x=x0; -niter_outer = 15; measurequality=~isempty(QualMeasOpts); qualMeasOut=zeros(length(QualMeasOpts),niter*niter_outer); resL2=zeros(1,niter*niter_outer); for iii = 1:niter_outer + if (iii==1 && verbose);tic;end % weights are N1 x N2 x N3 W = build_weights (x); @@ -78,7 +78,6 @@ for ii=1:niter x0 = x; - if (ii==1 && verbose);tic;end q_aux_1 = Ax(p ,geo,angles,'Siddon','gpuids',gpuids); q_aux_2 = Lx (W, p)*sqrt(lambda); @@ -114,16 +113,16 @@ gamma=gamma1; p=s+beta*p; - if (iii==1 && ii ==1 && verbose) - expected_time=toc*niter*niter_outer; - disp('ISN_TV_CGLS'); - disp(['Expected duration : ',secs2hms(expected_time)]); - disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); - disp(''); - end end + if (iii==1 && ii ==1 && verbose) + expected_time=toc*niter_outer; + disp('ISN_TV_CGLS'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end end end % % % Non-sense now, just giving output of the right dimensions @@ -188,7 +187,7 @@ DztWx_3=Wx_3; DxtWx_1(2:end-1,:,:)=Wx_1(2:end-1,:,:)-Wx_1(1:end-2,:,:); - DxtWx_1(end,:,:)=-Wx_2(end-1,:,:); + DxtWx_1(end,:,:)=-Wx_1(end-1,:,:); DytWx_2(:,2:end-1,:)=Wx_2(:,2:end-1,:)-Wx_2(:,1:end-2,:); DytWx_2(:,end,:)=-Wx_2(:,end-1,:); @@ -199,8 +198,8 @@ out = DxtWx_1 + DytWx_2 + DztWx_3; end %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids,lambda]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids','lambda'}; +function [verbose,x,QualMeasOpts,gpuids,lambda,niter_outer]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','lambda','niter_outer'}; defaults=ones(length(opts),1); % Check inputs @@ -257,6 +256,12 @@ if isempty(x) error('TIGRE:IRN_TV_CGLS:InvalidInput','Invalid Init option') end + case 'niter_outer' + if default + niter_outer=15 + else + niter_outer=val + end % % % % % % % ERROR case 'initimg' if default diff --git a/Python/tigre/algorithms/__init__.py b/Python/tigre/algorithms/__init__.py index e19036d7..2f59475e 100644 --- a/Python/tigre/algorithms/__init__.py +++ b/Python/tigre/algorithms/__init__.py @@ -13,7 +13,7 @@ from .krylov_subspace_algorithms import cgls from .krylov_subspace_algorithms import lsqr from .krylov_subspace_algorithms import lsmr - +from .krylov_subspace_algorithms import irn_tv_cgls from .pocs_algorithms import asd_pocs from .pocs_algorithms import os_asd_pocs from .pocs_algorithms import awasd_pocs diff --git a/Python/tigre/algorithms/krylov_subspace_algorithms.py b/Python/tigre/algorithms/krylov_subspace_algorithms.py index 34fcd0b7..ba0f3166 100644 --- a/Python/tigre/algorithms/krylov_subspace_algorithms.py +++ b/Python/tigre/algorithms/krylov_subspace_algorithms.py @@ -305,3 +305,128 @@ def run_main_iter(self): self.error_measurement(res_prev, i) lsmr = decorator(LSMR, name="lsmr") + +class IRN_TV_CGLS(IterativeReconAlg): + __doc__ = ( + " IRN_TV_CGLS solves the CBCT problem using CGLS with TV constraints\n" + " IRN_TV_CGLS(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem\n" + " using the projection data PROJ taken over ALPHA angles, corresponding\n" + " to the geometry descrived in GEO, using NITER iterations." + ) + IterativeReconAlg.__doc__ + + def __init__(self, proj, geo, angles, niter, **kwargs): + # Don't precompute V and W. + kwargs.update(dict(W=None, V=None)) + kwargs.update(dict(blocksize=angles.shape[0])) + self.re_init_at_iteration = 0 + IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) + + self.niter_outer=15 #TODO, make it kwargs. + + def __build_weights__(self): + Dxx=np.copy(self.res) + Dyx=np.copy(self.res) + Dzx=np.copy(self.res) + + Dxx[0:-2,:,:]=self.res[0:-2,:,:]-self.res[1:-1,:,:] + Dyx[:,0:-2,:]=self.res[:,0:-2,:]-self.res[:,1:-1,:] + Dzx[:,:,0:-2]=self.res[:,:,0:-2]-self.res[:,:,1:-1] + + return (Dxx**2+Dyx**2+Dzx**2+1e-6)**(-1/4) + + def Lx(self,W,img): + Dxx=np.copy(img) + Dyx=np.copy(img) + Dzx=np.copy(img) + + Dxx[0:-2,:,:]=img[0:-2,:,:]-img[1:-1,:,:] + Dyx[:,0:-2,:]=img[:,0:-2,:]-img[:,1:-1,:] + Dzx[:,:,0:-2]=img[:,:,0:-2]-img[:,:,1:-1] + + return np.stack((W*Dxx,W*Dyx,W*Dzx),axis=-1) + + def Ltx(self,W,img3): + Wx_1 = W * img3[:,:,:,0] + Wx_2 = W * img3[:,:,:,1] + Wx_3 = W * img3[:,:,:,2] + + DxtWx_1=Wx_1 + DytWx_2=Wx_2 + DztWx_3=Wx_3 + + DxtWx_1[1:-2,:,:]=Wx_1[1:-2,:,:]-Wx_1[0:-3,:,:] + DxtWx_1[-1,:,:]=-Wx_1[-2,:,:] + + DytWx_2[:,1:-2,:]=Wx_2[:,1:-2,:]-Wx_2[:,0:-3,:] + DytWx_2[:,-1,:]=-Wx_2[:,-2,:] + + DztWx_3[:,:,1:-2]=Wx_3[:,:,1:-2]-Wx_3[:,:,0:-3] + DztWx_3[:,:,-1]=-Wx_3[:,:,-2] + + return DxtWx_1 + DytWx_2 + DztWx_3 + + def run_main_iter(self): + self.l2l = np.zeros((1, self.niter*self.niter_outer), dtype=np.float32) + avgtime = [] + + res0=self.res + for outer in range(self.niter_outer): + if self.verbose: + self._estimate_time_until_completion(outer) + if self.Quameasopts is not None: + res_prev = copy.deepcopy(self.res) + avgtic = default_timer() + + + W=self.__build_weights__() + self.res=res0 + + prox_aux_1 =Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids) + prox_aux_2 = self.Lx(W,self.res)*np.sqrt(self.lmbda) + + r_aux_1 = self.proj - prox_aux_1 + r_aux_2 = -prox_aux_2 + #% Malena: changed the format, r_aux_2 is 3 + #% r = cat(3,r_aux_1, r_aux_2); % Malena: size guide, erase later, N x N x (100 + N-1) + p_aux_1 = tigre.Atb(r_aux_1, self.geo, self.angles, backprojection_type="matched", gpuids=self.gpuids) + p_aux_2 = np.sqrt(self.lmbda)*self.Ltx(W, r_aux_2) + p = p_aux_1 + p_aux_2 + + gamma=np.linalg.norm(p.ravel(),2)**2 + + for i in range(self.niter): + res0=self.res + + q_aux_1 = tigre.Ax(p, self.geo, self.angles, "Siddon", gpuids=self.gpuids) + q_aux_2 = self.Lx(W,p)*np.sqrt(self.lmbda) + + #% q = cat(3, q_aux_1, q_aux_2{1},q_aux_2{2},q_aux_2{3}); % Probably never need to actually do this + #% alpha=gamma/norm(q(:),2)^2; + alpha=gamma/(np.linalg.norm(q_aux_1.ravel(),2)**2 + np.linalg.norm(q_aux_2[:,:,:,0].ravel(),2)**2 + np.linalg.norm(q_aux_2[:,:,:,1].ravel(),2)**2+np.linalg.norm(q_aux_2[:,:,:,2].ravel(),2)**2) + self.res=self.res+alpha*p + aux=self.proj-tigre.Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids) + #% residual norm or the original least squares (not Tikhonov). + #% Think if that is what we want of the NE residual + self.l2l[0, outer*self.niter+i] = np.linalg.norm(aux.ravel(),2) + + + #% If step is adecuate, then continue withg CGLS + r_aux_1 = r_aux_1-alpha*q_aux_1 + r_aux_2=r_aux_2-alpha*q_aux_2 + + s_aux_1 = tigre.Atb(r_aux_1, self.geo, self.angles, backprojection_type="matched", gpuids=self.gpuids) + s_aux_2 = np.sqrt(self.lmbda) * self.Ltx(W, r_aux_2) + s = s_aux_1 + s_aux_2 + + gamma1=np.linalg.norm(s.ravel(),2)**2 + beta=gamma1/gamma + gamma=gamma1 + p=s+beta*p + + avgtoc = default_timer() + avgtime.append(abs(avgtic - avgtoc)) + + if self.Quameasopts is not None: + self.error_measurement(res_prev, outer) + +irn_tv_cgls = decorator(IRN_TV_CGLS, name="irn_tv_cgls") From 5b78a31aa71487ca2916ee995a5a6097b44d2880 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Mon, 3 Oct 2022 10:25:08 +0100 Subject: [PATCH 12/42] Update MATLAB demo for IRN_TV_CGLS --- MATLAB/Algorithms/IRN_TV_CGLS.m | 6 +++--- MATLAB/Demos/d09_Algorithms04.m | 22 ++++++++++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/MATLAB/Algorithms/IRN_TV_CGLS.m b/MATLAB/Algorithms/IRN_TV_CGLS.m index 14e97aba..54d535bc 100644 --- a/MATLAB/Algorithms/IRN_TV_CGLS.m +++ b/MATLAB/Algorithms/IRN_TV_CGLS.m @@ -116,7 +116,7 @@ end - if (iii==1 && ii ==1 && verbose) + if (iii==1 && verbose) expected_time=toc*niter_outer; disp('ISN_TV_CGLS'); disp(['Expected duration : ',secs2hms(expected_time)]); @@ -258,9 +258,9 @@ end case 'niter_outer' if default - niter_outer=15 + niter_outer=15; else - niter_outer=val + niter_outer=val; end % % % % % % % ERROR case 'initimg' diff --git a/MATLAB/Demos/d09_Algorithms04.m b/MATLAB/Demos/d09_Algorithms04.m index a02e0da9..5ff74d90 100644 --- a/MATLAB/Demos/d09_Algorithms04.m +++ b/MATLAB/Demos/d09_Algorithms04.m @@ -191,6 +191,24 @@ imgSARTTV=SART_TV(noise_projections,geo,angles,50,'TViter',100,'TVlambda',50); + +% IRN_TV_CGLS +%========================================================================== +%========================================================================== +% MALENA, CAN YOU ADD A SMALL TEXT HERE? EXPLAINING THE ALGORITHM IN 3 +% LINES OR SO +% +% 'lambda' hyperparameter in TV norm. It gives the ratio of +% importance of the image vs the minimum total variation. +% default is 15. Lower means less TV denoising. +% +% 'niter_outer' Number of outer iterations. Each outer iteration will +% perform niter number of inner iterations, in the example +% below, 20.Albeit this seems that it does many more +% iterations than the other algorithms, this is an inherently +% faster algorithm, both in convergence and time. + +imgIRN_TV_CGLS=IRN_TV_CGLS(noise_projections,geo,angles,20,'lambda',15); %% Lets visualize the results % Notice the smoother images due to TV regularization. % @@ -198,10 +216,10 @@ % % OSC-TV B-ASD-POCS-beta SART-TV -plotImg([ imgOSASDPOCS imgBASDPOCSbeta imgSARTTV; head imgOSSART imgASDPOCS ] ,'Dim','Z','Step',2,'clims',[0 1]) +plotImg([ imgOSASDPOCS imgBASDPOCSbeta imgSARTTV; imgIRN_TV_CGLS imgOSSART imgASDPOCS ] ,'Dim','Z','Step',2,'clims',[0 1]) % error -plotImg(abs([ head-imgOSASDPOCS head-imgBASDPOCSbeta head-imgSARTTV;head-head head-imgOSSART head-imgASDPOCS ]) ,'Dim','Z','Slice',64) +plotImg(abs([ head-imgOSASDPOCS head-imgBASDPOCSbeta head-imgSARTTV;head-imgIRN_TV_CGLS head-imgOSSART head-imgASDPOCS ]) ,'Dim','Z','Slice',64,'clims',[0 0.1]) From 9aa9eb55aac01fb79fca0d079dc0b5853f4256d3 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Mon, 3 Oct 2022 12:03:28 +0100 Subject: [PATCH 13/42] Add optional parameters to krylov --- Python/tigre/algorithms/iterative_recon_alg.py | 4 +++- .../algorithms/krylov_subspace_algorithms.py | 15 +++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Python/tigre/algorithms/iterative_recon_alg.py b/Python/tigre/algorithms/iterative_recon_alg.py index ad288db6..a03dc846 100644 --- a/Python/tigre/algorithms/iterative_recon_alg.py +++ b/Python/tigre/algorithms/iterative_recon_alg.py @@ -150,6 +150,7 @@ def __init__(self, proj, geo, angles, niter, **kwargs): name="Iterative Reconstruction", sup_kw_warning=False, gpuids=None, + niter_outer=15 ) allowed_keywords = [ "V", @@ -167,7 +168,8 @@ def __init__(self, proj, geo, angles, niter, **kwargs): "tvlambda", "hyper", "fista_p", - "fista_q" + "fista_q", + "niter_outer" ] self.__dict__.update(options) self.__dict__.update(**kwargs) diff --git a/Python/tigre/algorithms/krylov_subspace_algorithms.py b/Python/tigre/algorithms/krylov_subspace_algorithms.py index ba0f3166..899d3054 100644 --- a/Python/tigre/algorithms/krylov_subspace_algorithms.py +++ b/Python/tigre/algorithms/krylov_subspace_algorithms.py @@ -321,7 +321,6 @@ def __init__(self, proj, geo, angles, niter, **kwargs): self.re_init_at_iteration = 0 IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) - self.niter_outer=15 #TODO, make it kwargs. def __build_weights__(self): Dxx=np.copy(self.res) @@ -343,12 +342,12 @@ def Lx(self,W,img): Dyx[:,0:-2,:]=img[:,0:-2,:]-img[:,1:-1,:] Dzx[:,:,0:-2]=img[:,:,0:-2]-img[:,:,1:-1] - return np.stack((W*Dxx,W*Dyx,W*Dzx),axis=-1) + return np.stack((W*Dxx,W*Dyx,W*Dzx),axis=0) def Ltx(self,W,img3): - Wx_1 = W * img3[:,:,:,0] - Wx_2 = W * img3[:,:,:,1] - Wx_3 = W * img3[:,:,:,2] + Wx_1 = W * img3[0,:,:,:] + Wx_2 = W * img3[1,:,:,:] + Wx_3 = W * img3[2,:,:,:] DxtWx_1=Wx_1 DytWx_2=Wx_2 @@ -370,9 +369,13 @@ def run_main_iter(self): avgtime = [] res0=self.res + for outer in range(self.niter_outer): if self.verbose: + niter=self.niter + self.niter=self.niter_outer self._estimate_time_until_completion(outer) + self.niter=niter if self.Quameasopts is not None: res_prev = copy.deepcopy(self.res) avgtic = default_timer() @@ -402,7 +405,7 @@ def run_main_iter(self): #% q = cat(3, q_aux_1, q_aux_2{1},q_aux_2{2},q_aux_2{3}); % Probably never need to actually do this #% alpha=gamma/norm(q(:),2)^2; - alpha=gamma/(np.linalg.norm(q_aux_1.ravel(),2)**2 + np.linalg.norm(q_aux_2[:,:,:,0].ravel(),2)**2 + np.linalg.norm(q_aux_2[:,:,:,1].ravel(),2)**2+np.linalg.norm(q_aux_2[:,:,:,2].ravel(),2)**2) + alpha=gamma/(np.linalg.norm(q_aux_1.ravel(),2)**2 + np.linalg.norm(q_aux_2[0].ravel(),2)**2 + np.linalg.norm(q_aux_2[1].ravel(),2)**2+np.linalg.norm(q_aux_2[2].ravel(),2)**2) self.res=self.res+alpha*p aux=self.proj-tigre.Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids) #% residual norm or the original least squares (not Tikhonov). From 82084a35ac53ec4f058a8009a55b4cc63599247d Mon Sep 17 00:00:00 2001 From: Malena Sabate Landman Date: Mon, 3 Oct 2022 14:37:07 +0100 Subject: [PATCH 14/42] First version of hybrid_LSQR --- MATLAB/Algorithms/hybrid_LSQR.m | 270 ++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 MATLAB/Algorithms/hybrid_LSQR.m diff --git a/MATLAB/Algorithms/hybrid_LSQR.m b/MATLAB/Algorithms/hybrid_LSQR.m new file mode 100644 index 00000000..ba65aee5 --- /dev/null +++ b/MATLAB/Algorithms/hybrid_LSQR.m @@ -0,0 +1,270 @@ +function [x,errorL2,qualMeasOut]= hybrid_LSQR(proj,geo,angles,niter,varargin) + +% LSQR solves the CBCT problem using LSQR. +% This is mathematically equivalent to CGLS. +% +% LSQR(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% using the projection data PROJ taken over ANGLES angles, corresponding +% to the geometry descrived in GEO, using NITER iterations. +% +% LSQR(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% possible options in OPT are: +% +% +% 'Init' Describes diferent initialization techniques. +% * 'none' : Initializes the image to zeros (default) +% * 'FDK' : intializes image to FDK reconstrucition +% * 'multigrid': Initializes image by solving the problem in +% small scale and increasing it when relative +% convergence is reached. +% * 'image' : Initialization using a user specified +% image. Not recomended unless you really +% know what you are doing. +% 'InitImg' an image for the 'image' initialization. Avoid. +%-------------------------------------------------------------------------- +%-------------------------------------------------------------------------- +% This file is part of the TIGRE Toolbox +% +% Copyright (c) 2015, University of Bath and +% CERN-European Organization for Nuclear Research +% All rights reserved. +% +% License: Open Source under BSD. +% See the full license at +% https://github.com/CERN/TIGRE/blob/master/LICENSE +% +% Contact: tigre.toolbox@gmail.com +% Codes: max(K) https://github.com/CERN/TIGRE/ +% Coded by: Malena Sabate Landman, Ander Biguri +%-------------------------------------------------------------------------- + +%% + +[verbose,x0,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); + +% msl: no idea of what this is. Should I check? +measurequality=~isempty(QualMeasOpts); +qualMeasOut=zeros(length(QualMeasOpts),niter); + +% Paige and Saunders //doi.org/10.1145/355984.355989 + +% This should be one to avoid loosing orthogonality, but can be switched +% off for testing. +reorth = 1; + +% Initialise matrices +U = zeros(prod(size(proj)), niter+1); +V = zeros(prod(geo.nVoxel), niter); % Malena: Check if prod(geo.nVoxel) is correct +B = zeros(niter+1,niter); % Projected matrix +proj_rhs = zeros(niter+1,1); % Projected right hand side + +% Enumeration as given in the paper for 'Algorithm LSQR' +% (1) Initialize +u = proj-Ax(x0,geo,angles,'Siddon','gpuids',gpuids); +normr = norm(u(:),2); +u = u/normr; +U(:,1) = u(:); + +beta = normr; +proj_rhs(1) = normr; +errorL2 = zeros(1,niter); + +% (2) Start iterations +for ii=1:niter + + if (ii==1 && verbose);tic;end + + % Update V_ii + v = Atb(u,geo,angles,'matched','gpuids',gpuids); + + if ii>1 + v(:) = v(:) - beta*V(:,ii-1); + end + if reorth % Maybe change to matrix operations! + for jj = 1:ii-1 + v(:) = v(:) - (V(:,jj)'*v(:))*V(:,jj); + end + end + alpha = norm(v(:),2); % msl: do we want to check if it is 0? + v = v/alpha; + V(:,ii) = v(:); + + % Update U_{ii+1} + u = Ax(v,geo,angles,'Siddon','gpuids',gpuids) - alpha*u; + if reorth % Maybe change to matrix operations! + for jj = 1:ii-1 + u(:) = u(:) - (U(:,jj)'*u(:))*U(:,jj); + end + end + beta = norm(u(:),2); + u = u / beta; + U(:,ii+1) = u(:); + + % Update projected matrix + B(ii,ii) = alpha; + B(ii+1,ii) = beta; + % Malena. Proposed update: we should check algorithms breaks; + % 'if abs(alpha) <= eps || abs(beta) <= eps' - end and save + + % Solve the projected problem + % (using the SVD of the small projected matrix) + Bk = B(1:ii+1,1:ii); + [Uk, Sk, Vk] = svd(Bk); + if ii==1 + Sk = Sk(1,1); + else + Sk = diag(Sk); + end + rhsk = proj_rhs(1:ii+1); + rhskhat = Uk'*rhsk; + + % Malena: add regularization parameter choices + lambda = 10; + + Dk = Sk.^2 + lambda^2; + rhskhat = Sk .* rhskhat(1:ii); + yhat = rhskhat(1:ii)./Dk; + y = Vk * yhat; + + errorL2(ii)=norm(rhsk - Bk*y); % residual norm + % norm_proj = norm (proj(:),2); + % errorL2(ii) = norm(rhsk - Bk*y)/norm_proj; % relative residual norm + + d = V(:,1:ii)*y; + x = x0 + reshape(d,size(x0)); + + + % Test for convergence. + % msl: I still need to implement this. + % msl: There are suggestions on the original paper. Let's talk about it! + + if measurequality + qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); + end + + if ii>1 && errorL2(ii)>errorL2(ii-1) % msl: not checked + % OUT! + x=x-alpha*v; + if verbose + disp(['CGLS stoped in iteration N', num2str(ii),' due to divergence.']) + end + return; + end + + if (ii==1 && verbose) + expected_time=toc*niter; + disp('LSQR'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end +end + +end + + +%% parse inputs' +function [verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids'}; +defaults=ones(length(opts),1); + +% Check inputs +nVarargs = length(argin); +if mod(nVarargs,2) + error('TIGRE:LSQR:InvalidInput','Invalid number of inputs') +end + +% check if option has been passed as input +for ii=1:2:nVarargs + ind=find(ismember(opts,lower(argin{ii}))); + if ~isempty(ind) + defaults(ind)=0; + else + error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + end +end + +for ii=1:length(opts) + opt=opts{ii}; + default=defaults(ii); + % if one option isnot default, then extranc value from input + if default==0 + ind=double.empty(0,1);jj=1; + while isempty(ind) + ind=find(isequal(opt,lower(argin{jj}))); + jj=jj+1; + end + if isempty(ind) + error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + end + val=argin{jj}; + end + + switch opt + case 'init' + x=[]; + if default || strcmp(val,'none') + x=zeros(geo.nVoxel','single'); + continue; + end + if strcmp(val,'FDK') + x=FDK(proj,geo,angles); + continue; + end + if strcmp(val,'multigrid') + x=init_multigrid(proj,geo,angles); + continue; + end + if strcmp(val,'image') + initwithimage=1; + continue; + end + if isempty(x) + error('TIGRE:LSQR:InvalidInput','Invalid Init option') + end + % % % % % % % ERROR + case 'initimg' + if default + continue; + end + if exist('initwithimage','var') + if isequal(size(val),geo.nVoxel') + x=single(val); + else + error('TIGRE:LSQR:InvalidInput','Invalid image for initialization'); + end + end + % ========================================================================= + case 'qualmeas' + if default + QualMeasOpts={}; + else + if iscellstr(val) + QualMeasOpts=val; + else + error('TIGRE:LSQR:InvalidInput','Invalid quality measurement parameters'); + end + end + case 'verbose' + if default + verbose=1; + else + verbose=val; + end + if ~is2014bOrNewer + warning('TIGRE:LSQR:Verbose mode not available for older versions than MATLAB R2014b'); + verbose=false; + end + case 'gpuids' + if default + gpuids = GpuIds(); + else + gpuids = val; + end + otherwise + error('TIGRE:LSQR:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); + end +end + + +end \ No newline at end of file From 980ea2e80db7b4405c07ce950098e18c20d0944f Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Tue, 4 Oct 2022 18:56:54 +0100 Subject: [PATCH 15/42] Add MATLAB demos for krylov algos --- MATLAB/Algorithms/IRN_TV_CGLS.m | 2 +- MATLAB/Algorithms/hybrid_LSQR.m | 67 +++++++++++++------------------- MATLAB/Demos/d08_Algorithms03.m | 15 ++++--- Python/demos/d09_Algorithms04.py | 4 +- 4 files changed, 39 insertions(+), 49 deletions(-) diff --git a/MATLAB/Algorithms/IRN_TV_CGLS.m b/MATLAB/Algorithms/IRN_TV_CGLS.m index 54d535bc..36dd0ce6 100644 --- a/MATLAB/Algorithms/IRN_TV_CGLS.m +++ b/MATLAB/Algorithms/IRN_TV_CGLS.m @@ -118,7 +118,7 @@ if (iii==1 && verbose) expected_time=toc*niter_outer; - disp('ISN_TV_CGLS'); + disp('IRN_TV_CGLS'); disp(['Expected duration : ',secs2hms(expected_time)]); disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); disp(''); diff --git a/MATLAB/Algorithms/hybrid_LSQR.m b/MATLAB/Algorithms/hybrid_LSQR.m index ba65aee5..f5d08ec0 100644 --- a/MATLAB/Algorithms/hybrid_LSQR.m +++ b/MATLAB/Algorithms/hybrid_LSQR.m @@ -1,13 +1,12 @@ -function [x,errorL2,qualMeasOut]= hybrid_LSQR(proj,geo,angles,niter,varargin) +function [x,resL2,qualMeasOut]= hybrid_LSQR(proj,geo,angles,niter,varargin) -% LSQR solves the CBCT problem using LSQR. -% This is mathematically equivalent to CGLS. +% hybrid_LSQR solves the CBCT problem using LSQR. % -% LSQR(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% hybrid_LSQR(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem % using the projection data PROJ taken over ANGLES angles, corresponding % to the geometry descrived in GEO, using NITER iterations. % -% LSQR(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% hybrid_LSQR(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The % possible options in OPT are: % % @@ -40,9 +39,12 @@ %% -[verbose,x0,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); +[verbose,x0,QualMeasOpts,gpuids, lambda]=parse_inputs(proj,geo,angles,varargin); -% msl: no idea of what this is. Should I check? +if isnan(lambda) + % GCV + lambda=10; +end measurequality=~isempty(QualMeasOpts); qualMeasOut=zeros(length(QualMeasOpts),niter); @@ -53,10 +55,10 @@ reorth = 1; % Initialise matrices -U = zeros(prod(size(proj)), niter+1); -V = zeros(prod(geo.nVoxel), niter); % Malena: Check if prod(geo.nVoxel) is correct -B = zeros(niter+1,niter); % Projected matrix -proj_rhs = zeros(niter+1,1); % Projected right hand side +U = zeros(numel(proj), niter+1,'single'); +V = zeros(prod(geo.nVoxel), niter,'single'); % Malena: Check if prod(geo.nVoxel) is correct +B = zeros(niter+1,niter,'single'); % Projected matrix +proj_rhs = zeros(niter+1,1,'single'); % Projected right hand side % Enumeration as given in the paper for 'Algorithm LSQR' % (1) Initialize @@ -67,7 +69,7 @@ beta = normr; proj_rhs(1) = normr; -errorL2 = zeros(1,niter); +resL2 = zeros(1,niter); % (2) Start iterations for ii=1:niter @@ -117,55 +119,32 @@ end rhsk = proj_rhs(1:ii+1); rhskhat = Uk'*rhsk; - - % Malena: add regularization parameter choices - lambda = 10; Dk = Sk.^2 + lambda^2; rhskhat = Sk .* rhskhat(1:ii); yhat = rhskhat(1:ii)./Dk; y = Vk * yhat; - errorL2(ii)=norm(rhsk - Bk*y); % residual norm - % norm_proj = norm (proj(:),2); - % errorL2(ii) = norm(rhsk - Bk*y)/norm_proj; % relative residual norm - - d = V(:,1:ii)*y; - x = x0 + reshape(d,size(x0)); - - - % Test for convergence. - % msl: I still need to implement this. - % msl: There are suggestions on the original paper. Let's talk about it! - - if measurequality - qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); - end + resL2(ii)=norm(rhsk - Bk*y); % residual norm - if ii>1 && errorL2(ii)>errorL2(ii-1) % msl: not checked - % OUT! - x=x-alpha*v; - if verbose - disp(['CGLS stoped in iteration N', num2str(ii),' due to divergence.']) - end - return; - end if (ii==1 && verbose) expected_time=toc*niter; - disp('LSQR'); + disp('hybrid LSQR'); disp(['Expected duration : ',secs2hms(expected_time)]); disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); disp(''); end end +x = x0 + reshape(V(:,1:ii)*y,size(x0)); + end %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids'}; +function [verbose,x,QualMeasOpts,gpuids,lambda]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','lambda'}; defaults=ones(length(opts),1); % Check inputs @@ -234,6 +213,12 @@ error('TIGRE:LSQR:InvalidInput','Invalid image for initialization'); end end + case 'lambda' + if default + lambda=NaN; + else + lambda=val; + end % ========================================================================= case 'qualmeas' if default diff --git a/MATLAB/Demos/d08_Algorithms03.m b/MATLAB/Demos/d08_Algorithms03.m index 788b6463..9b002c20 100644 --- a/MATLAB/Demos/d08_Algorithms03.m +++ b/MATLAB/Demos/d08_Algorithms03.m @@ -30,7 +30,7 @@ close all; %% Define Geometry -geo=defaultGeometry('nVoxel',[512,512,512]','nDetector',[512,512]); +geo=defaultGeometry('nVoxel',[256,256,256]','nDetector',[256,256]); %% Load data and generate projections % see previous demo for explanation @@ -65,6 +65,9 @@ [imgCGLS, residual_CGLS]=CGLS(noise_projections,geo,angles,60); % use LSQR [imgLSQR, residual_LSQR]=LSQR(noise_projections,geo,angles,60); +% use hybrid LSQR (note, this algorithm requires tons of memory, +% [niter x size(image)] memory. Do not use for large images. +[imghLSQR, residual_hLSQR]=hybrid_LSQR(noise_projections,geo,angles,60); % use LSMR [imgLSMR, residual_LSMR]=LSMR(noise_projections,geo,angles,60); % use LSMR with a lambda value @@ -81,18 +84,20 @@ length(residual_CGLS), length(residual_SIRT), length(residual_LSMR), - length(residual_LSMR_lambda)]); + length(residual_LSMR_lambda), + length(residual_hLSQR)]); plot([[residual_SIRT nan(1,len-length(residual_SIRT))]; [residual_CGLS nan(1,len-length(residual_CGLS))]; [residual_LSQR nan(1,len-length(residual_LSQR))]; + [residual_hLSQR nan(1,len-length(residual_hLSQR))]; [residual_LSMR nan(1,len-length(residual_LSMR))]; [residual_LSMR_lambda nan(1,len-length(residual_LSMR_lambda))]]'); title('Residual') -legend('SIRT','CGLS','LSQR','LSMR','LSMR lambda') +legend('SIRT','CGLS','LSQR','hybrid LSQR','LSMR','LSMR lambda') % plot images -plotImg([imgLSQR imgCGLS, imgLSMR, imgLSMR_lambda, imgSIRT],'Dim','Z','Step',2) +plotImg([imgLSQR imgCGLS, imgLSMR;imghLSQR imgLSMR_lambda, imgSIRT],'Dim','Z','Step',2) %plot errors -plotImg(abs([head-imgLSQR head-imgCGLS head-imgLSMR head-imgLSMR_lambda head-imgSIRT]),'Dim','Z','Slice',64,'clims',[0, 0.3]) +plotImg(abs([head-imgLSQR head-imgCGLS head-imgLSMR; head-imghLSQR head-imgLSMR_lambda head-imgSIRT]),'Dim','Z','Slice',64,'clims',[0, 0.3]) diff --git a/Python/demos/d09_Algorithms04.py b/Python/demos/d09_Algorithms04.py index 3957b54d..5426ae1d 100644 --- a/Python/demos/d09_Algorithms04.py +++ b/Python/demos/d09_Algorithms04.py @@ -170,8 +170,8 @@ # ========================================================================== # ========================================================================== # -# This is a more edge preserving algorithms than ASD_POCS, in theory. -# delta is the cuttof vlaue of anromalized edge exponential weight.... +# This is a more edge preserving algorithms than ASD_POCS. +# delta is the cut-off value of a normalized edge exponential weight.... # not super clear, but it cotnrols at which point you accept something as real vs noise edge. imgAWASDPOCS = algs.awasd_pocs( From 1e9ccd0703c7683be9365edebc572ba57096249e Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Tue, 4 Oct 2022 18:57:09 +0100 Subject: [PATCH 16/42] add hybrid_LSQR to python --- .../algorithms/krylov_subspace_algorithms.py | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/Python/tigre/algorithms/krylov_subspace_algorithms.py b/Python/tigre/algorithms/krylov_subspace_algorithms.py index 899d3054..50cd3567 100644 --- a/Python/tigre/algorithms/krylov_subspace_algorithms.py +++ b/Python/tigre/algorithms/krylov_subspace_algorithms.py @@ -177,6 +177,107 @@ def run_main_iter(self): lsqr = decorator(LSQR, name="lsqr") +class hybrid_LSQR(IterativeReconAlg): + __doc__ = ( + " hybrid_LSQR solves the CBCT problem using the hybrid_LSQR\n" + " hybrid_LSQR(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem\n" + " using the projection data PROJ taken over ALPHA angles, corresponding\n" + " to the geometry descrived in GEO, using NITER iterations." + ) + IterativeReconAlg.__doc__ + + def __init__(self, proj, geo, angles, niter, **kwargs): + # Don't precompute V and W. + kwargs.update(dict(W=None, V=None)) + kwargs.update(dict(blocksize=angles.shape[0])) + self.re_init_at_iteration = 0 + IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) + # Paige and Saunders //doi.org/10.1145/355984.355989 + + # Enumeration as given in the paper for 'Algorithm LSQR' + + # % Initialise matrices + self.__U__ = np.zeros((niter+1,np.prod(geo.nDetector)*len(angles)),dtype=np.float32) + self.__V__ = np.zeros(niter,(np.prod(geo.nVoxel)),dtype=np.float32) + self.__B__ = np.zeros((niter,niter+1),dtype=np.float32); #% Projected matrix + self.__proj_rhs__ = np.zeros((niter+1,1),dtype=np.float32); #% Projected right hand side + + # (1) Initialize + self.__u__=self.proj - Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids) + + normr = np.linalg.norm(self.__u__.ravel(), 2) + self.__u__ = self.__u__/normr + self.__U__[0]=self.__u__.ravel() + + self.__beta__ = normr + self.__proj_rhs__[0]=normr + + + def run_main_iter(self): + self.l2l = np.zeros((1, self.niter), dtype=np.float32) + avgtime = [] + for i in range(self.niter): + if self.verbose: + self._estimate_time_until_completion(i) + if self.Quameasopts is not None: + res_prev = copy.deepcopy(self.res) + avgtic = default_timer() + + v = Atb(self.__u__,self.geo,self.angles,backprojection_type="matched",gpuids=self.gpuids) + + if i>0: + v = np.reshape(v.ravel() - self.__beta__*self.__V__[i-1],v.shape) + + + for j in range(i-1): + v=np.reshape(v.ravel()-(self.__V__[j]*v.ravel())*self.__V__[j],v.shape) + + + alpha = np.linalg.norm(v.ravel(), 2) + v = v/alpha + self.__V__[i] = v.ravel() + + #% Update U_{ii+1} + self.__u__ = tigre.Ax(v, self.geo, self.angles, "Siddon", gpuids=self.gpuids) - alpha*self.__u__ + + for j in range(i-1): + self.__u__=np.reshape(self.__u__ .ravel()-(self.__U__[j]*self.__u__.ravel())*self.__U_[j],self.__u__.shape) + + + self.__beta__ = np.linalg.norm(self.__u__.ravel(), 2) + u = u / self.__beta__ + self.__U__[i+1] = self.__u__.ravel() + + #% Update projected matrix + self.__B__[i,i] = alpha + self.__B__[i,i+i] = self.__beta__ + #% Malena. Proposed update: we should check algorithms breaks; + #% 'if abs(alpha) <= eps || abs(beta) <= eps' - end and save + + #% Solve the projected problem + #% (using the SVD of the small projected matrix) + Bk = self.__B__[0:i,0:i+1] + Uk, Sk, Vk = np.linalg.svd(Bk) + if i==0: + Sk = Sk[0,0] + else: + Sk = np.diag(Sk) + + rhsk = self.__proj_rhs__[0:i+1] + rhskhat = np.matmul(Uk,rhsk) # AB: transpose Uk?? + + Dk = Sk**2 + self.lmbda**2 + rhskhat = Sk * rhskhat[0:i] + yhat = rhskhat[0:i]/Dk + y = np.matmul(Vk, yhat) + + self.l2l[i]=np.linalg.norm(rhsk - Bk*y)# % residual norm + + #% Test for convergence. + #% msl: I still need to implement this. + #% msl: There are suggestions on the original paper. Let's talk about it! + + self.res = self.res + np.reshape(np.matmul(self.__V__,y),self.res.shape) + class LSMR(IterativeReconAlg): __doc__ = ( From e72fef60b67fa404306c3a92238347c167ed0d15 Mon Sep 17 00:00:00 2001 From: Malena Sabate Landman Date: Fri, 7 Oct 2022 09:26:14 +0100 Subject: [PATCH 17/42] Fixed bug Fixed bug and updated default parameters --- MATLAB/Algorithms/IRN_TV_CGLS.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MATLAB/Algorithms/IRN_TV_CGLS.m b/MATLAB/Algorithms/IRN_TV_CGLS.m index 36dd0ce6..b65e04f9 100644 --- a/MATLAB/Algorithms/IRN_TV_CGLS.m +++ b/MATLAB/Algorithms/IRN_TV_CGLS.m @@ -77,7 +77,7 @@ gamma=norm(p(:),2)^2; for ii=1:niter - x0 = x; + %x0 = x; q_aux_1 = Ax(p ,geo,angles,'Siddon','gpuids',gpuids); q_aux_2 = Lx (W, p)*sqrt(lambda); @@ -258,7 +258,7 @@ end case 'niter_outer' if default - niter_outer=15; + niter_outer=5; else niter_outer=val; end From 9874275ecc9906c105a06d7e0a752206df1efe7c Mon Sep 17 00:00:00 2001 From: Malena Sabate Landman Date: Fri, 7 Oct 2022 10:47:39 +0100 Subject: [PATCH 18/42] First version of hybrid_fLSQR_TV --- MATLAB/Algorithms/hybrid_fLSQR_TV.m | 409 ++++++++++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 MATLAB/Algorithms/hybrid_fLSQR_TV.m diff --git a/MATLAB/Algorithms/hybrid_fLSQR_TV.m b/MATLAB/Algorithms/hybrid_fLSQR_TV.m new file mode 100644 index 00000000..ca85fabb --- /dev/null +++ b/MATLAB/Algorithms/hybrid_fLSQR_TV.m @@ -0,0 +1,409 @@ +function [x,errorL2,qualMeasOut]= hybrid_fLSQR_TV(proj,geo,angles,niter,varargin) + +% LSQR solves the CBCT problem using LSQR. +% This is mathematically equivalent to CGLS. +% +% LSQR(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% using the projection data PROJ taken over ANGLES angles, corresponding +% to the geometry descrived in GEO, using NITER iterations. +% +% LSQR(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% possible options in OPT are: +% +% +% 'Init' Describes diferent initialization techniques. +% * 'none' : Initializes the image to zeros (default) +% * 'FDK' : intializes image to FDK reconstrucition +% * 'multigrid': Initializes image by solving the problem in +% small scale and increasing it when relative +% convergence is reached. +% * 'image' : Initialization using a user specified +% image. Not recomended unless you really +% know what you are doing. +% 'InitImg' an image for the 'image' initialization. Avoid. +%-------------------------------------------------------------------------- +%-------------------------------------------------------------------------- +% This file is part of the TIGRE Toolbox +% +% Copyright (c) 2015, University of Bath and +% CERN-European Organization for Nuclear Research +% All rights reserved. +% +% License: Open Source under BSD. +% See the full license at +% https://github.com/CERN/TIGRE/blob/master/LICENSE +% +% Contact: tigre.toolbox@gmail.com +% Codes: max(K) https://github.com/CERN/TIGRE/ +% Coded by: Malena Sabate Landman, Ander Biguri +%-------------------------------------------------------------------------- + +%% + +[verbose,x0,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); + +% msl: no idea of what this is. Should I check? +measurequality=~isempty(QualMeasOpts); +qualMeasOut=zeros(length(QualMeasOpts),niter); + +% Paige and Saunders //doi.org/10.1145/355984.355989 + +% Initialise matrices +U = single(zeros(prod(size(proj)), niter+1)); +V = single(zeros(prod(geo.nVoxel), niter)); % Malena: Check if prod(geo.nVoxel) is correct, I want size of object +Z = single(zeros(prod(geo.nVoxel), niter)); % Flexible basis +M = zeros(niter+1,niter); % Projected matrix 1 +T = zeros(niter+1); % Projected matrix 2 +proj_rhs = zeros(niter+1,1); % Projected right hand side + +z = zeros(geo.nVoxel'); + +% Enumeration as given in the paper for 'Algorithm LSQR' +% (1) Initialize +% norm_proj = norm (proj(:),2); +u = proj-Ax(x0,geo,angles,'Siddon','gpuids',gpuids); + +% Build xA0 +null_D = single((prod(geo.nVoxel)^(-1/2))*ones(geo.nVoxel')); + +k_aux = Ax(null_D,geo,angles,'Siddon','gpuids',gpuids); +xA0 = (prod(geo.nVoxel)^(-1/2))*norm(k_aux(:),2)^(-2)* (k_aux(:)'*u(:)); % scalar +xA0 = single(ones(geo.nVoxel')) * xA0; + +% Other way of computing the same, what is better? +% vecAN = Ax(null_D,geo,angles,'Siddon','gpuids',gpuids); +% [Q0, R0] = qr(vecAN(:), 0); +% x0 = Q0'*u(:); x0 = R0\x0; x0 = null_D(:)*x0; +% Ax0 = A_times_vec(A, x0) + +k_aux = (prod(geo.nVoxel)^(-1/2))*norm(k_aux(:),2)^(-2)*Atb(k_aux,geo,angles,'matched','gpuids',gpuids); + +u = u-Ax(xA0,geo,angles,'Siddon','gpuids',gpuids); + +normr = norm(u(:),2); +u = u/normr; +U(:,1) = u(:); + +if max(max(max(x0))) == 0 + W = ones(size(x0)); +else + W = build_weights (x0); +end + +proj_rhs(1) = normr; +errorL2 = zeros(1,niter); + + +% (2) Start iterations +for ii=1:niter + + if (ii==1 && verbose);tic;end + + % Update V, Z, and projected matrix T + v = Atb(u,geo,angles,'matched','gpuids',gpuids); + for jj = 1:ii-1 + T(jj,ii) = V(:,jj)'*v(:); + v(:) = v(:) - T(jj,ii) *V(:,jj); + end + T(ii,ii) = norm(v(:),2); + v = v/T(ii,ii); + % Choose if fixing a tolerance or a number of iterations of both + % This should maybe be done in single precisions... + z(:) = mvpE(k_aux, v(:), 'transp'); + aux_z = lsqr(@(x,tflag) Ltx(W, x, tflag), z(:), [], 25); + z(:) = lsqr(@(x,tflag) Lx(W, x, tflag), aux_z, [], 25); + z(:) = mvpE(k_aux, z(:), 'notransp'); +% z(:) = lsqr(@(x,tflag)mvpEt(k_aux, x, tflag), v(:), [], 25); +% z(:) = lsqr(@(x,tflag)mvpE(k_aux, x, tflag), z(:), [], 25); + z = single(z); + + V(:,ii) = v(:); + Z(:,ii) = z(:); + + % Update U and projected matrix M + u = Ax(z,geo,angles,'Siddon','gpuids',gpuids); + for jj = 1:ii + M(jj,ii) = U(:,jj)'*u(:); + u(:) = u(:) - M(jj,ii) *U(:,jj); + end + M(ii+1,ii) = norm(u(:),2); + u = u / M(ii+1,ii); + U(:,ii+1) = u(:); + + % Malena. Proposed update: we should check algorithms breaks; + % 'if abs(alpha) <= eps || abs(beta) <= eps' - end and save + + %%% Solve the regularised projected problem + % (using the SVD of the small projected matrix) + Mk = M(1:ii+1,1:ii); + + % Malena: add regularization parameter choices + lambda = 10; + + % Prepare the projected regularization term + WZ = zeros(3*prod(geo.nVoxel),ii); + for jj=1:ii + % This can be done more efficiently... + % DZ can be saved and updates at each iteration + out = Lx (W, Z(:,jj), 'notransp'); + WZ(:,jj) = out; %[out{1}(:);out{2}(:);out{3}(:)]; + end + [~, ZRk] = qr(WZ(:,1:ii), 0); + ZRksq = ZRk(1:ii,1:ii); + MZk = [Mk; lambda*ZRksq]; + rhsk = proj_rhs(1:ii+1); + rhsZk = [rhsk; zeros(ii,1)]; + y = MZk\rhsZk; + + errorL2(ii)=norm(rhsk - Mk*y); + + d = Z(:,1:ii)*y; + x = x0 + reshape(d,size(x0)) + xA0; + + W = build_weights (x); + + % Test for convergence. + % msl: I still need to implement this. + % msl: There are suggestions on the original paper. Let's talk about it! + + if measurequality + qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); + end + + if (ii==1 && verbose) + expected_time=toc*niter; + disp('LSQR'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end +end + +end + + +function W = build_weights ( x) + % Directional discrete derivatives + % Reflective boundary conditions + Dxx=zeros(size(x),'single'); + Dyx=zeros(size(x),'single'); + Dzx=zeros(size(x),'single'); + + Dxx(1:end-1,:,:)=x(1:end-1,:,:)-x(2:end,:,:); + Dyx(:,1:end-1,:)=x(:,1:end-1,:)-x(:,2:end,:); + Dzx(:,:,1:end-1)=x(:,:,1:end-1)-x(:,:,2:end); + + W = (Dxx.^2+Dyx.^2+Dzx.^2+1e-6).^(-1/4); % Fix this... + +end + +function out = Lx (W, x, transp_flag) +if strcmp(transp_flag,'transp') + x = reshape(x,[size(W),3]); + Wx_1 = W .* x(:,:,:,1); + Wx_2 = W .* x(:,:,:,2); + Wx_3 = W .* x(:,:,:,3); + + % Left here, but this is how to make a transpose + DxtWx_1=Wx_1; + DytWx_2=Wx_2; + DztWx_3=Wx_3; + + DxtWx_1(2:end-1,:,:)=Wx_1(2:end-1,:,:)-Wx_1(1:end-2,:,:); + DxtWx_1(end,:,:)=-Wx_1(end-1,:,:); + + DytWx_2(:,2:end-1,:)=Wx_2(:,2:end-1,:)-Wx_2(:,1:end-2,:); + DytWx_2(:,end,:)=-Wx_2(:,end-1,:); + + DztWx_3(:,:,2:end-1)=Wx_3(:,:,2:end-1)-Wx_3(:,:,1:end-2); + DztWx_3(:,:,end)=-Wx_3(:,:,end-1); + + out = DxtWx_1 + DytWx_2 + DztWx_3; + out = out(:); + +elseif strcmp(transp_flag,'notransp') + + x = reshape(x,size(W)); + + % Directional discrete derivatives + % Reflective boundary conditions + Dxx=zeros(size(x),'single'); + Dyx=zeros(size(x),'single'); + Dzx=zeros(size(x),'single'); + + Dxx(1:end-1,:,:)=x(1:end-1,:,:)-x(2:end,:,:); + Dyx(:,1:end-1,:)=x(:,1:end-1,:)-x(:,2:end,:); + Dzx(:,:,1:end-1)=x(:,:,1:end-1)-x(:,:,2:end); + % Build weights - is it better to find the right rotation and add + % tensors? + out = cat(4,W .* Dxx, W .* Dyx, W .* Dzx); + out = out(:); +end +end + +function out = Ltx (W, x, transp_flag) +if strcmp(transp_flag,'notransp') + x = reshape(x,[size(W),3]); + Wx_1 = W .* x(:,:,:,1); + Wx_2 = W .* x(:,:,:,2); + Wx_3 = W .* x(:,:,:,3); + + % Left here, but this is how to make a transpose + DxtWx_1=Wx_1; + DytWx_2=Wx_2; + DztWx_3=Wx_3; + + DxtWx_1(2:end-1,:,:)=Wx_1(2:end-1,:,:)-Wx_1(1:end-2,:,:); + DxtWx_1(end,:,:)=-Wx_1(end-1,:,:); + + DytWx_2(:,2:end-1,:)=Wx_2(:,2:end-1,:)-Wx_2(:,1:end-2,:); + DytWx_2(:,end,:)=-Wx_2(:,end-1,:); + + DztWx_3(:,:,2:end-1)=Wx_3(:,:,2:end-1)-Wx_3(:,:,1:end-2); + DztWx_3(:,:,end)=-Wx_3(:,:,end-1); + + out = DxtWx_1 + DytWx_2 + DztWx_3; + out = out(:); + +elseif strcmp(transp_flag,'transp') + + x = reshape(x,size(W)); + + % Directional discrete derivatives + % Reflective boundary conditions + Dxx=zeros(size(x),'single'); + Dyx=zeros(size(x),'single'); + Dzx=zeros(size(x),'single'); + + Dxx(1:end-1,:,:)=x(1:end-1,:,:)-x(2:end,:,:); + Dyx(:,1:end-1,:)=x(:,1:end-1,:)-x(:,2:end,:); + Dzx(:,:,1:end-1)=x(:,:,1:end-1)-x(:,:,2:end); + % Build weights - is it better to find the right rotation and add + % tensors? + out = cat(4,W .* Dxx, W .* Dyx, W .* Dzx); + out = out(:); +end +end + +function out = mvpE(k_aux, x , transp_flag) +if strcmp(transp_flag,'transp') + out = x(:) - k_aux(:)*(ones(size(x(:)))'*x(:)); +elseif strcmp(transp_flag,'notransp') + out = x(:) - ones(size(x(:)))*(k_aux(:)'*x(:)); +end +end + +% function out = mvpEt(k_aux, x , transp_flag) +% if strcmp(transp_flag,'notransp') +% out = x(:) - k_aux(:)*(ones(size(x(:)))'*x(:)); +% elseif strcmp(transp_flag,'transp') +% out = x(:) - ones(size(x(:)))*(k_aux(:)'*x(:)); +% end +% end + + +%% parse inputs' +function [verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids'}; +defaults=ones(length(opts),1); + +% Check inputs +nVarargs = length(argin); +if mod(nVarargs,2) + error('TIGRE:LSQR:InvalidInput','Invalid number of inputs') +end + +% check if option has been passed as input +for ii=1:2:nVarargs + ind=find(ismember(opts,lower(argin{ii}))); + if ~isempty(ind) + defaults(ind)=0; + else + error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + end +end + +for ii=1:length(opts) + opt=opts{ii}; + default=defaults(ii); + % if one option isnot default, then extranc value from input + if default==0 + ind=double.empty(0,1);jj=1; + while isempty(ind) + ind=find(isequal(opt,lower(argin{jj}))); + jj=jj+1; + end + if isempty(ind) + error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + end + val=argin{jj}; + end + + switch opt + case 'init' + x=[]; + if default || strcmp(val,'none') + x=zeros(geo.nVoxel','single'); + continue; + end + if strcmp(val,'FDK') + x=FDK(proj,geo,angles); + continue; + end + if strcmp(val,'multigrid') + x=init_multigrid(proj,geo,angles); + continue; + end + if strcmp(val,'image') + initwithimage=1; + continue; + end + if isempty(x) + error('TIGRE:LSQR:InvalidInput','Invalid Init option') + end + % % % % % % % ERROR + case 'initimg' + if default + continue; + end + if exist('initwithimage','var') + if isequal(size(val),geo.nVoxel') + x=single(val); + else + error('TIGRE:LSQR:InvalidInput','Invalid image for initialization'); + end + end + % ========================================================================= + case 'qualmeas' + if default + QualMeasOpts={}; + else + if iscellstr(val) + QualMeasOpts=val; + else + error('TIGRE:LSQR:InvalidInput','Invalid quality measurement parameters'); + end + end + case 'verbose' + if default + verbose=1; + else + verbose=val; + end + if ~is2014bOrNewer + warning('TIGRE:LSQR:Verbose mode not available for older versions than MATLAB R2014b'); + verbose=false; + end + case 'gpuids' + if default + gpuids = GpuIds(); + else + gpuids = val; + end + otherwise + error('TIGRE:LSQR:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); + end +end + + +end \ No newline at end of file From 2385ceff81f8723987e6da1322c36dd59585a155 Mon Sep 17 00:00:00 2001 From: Malena Sabate Landman Date: Sat, 8 Oct 2022 11:00:25 +0100 Subject: [PATCH 19/42] Regularisation parameter choices added hybrid LSQR has been updated demos have been updated (but need polishing) --- MATLAB/Algorithms/hybrid_LSQR.m | 85 ++++++++++++-- MATLAB/Demos/d08_Algorithms03.m | 197 +++++++++++++++----------------- 2 files changed, 171 insertions(+), 111 deletions(-) diff --git a/MATLAB/Algorithms/hybrid_LSQR.m b/MATLAB/Algorithms/hybrid_LSQR.m index f5d08ec0..6ba49d71 100644 --- a/MATLAB/Algorithms/hybrid_LSQR.m +++ b/MATLAB/Algorithms/hybrid_LSQR.m @@ -1,4 +1,4 @@ -function [x,resL2,qualMeasOut]= hybrid_LSQR(proj,geo,angles,niter,varargin) +function [x,resL2,lambda_vec, qualMeasOut]= hybrid_LSQR(proj,geo,angles,niter,varargin) % hybrid_LSQR solves the CBCT problem using LSQR. % @@ -33,17 +33,26 @@ % https://github.com/CERN/TIGRE/blob/master/LICENSE % % Contact: tigre.toolbox@gmail.com -% Codes: max(K) https://github.com/CERN/TIGRE/ +% Codes: https://github.com/CERN/TIGRE/ % Coded by: Malena Sabate Landman, Ander Biguri %-------------------------------------------------------------------------- %% -[verbose,x0,QualMeasOpts,gpuids, lambda]=parse_inputs(proj,geo,angles,varargin); +[verbose,x0,QualMeasOpts,gpuids, lambda, NoiseLevel]=parse_inputs(proj,geo,angles,varargin); + +%%% PARAMETER CHOICE HIERARCHY: given lambda, DP, GCV if isnan(lambda) - % GCV - lambda=10; + if isnan(NoiseLevel) + RegParam = 'gcv'; + % Malena: if this is not good, we can use alternative formulations + else + RegParam = 'discrepit'; + % Malena: if this is slow, we can use an adaptive method + end +else + RegParam = 'given_lambda'; end measurequality=~isempty(QualMeasOpts); qualMeasOut=zeros(length(QualMeasOpts),niter); @@ -59,6 +68,7 @@ V = zeros(prod(geo.nVoxel), niter,'single'); % Malena: Check if prod(geo.nVoxel) is correct B = zeros(niter+1,niter,'single'); % Projected matrix proj_rhs = zeros(niter+1,1,'single'); % Projected right hand side +lambda_vec = zeros(niter,1); % Enumeration as given in the paper for 'Algorithm LSQR' % (1) Initialize @@ -119,7 +129,25 @@ end rhsk = proj_rhs(1:ii+1); rhskhat = Uk'*rhsk; - + lsqr_res = abs(rhskhat(ii+1))/normr; + + + if strcmp(RegParam,'discrepit') + eta = 1; + if lsqr_res > eta*NoiseLevel + lambda = 0; + else + lambda = fzero(@(l)discrepancy(l, Bk, rhsk, eta*NoiseLevel), [0, 1e10]); + end + lambda_vec(ii) = lambda; % We should output this, maybe? + elseif strcmp(RegParam,'gcv') + lambda = fminbnd(@(l)gcv(l, rhskhat, Sk), 0, double(Sk(1))); + % Adapt from IRtools + lambda_vec(ii) = lambda; % We should output this, maybe? + elseif strcmp(RegParam,'given_lambda') + lambda_vec(ii) = lambda; + end + Dk = Sk.^2 + lambda^2; rhskhat = Sk .* rhskhat(1:ii); yhat = rhskhat(1:ii)./Dk; @@ -141,10 +169,44 @@ end +%%% Regularization parameter choices + +function out = discrepancy(l, A, b, nnoise) +n = size(A,2); +if n == 1 + out = 0; +else + xl = [A; l*eye(n)]\[b; zeros(n,1)]; + out = (norm(A*xl -b)/norm(b))^2 - nnoise^2; +end +end + +function out = gcv(lambda, bhat, s) +% GCV for the projected problem - no weights +% If Bk is the projected matrix and Bk=Uk*Sk*Vk^T +% lambda is the regularisation parameter +% bhat is Uk'*bk +% s=diag(Sk) + +m = length(bhat); +n = length(s); + +t0 = sum(abs(bhat(n+1:m)).^2); + +s2 = abs(s) .^ 2; +lambda2 = lambda^2; + +t1 = lambda2 ./ (s2 + lambda2); +t2 = abs(bhat(1:n) .* t1) .^2; + +out = (sum(t2) + t0) / ((sum(t1)+m-n)^2); + +end + %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids,lambda]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids','lambda'}; +function [verbose,x,QualMeasOpts,gpuids,lambda,NoiseLevel]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','lambda','noiselevel'}; defaults=ones(length(opts),1); % Check inputs @@ -219,6 +281,13 @@ else lambda=val; end + case 'noiselevel' + if default + NoiseLevel=NaN; + else + NoiseLevel=val; + end + % ========================================================================= case 'qualmeas' if default diff --git a/MATLAB/Demos/d08_Algorithms03.m b/MATLAB/Demos/d08_Algorithms03.m index 9b002c20..f77d46a4 100644 --- a/MATLAB/Demos/d08_Algorithms03.m +++ b/MATLAB/Demos/d08_Algorithms03.m @@ -1,103 +1,94 @@ -%% DEMO 8: Algorithms 03. Krylov subspace -% -% -% In this demo the usage of the the Krylov subspace family is explained. -% This family of algorithms iterates trhough the eigenvectors of the -% residual (Ax-b) of the problem in descending order, achieving increased -% convergence rates comparing to SART family. -% -% In cases where the data is good quality, SART type families tend to reach -% to a better image, but when the data gets very big, or has bad quality, -% CGLS is a good and fast algorithm -%-------------------------------------------------------------------------- -%-------------------------------------------------------------------------- -% This file is part of the TIGRE Toolbox -% -% Copyright (c) 2015, University of Bath and -% CERN-European Organization for Nuclear Research -% All rights reserved. -% -% License: Open Source under BSD. -% See the full license at -% https://github.com/CERN/TIGRE/blob/master/LICENSE -% -% Contact: tigre.toolbox@gmail.com -% Codes: https://github.com/CERN/TIGRE/ -% Coded by: Ander Biguri -%-------------------------------------------------------------------------- -%% Initialize -clear; -close all; - -%% Define Geometry -geo=defaultGeometry('nVoxel',[256,256,256]','nDetector',[256,256]); - -%% Load data and generate projections -% see previous demo for explanation -angles=linspace(0,2*pi,100); -head=headPhantom(geo.nVoxel); -projections=Ax(head,geo,angles,'interpolated'); -noise_projections=addCTnoise(projections); - -%% Usage CGLS -% -% -% CGLS has the common 4 inputs for iterative algorithms in TIGRE: -% -% Projections, geometry, angles, and number of iterations -% -% Additionally it contains optional initialization tehcniques, but we -% reccomend not using them. CGLS is already quite fast and using them may -% lead to divergence. -% The options are: -% 'Init' Describes diferent initialization techniques. -% � 'none' : Initializes the image to zeros (default) -% � 'FDK' : intializes image to FDK reconstrucition -% � 'multigrid': Initializes image by solving the problem in -% small scale and increasing it when relative -% convergence is reached. -% � 'image' : Initialization using a user specified -% image. Not recomended unless you really -% know what you are doing. -% 'InitImg' an image for the 'image' initialization. Avoid. - -% use CGLS -[imgCGLS, residual_CGLS]=CGLS(noise_projections,geo,angles,60); -% use LSQR -[imgLSQR, residual_LSQR]=LSQR(noise_projections,geo,angles,60); -% use hybrid LSQR (note, this algorithm requires tons of memory, -% [niter x size(image)] memory. Do not use for large images. -[imghLSQR, residual_hLSQR]=hybrid_LSQR(noise_projections,geo,angles,60); -% use LSMR -[imgLSMR, residual_LSMR]=LSMR(noise_projections,geo,angles,60); -% use LSMR with a lambda value -[imgLSMR_lambda, residual_LSMR_lambda]=LSMR(noise_projections,geo,angles,60,'lambda',10); -% SIRT for comparison. -[imgSIRT, residual_SIRT]=SIRT(noise_projections,geo,angles,60); - -%% plot results -% -% We can see that CGLS gets to the same L2 error in less amount of -% iterations. - -len=max([length(residual_LSQR), - length(residual_CGLS), - length(residual_SIRT), - length(residual_LSMR), - length(residual_LSMR_lambda), - length(residual_hLSQR)]); - - -plot([[residual_SIRT nan(1,len-length(residual_SIRT))]; - [residual_CGLS nan(1,len-length(residual_CGLS))]; - [residual_LSQR nan(1,len-length(residual_LSQR))]; - [residual_hLSQR nan(1,len-length(residual_hLSQR))]; - [residual_LSMR nan(1,len-length(residual_LSMR))]; - [residual_LSMR_lambda nan(1,len-length(residual_LSMR_lambda))]]'); -title('Residual') -legend('SIRT','CGLS','LSQR','hybrid LSQR','LSMR','LSMR lambda') - -% plot images -plotImg([imgLSQR imgCGLS, imgLSMR;imghLSQR imgLSMR_lambda, imgSIRT],'Dim','Z','Step',2) -%plot errors -plotImg(abs([head-imgLSQR head-imgCGLS head-imgLSMR; head-imghLSQR head-imgLSMR_lambda head-imgSIRT]),'Dim','Z','Slice',64,'clims',[0, 0.3]) +%% DEMO 8: Algorithms 03. Krylov subspace +% +% +% In this demo the usage of the the Krylov subspace family is explained. +% This family of algorithms iterates trhough the eigenvectors of the +% residual (Ax-b) of the problem in descending order, achieving increased +% convergence rates comparing to SART family. +% +% In cases where the data is good quality, SART type families tend to reach +% to a better image, but when the data gets very big, or has bad quality, +% CGLS is a good and fast algorithm +%-------------------------------------------------------------------------- +%-------------------------------------------------------------------------- +% This file is part of the TIGRE Toolbox +% +% Copyright (c) 2015, University of Bath and +% CERN-European Organization for Nuclear Research +% All rights reserved. +% +% License: Open Source under BSD. +% See the full license at +% https://github.com/CERN/TIGRE/blob/master/LICENSE +% +% Contact: tigre.toolbox@gmail.com +% Codes: https://github.com/CERN/TIGRE/ +% Coded by: Ander Biguri +%-------------------------------------------------------------------------- +%% Initialize +clear; +close all; + +%% Define Geometry +geo=defaultGeometry('nVoxel',[512,512,512]','nDetector',[512,512]); + +%% Load data and generate projections +% see previous demo for explanation +angles=linspace(0,2*pi,100); +head=headPhantom(geo.nVoxel); +projections=Ax(head,geo,angles,'interpolated'); +noise_projections=addCTnoise(projections); + +%% Usage CGLS +% +% +% CGLS has the common 4 inputs for iterative algorithms in TIGRE: +% +% Projections, geometry, angles, and number of iterations +% +% Additionally it contains optional initialization tehcniques, but we +% reccomend not using them. CGLS is already quite fast and using them may +% lead to divergence. +% The options are: +% 'Init' Describes diferent initialization techniques. +% � 'none' : Initializes the image to zeros (default) +% � 'FDK' : intializes image to FDK reconstrucition +% � 'multigrid': Initializes image by solving the problem in +% small scale and increasing it when relative +% convergence is reached. +% � 'image' : Initialization using a user specified +% image. Not recomended unless you really +% know what you are doing. +% 'InitImg' an image for the 'image' initialization. Avoid. + +% use CGLS +[imgCGLS, errL2CGLS]=CGLS(noise_projections,geo,angles,60); +% SIRT for comparison. +[imgSIRT,errL2SIRT]=SIRT(noise_projections,geo,angles,60); + +%% Hybrid methods with different regularisation parameter choices +[imghLSQR_l10, residual_hLSQR_l10]=hybrid_LSQR(noise_projections,geo,angles,60, 'lambda', 10); +[imghLSQR_nl002, residual_hLSQR_nl002, lambda_vec_nl002]=hybrid_LSQR(noise_projections,geo,angles,60, 'NoiseLevel', 0.02); +[imghLSQR_gcv, residual_hLSQR_gcv, lambda_vec_gcv]=hybrid_LSQR(noise_projections,geo,angles,60); + +% plot images +plotImg([imghLSQR_l10 imghLSQR_nl10 imghLSQR_gcv],'Dim','Z','Step',2) + +% Look at the parameters +figure +plot(lambda_vec_nl002) +hold on +plot(lambda_vec_gcv) +%% plot results +% +% We can see that CGLS gets to the same L2 error in less amount of +% iterations. + +plot([errL2SIRT;[errL2CGLS nan(1,length(errL2SIRT)-length(errL2CGLS))]]'); +title('L2 error') +legend('SIRT','CGLS') + +% plot images +plotImg([imgCGLS imgSIRT],'Dim','Z','Step',2) +%plot errors +plotImg(abs([head-imgCGLS head-imgSIRT]),'Dim','Z','Slice',64) From cae9f69eb8a54601def948985f2b555c108c509a Mon Sep 17 00:00:00 2001 From: Malena Sabate Landman Date: Sat, 8 Oct 2022 11:43:36 +0100 Subject: [PATCH 20/42] small update small update --- MATLAB/Demos/d08_Algorithms03.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MATLAB/Demos/d08_Algorithms03.m b/MATLAB/Demos/d08_Algorithms03.m index f77d46a4..d6dada79 100644 --- a/MATLAB/Demos/d08_Algorithms03.m +++ b/MATLAB/Demos/d08_Algorithms03.m @@ -67,12 +67,12 @@ [imgSIRT,errL2SIRT]=SIRT(noise_projections,geo,angles,60); %% Hybrid methods with different regularisation parameter choices -[imghLSQR_l10, residual_hLSQR_l10]=hybrid_LSQR(noise_projections,geo,angles,60, 'lambda', 10); -[imghLSQR_nl002, residual_hLSQR_nl002, lambda_vec_nl002]=hybrid_LSQR(noise_projections,geo,angles,60, 'NoiseLevel', 0.02); -[imghLSQR_gcv, residual_hLSQR_gcv, lambda_vec_gcv]=hybrid_LSQR(noise_projections,geo,angles,60); +[imghLSQR_l10, residual_hLSQR_l10]=hybrid_LSQR(noise_projections,geo,angles,30, 'lambda', 10); +[imghLSQR_nl002, residual_hLSQR_nl002, lambda_vec_nl002]=hybrid_LSQR(noise_projections,geo,angles,30, 'NoiseLevel', 0.02); +[imghLSQR_gcv, residual_hLSQR_gcv, lambda_vec_gcv]=hybrid_LSQR(noise_projections,geo,angles,30); % plot images -plotImg([imghLSQR_l10 imghLSQR_nl10 imghLSQR_gcv],'Dim','Z','Step',2) +plotImg([imgCGLS imghLSQR_l10 imghLSQR_nl10 imghLSQR_gcv],'Dim','Z','Step',2) % Look at the parameters figure From 695fb267855301cd8fdb3475a0503dcf97bb6237 Mon Sep 17 00:00:00 2001 From: Malena Sabate Landman Date: Sat, 8 Oct 2022 15:16:42 +0100 Subject: [PATCH 21/42] First version of regularization parameter choices --- MATLAB/Algorithms/hybrid_fLSQR_TV.m | 104 +++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 10 deletions(-) diff --git a/MATLAB/Algorithms/hybrid_fLSQR_TV.m b/MATLAB/Algorithms/hybrid_fLSQR_TV.m index ca85fabb..ad47d877 100644 --- a/MATLAB/Algorithms/hybrid_fLSQR_TV.m +++ b/MATLAB/Algorithms/hybrid_fLSQR_TV.m @@ -1,4 +1,4 @@ -function [x,errorL2,qualMeasOut]= hybrid_fLSQR_TV(proj,geo,angles,niter,varargin) +function [x,errorL2,lambda_vec, qualMeasOut]= hybrid_fLSQR_TV(proj,geo,angles,niter,varargin) % LSQR solves the CBCT problem using LSQR. % This is mathematically equivalent to CGLS. @@ -40,7 +40,21 @@ %% -[verbose,x0,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); +[verbose,x0,QualMeasOpts,gpuids, lambda, NoiseLevel]=parse_inputs(proj,geo,angles,varargin); + +%%% PARAMETER CHOICE HIERARCHY: given lambda, DP, GCV + +if isnan(lambda) + if isnan(NoiseLevel) + RegParam = 'gcv'; + % Malena: if this is not good, we can use alternative formulations + else + RegParam = 'discrepit'; + % Malena: if this is slow, we can use an adaptive method + end +else + RegParam = 'given_lambda'; +end % msl: no idea of what this is. Should I check? measurequality=~isempty(QualMeasOpts); @@ -55,6 +69,7 @@ M = zeros(niter+1,niter); % Projected matrix 1 T = zeros(niter+1); % Projected matrix 2 proj_rhs = zeros(niter+1,1); % Projected right hand side +lambda_vec = zeros(niter,1); z = zeros(geo.nVoxel'); @@ -110,8 +125,8 @@ % Choose if fixing a tolerance or a number of iterations of both % This should maybe be done in single precisions... z(:) = mvpE(k_aux, v(:), 'transp'); - aux_z = lsqr(@(x,tflag) Ltx(W, x, tflag), z(:), [], 25); - z(:) = lsqr(@(x,tflag) Lx(W, x, tflag), aux_z, [], 25); + aux_z = lsqr(@(x,tflag) Ltx(W, x, tflag), z(:), [], 50); + z(:) = lsqr(@(x,tflag) Lx(W, x, tflag), aux_z, [], 50); z(:) = mvpE(k_aux, z(:), 'notransp'); % z(:) = lsqr(@(x,tflag)mvpEt(k_aux, x, tflag), v(:), [], 25); % z(:) = lsqr(@(x,tflag)mvpE(k_aux, x, tflag), z(:), [], 25); @@ -137,9 +152,6 @@ % (using the SVD of the small projected matrix) Mk = M(1:ii+1,1:ii); - % Malena: add regularization parameter choices - lambda = 10; - % Prepare the projected regularization term WZ = zeros(3*prod(geo.nVoxel),ii); for jj=1:ii @@ -150,8 +162,32 @@ end [~, ZRk] = qr(WZ(:,1:ii), 0); ZRksq = ZRk(1:ii,1:ii); - MZk = [Mk; lambda*ZRksq]; rhsk = proj_rhs(1:ii+1); + + if strcmp(RegParam,'discrepit') + eta = 1.01; + if discrepancy_Tik(0, Mk, ZRksq, rhsk, eta*NoiseLevel) > 0 + lambda = 0; + else + lambda = fzero(@(l)discrepancy_Tik(l, Mk, ZRksq, rhsk, eta*NoiseLevel), [0, 1e10]); + end + lambda_vec(ii) = lambda; % We should output this, maybe? + elseif strcmp(RegParam,'gcv') + [Uk, ~, ~, Ck, Sk] = gsvd(Mk, ZRksq); + rhskhat = Uk'*rhsk; + if ii==1 + gammak = Ck(1)/Sk(1); + else + gammak = sqrt(diag(Ck'*Ck)./diag(Sk'*Sk)); + end + lambda = fminbnd(@(l)gcv(l, rhskhat, gammak), 0, double(gammak(ii))); + lambda_vec(ii) = lambda; % We should output this, maybe? + + elseif strcmp(RegParam,'given_lambda') + lambda_vec(ii) = lambda; + end + + MZk = [Mk; lambda*ZRksq]; rhsZk = [rhsk; zeros(ii,1)]; y = MZk\rhsZk; @@ -181,6 +217,41 @@ end +%%% Regularization parameter choices + +function out = discrepancy_Tik(lambda, A, ZRksq, b, nnoise) +n = size(A,2); +if n == 1 + out = 0; +else + xl = [A; lambda*ZRksq]\[b; zeros(size(ZRksq,1),1)]; + out = (norm(A*xl - b)/norm(b))^2 - nnoise^2; +end +end + +function out = gcv(lambda, bhat, s) +% GCV for the projected problem - no weights +% If Bk is the projected matrix and Bk=Uk*Sk*Vk^T +% lambda is the regularisation parameter +% bhat is Uk'*bk +% s=diag(Sk) + +m = length(bhat); +n = length(s); + +t0 = sum(abs(bhat(n+1:m)).^2); + +s2 = abs(s) .^ 2; +lambda2 = lambda^2; + +t1 = lambda2 ./ (s2 + lambda2); +t2 = abs(bhat(1:n) .* t1) .^2; + +out = (sum(t2) + t0) / ((sum(t1)+m-n)^2); + +end + +%%%%%% function W = build_weights ( x) % Directional discrete derivatives @@ -303,8 +374,8 @@ %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids'}; +function [verbose,x,QualMeasOpts,gpuids, lambda, NoiseLevel]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','lambda','noiselevel'}; defaults=ones(length(opts),1); % Check inputs @@ -373,6 +444,19 @@ error('TIGRE:LSQR:InvalidInput','Invalid image for initialization'); end end + case 'lambda' + if default + lambda=NaN; + else + lambda=val; + end + case 'noiselevel' + if default + NoiseLevel=NaN; + else + NoiseLevel=val; + end + % ========================================================================= case 'qualmeas' if default From 5e4b36aafe3d5b2f45e326e2aa2cd0c27186f9a9 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Mon, 10 Oct 2022 12:14:38 +0100 Subject: [PATCH 22/42] Update d08 with parameter choice and the rest of krylov algorithms --- MATLAB/Demos/d08_Algorithms03.m | 63 ++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/MATLAB/Demos/d08_Algorithms03.m b/MATLAB/Demos/d08_Algorithms03.m index d6dada79..bb1535e2 100644 --- a/MATLAB/Demos/d08_Algorithms03.m +++ b/MATLAB/Demos/d08_Algorithms03.m @@ -30,7 +30,7 @@ close all; %% Define Geometry -geo=defaultGeometry('nVoxel',[512,512,512]','nDetector',[512,512]); +geo=defaultGeometry('nVoxel',[256,256,256]','nDetector',[256,256]); %% Load data and generate projections % see previous demo for explanation @@ -62,33 +62,62 @@ % 'InitImg' an image for the 'image' initialization. Avoid. % use CGLS -[imgCGLS, errL2CGLS]=CGLS(noise_projections,geo,angles,60); +[imgCGLS, residual_CGLS]=CGLS(noise_projections,geo,angles,60); +% use LSQR +[imgLSQR, residual_LSQR]=LSQR(noise_projections,geo,angles,60); +% use hybrid LSQR (note, this algorithm requires tons of memory, +% [niter x size(image)] memory. Do not use for large images. +[imghLSQR, residual_hLSQR]=hybrid_LSQR(noise_projections,geo,angles,60); +% use LSMR +[imgLSMR, residual_LSMR]=LSMR(noise_projections,geo,angles,60); +% use LSMR with a lambda value +[imgLSMR_lambda, residual_LSMR_lambda]=LSMR(noise_projections,geo,angles,60,'lambda',10); % SIRT for comparison. -[imgSIRT,errL2SIRT]=SIRT(noise_projections,geo,angles,60); +[imgSIRT, residual_SIRT]=SIRT(noise_projections,geo,angles,60); + +%% plot results +% +% We can see that CGLS gets to the same L2 error in less amount of +% iterations. + +len=max([length(residual_LSQR), + length(residual_CGLS), + length(residual_SIRT), + length(residual_LSMR), + length(residual_LSMR_lambda), + length(residual_hLSQR)]); + + +plot([[residual_SIRT nan(1,len-length(residual_SIRT))]; + [residual_CGLS nan(1,len-length(residual_CGLS))]; + [residual_LSQR nan(1,len-length(residual_LSQR))]; + [residual_hLSQR nan(1,len-length(residual_hLSQR))]; + [residual_LSMR nan(1,len-length(residual_LSMR))]; + [residual_LSMR_lambda nan(1,len-length(residual_LSMR_lambda))]]'); +title('Residual') +legend('SIRT','CGLS','LSQR','hybrid LSQR','LSMR','LSMR lambda') + +% plot images +plotImg([imgLSQR imgCGLS, imgLSMR;imghLSQR imgLSMR_lambda, imgSIRT],'Dim','Z','Step',2) +%plot errors +plotImg(abs([head-imgLSQR head-imgCGLS head-imgLSMR; head-imghLSQR head-imgLSMR_lambda head-imgSIRT]),'Dim','Z','Slice',64,'clims',[0, 0.3]) + %% Hybrid methods with different regularisation parameter choices +% you can explicitly defined the parameter in the mathematical terms [imghLSQR_l10, residual_hLSQR_l10]=hybrid_LSQR(noise_projections,geo,angles,30, 'lambda', 10); +% You can give it a "noise level" (in %) instead, and it will chose the lamba inside [imghLSQR_nl002, residual_hLSQR_nl002, lambda_vec_nl002]=hybrid_LSQR(noise_projections,geo,angles,30, 'NoiseLevel', 0.02); +% if you don't give it any, it will use Generalized Cross Validation to approximate a good lambda [imghLSQR_gcv, residual_hLSQR_gcv, lambda_vec_gcv]=hybrid_LSQR(noise_projections,geo,angles,30); % plot images -plotImg([imgCGLS imghLSQR_l10 imghLSQR_nl10 imghLSQR_gcv],'Dim','Z','Step',2) +plotImg([imgCGLS imghLSQR_l10 imghLSQR_nl002 imghLSQR_gcv],'Dim','Z','Step',2) % Look at the parameters figure plot(lambda_vec_nl002) hold on plot(lambda_vec_gcv) -%% plot results -% -% We can see that CGLS gets to the same L2 error in less amount of -% iterations. - -plot([errL2SIRT;[errL2CGLS nan(1,length(errL2SIRT)-length(errL2CGLS))]]'); -title('L2 error') -legend('SIRT','CGLS') - -% plot images -plotImg([imgCGLS imgSIRT],'Dim','Z','Step',2) -%plot errors -plotImg(abs([head-imgCGLS head-imgSIRT]),'Dim','Z','Slice',64) +title("lambda vs iteratios") +legend({"Noise Level", "Generalized Cross Validation"}) From 410a58081a445fd384492bea9e80eb8a6df963a0 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Mon, 17 Oct 2022 11:33:32 +0100 Subject: [PATCH 23/42] Add ground truth comparison MATLAB fix #269 --- MATLAB/Algorithms/ASD_POCS.m | 98 ++++-- MATLAB/Algorithms/AwASD_POCS.m | 91 +++-- MATLAB/Algorithms/AwPCSD.m | 95 ++--- MATLAB/Algorithms/B_ASD_POCS_beta.m | 88 +++-- MATLAB/Algorithms/CGLS.m | 181 ++++++---- MATLAB/Algorithms/FISTA.m | 87 +++-- MATLAB/Algorithms/IRN_TV_CGLS.m | 264 ++++++++------ MATLAB/Algorithms/LSMR.m | 333 ++++++++++-------- MATLAB/Algorithms/LSQR.m | 238 ++++++++----- MATLAB/Algorithms/MLEM.m | 48 ++- MATLAB/Algorithms/OS_ASD_POCS.m | 96 +++-- MATLAB/Algorithms/OS_AwASD_POCS.m | 91 +++-- MATLAB/Algorithms/OS_AwPCSD.m | 121 ++++--- MATLAB/Algorithms/OS_SART.m | 60 ++-- MATLAB/Algorithms/PCSD.m | 30 +- MATLAB/Algorithms/SART.m | 44 ++- MATLAB/Algorithms/SART_TV.m | 44 ++- MATLAB/Algorithms/SIRT.m | 42 ++- MATLAB/Algorithms/hybrid_LSQR.m | 129 ++++--- MATLAB/Algorithms/hybrid_fLSQR_TV.m | 50 ++- .../Quality_measures/Measure_Quality.m | 6 +- 21 files changed, 1365 insertions(+), 871 deletions(-) diff --git a/MATLAB/Algorithms/ASD_POCS.m b/MATLAB/Algorithms/ASD_POCS.m index 66e2a6b1..d28f699d 100644 --- a/MATLAB/Algorithms/ASD_POCS.m +++ b/MATLAB/Algorithms/ASD_POCS.m @@ -18,7 +18,7 @@ % % 'init': Describes diferent initialization techniques. % • 'none' : Initializes the image to zeros (default) - +% % • 'FDK' : intializes image to FDK reconstrucition % 'TViter': Defines the amount of TV iterations performed per SART % iteration. Default is 20 @@ -45,6 +45,9 @@ % 'redundancy_weighting': true or false. Default is true. Applies data % redundancy weighting to projections in the update step % (relevant for offset detector geometry) +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -66,17 +69,25 @@ %% parse inputs blocksize=1; -[beta,beta_red,f,ng,verbose,alpha,alpha_red,rmax,epsilon,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); +[beta,beta_red,f,ng,verbose,alpha,alpha_red,rmax,epsilon,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,varargin); + +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt +end +if nargout<2 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end +qualMeasOut=zeros(length(QualMeasOpts),niter); + [alphablocks,orig_index]=order_subsets(angles,blocksize,OrderStrategy); angles_reorder=cell2mat(alphablocks); index_angles=cell2mat(orig_index); -if measurequality - qualMeasOut=zeros(length(QualMeasOpts),maxiter); -end - % does detector rotation exists? if ~isfield(geo,'rotDetector') geo.rotDetector=[0;0;0]; @@ -114,6 +125,11 @@ DSD=geo.DSD; DSO=geo.DSO; while ~stop_criteria %POCS + % If quality is going to be measured, then we need to save previous image + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = f; % only store if necesary + end + f0=f; if (iter==0 && verbose==1);tic;end iter=iter+1; @@ -154,7 +170,7 @@ geo.rotDetector=rotDetector; % Save copy of image. if measurequality - qualMeasOut(:,iter)=Measure_Quality(f0,f,QualMeasOpts); + qualMeasOut(:,iter)=Measure_Quality(res_prev,f,QualMeasOpts); end % compute L2 error of actual image. Ax-b dd=im3Dnorm(Ax(f,geo,angles,'gpuids',gpuids)-proj,'L2'); @@ -223,9 +239,9 @@ end -function [beta,beta_red,f0,ng,verbose,alpha,alpha_red,rmax,epsilon,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,argin) +function [beta,beta_red,f0,ng,verbose,alpha,alpha_red,rmax,epsilon,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,argin) -opts= {'lambda','lambda_red','init','tviter','verbose','alpha','alpha_red','ratio','maxl2err','orderstrategy','qualmeas','nonneg','gpuids','redundancy_weighting'}; +opts= {'lambda','lambda_red','init','tviter','verbose','alpha','alpha_red','ratio','maxl2err','orderstrategy','qualmeas','nonneg','gpuids','redundancy_weighting','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -272,9 +288,9 @@ warning('TIGRE:Verbose mode not available for older versions than MATLAB R2014b'); verbose=false; end - % Lambda - % ========================================================================= - % Its called beta in ASD-POCS + % Lambda + % ========================================================================= + % Its called beta in ASD-POCS case 'lambda' if default beta=1; @@ -284,8 +300,8 @@ end beta=val; end - % Lambda reduction - % ========================================================================= + % Lambda reduction + % ========================================================================= case 'lambda_red' if default beta_red=0.99; @@ -295,70 +311,70 @@ end beta_red=val; end - % Initial image - % ========================================================================= + % Initial image + % ========================================================================= case 'init' if default || strcmp(val,'none') f0=zeros(geo.nVoxel','single'); - + else if strcmp(val,'FDK') f0=FDK(proj, geo, angles); else error('TIGRE:ASD_POCS:InvalidInput','Invalid init') - + end end - % Number of iterations of TV - % ========================================================================= + % Number of iterations of TV + % ========================================================================= case 'tviter' if default ng=20; else ng=val; end - % TV hyperparameter - % ========================================================================= + % TV hyperparameter + % ========================================================================= case 'alpha' if default alpha=0.002; % 0.2 else alpha=val; end - % TV hyperparameter redution - % ========================================================================= + % TV hyperparameter redution + % ========================================================================= case 'alpha_red' if default alpha_red=0.95; else alpha_red=val; end - % Maximum update ratio - % ========================================================================= + % Maximum update ratio + % ========================================================================= case 'ratio' if default rmax=0.95; else rmax=val; end - % Maximum L2 error to have a "good image" - % ========================================================================= + % Maximum L2 error to have a "good image" + % ========================================================================= case 'maxl2err' if default epsilon=im3Dnorm(Ax(FDK(proj,geo,angles),geo,angles)-proj,'L2')*0.2; %heuristic else epsilon=val; end - % Order strategy - % ========================================================================= + % Order strategy + % ========================================================================= case 'orderstrategy' if default OrderStrategy='random'; else OrderStrategy=val; end - % Image Quality Measure - % ========================================================================= + % Image Quality Measure + % ========================================================================= case 'qualmeas' if default QualMeasOpts={}; @@ -369,29 +385,35 @@ error('TIGRE:ASD_POCS:InvalidInput','Invalid quality measurement parameters'); end end - % Non negative - % ========================================================================= + % Non negative + % ========================================================================= case 'nonneg' if default nonneg=true; else nonneg=val; end - % GPU Ids - % ========================================================================= + % GPU Ids + % ========================================================================= case 'gpuids' if default gpuids = GpuIds(); else gpuids = val; end - % Data Redundancy weighting + % Data Redundancy weighting case 'redundancy_weighting' if default redundancy_weights = true; else redundancy_weights = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:ASD_POCS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in ASD_POCS()']); diff --git a/MATLAB/Algorithms/AwASD_POCS.m b/MATLAB/Algorithms/AwASD_POCS.m index 88fc2d5b..923023d1 100644 --- a/MATLAB/Algorithms/AwASD_POCS.m +++ b/MATLAB/Algorithms/AwASD_POCS.m @@ -53,6 +53,9 @@ % 'redundancy_weighting': true or false. Default is true. Applies data % redundancy weighting to projections in the update step % (relevant for offset detector geometry) +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -71,12 +74,20 @@ %% parse inputs blocksize=1; -[beta,beta_red,f,ng,verbose,alpha,alpha_red,rmax,epsilon,delta,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); +[beta,beta_red,f,ng,verbose,alpha,alpha_red,rmax,epsilon,delta,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,varargin); -if measurequality - qualMeasOut=zeros(length(QualMeasOpts),maxiter); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt end +if nargout<2 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end +qualMeasOut=zeros(length(QualMeasOpts),niter); + [alphablocks,orig_index]=order_subsets(angles,blocksize,OrderStrategy); @@ -120,6 +131,10 @@ DSO=geo.DSO; while ~stop_criteria %POCS + % If quality is going to be measured, then we need to save previous image + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = f; % only store if necesary + end f0=f; if (iter==0 && verbose==1);tic;end iter=iter+1; @@ -141,7 +156,9 @@ geo.DSO=DSO(jj); end f=f+beta* bsxfun(@times,1./V(:,:,jj),Atb(W(:,:,jj).*(proj(:,:,index_angles(:,jj))-Ax(f,geo,angles(:,jj),'gpuids',gpuids)),geo,angles(:,jj),'gpuids',gpuids)); - f(f<0)=0; + if nonneg + f(f<0)=0; + end end geo.offDetector=offDetector; @@ -150,7 +167,7 @@ geo.DSO=DSO; geo.rotDetector=rotDetector; if measurequality - qualMeasOut(:,iter)=Measure_Quality(f0,f,QualMeasOpts); + qualMeasOut(:,iter)=Measure_Quality(res_prev,f,QualMeasOpts); end % compute L2 error of actual image. Ax-b @@ -219,8 +236,8 @@ end -function [beta,beta_red,f0,ng,verbose,alpha,alpha_red,rmax,epsilon,delta,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,argin) -opts= {'lambda','lambda_red','init','tviter','verbose','alpha','alpha_red','ratio','maxl2err','delta','orderstrategy','qualmeas','nonneg','gpuids','redundancy_weighting'}; +function [beta,beta_red,f0,ng,verbose,alpha,alpha_red,rmax,epsilon,delta,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,argin) +opts= {'lambda','lambda_red','init','tviter','verbose','alpha','alpha_red','ratio','maxl2err','delta','orderstrategy','qualmeas','nonneg','gpuids','redundancy_weighting','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -267,8 +284,8 @@ warning('TIGRE:Verbose mode not available for older versions than MATLAB R2014b'); verbose=false; end - % Lambda - % ========================================================================= + % Lambda + % ========================================================================= case 'lambda' if default beta=1; @@ -278,8 +295,8 @@ end beta=val; end - % Lambda reduction - % ========================================================================= + % Lambda reduction + % ========================================================================= case 'lambda_red' if default beta_red=0.99; @@ -289,12 +306,12 @@ end beta_red=val; end - % Initial image - % ========================================================================= + % Initial image + % ========================================================================= case 'init' if default || strcmp(val,'none') f0=zeros(geo.nVoxel','single'); - + else if strcmp(val,'FDK') f0=FDK(proj, geo, angles); @@ -302,49 +319,49 @@ error('TIGRE:AwASD_POCS:InvalidInput','Invalid init') end end - % Number of iterations of TV - % ========================================================================= + % Number of iterations of TV + % ========================================================================= case 'tviter' if default ng=20; else ng=val; end - % TV hyperparameter - % ========================================================================= + % TV hyperparameter + % ========================================================================= case 'alpha' if default alpha=0.002; % 0.2 else alpha=val; end - % TV hyperparameter redution - % ========================================================================= + % TV hyperparameter redution + % ========================================================================= case 'alpha_red' if default alpha_red=0.95; else alpha_red=val; end - % Maximum update ratio - % ========================================================================= + % Maximum update ratio + % ========================================================================= case 'ratio' if default rmax=0.95; else rmax=val; end - % Maximum L2 error to have a "good image" - % ========================================================================= + % Maximum L2 error to have a "good image" + % ========================================================================= case 'maxl2err' if default epsilon=im3Dnorm(FDK(proj,geo,angles),'L2')*0.2; %heuristic else epsilon=val; end - %Parameter to control the amount of smoothing for pixels at the - %edges - % ========================================================================= + %Parameter to control the amount of smoothing for pixels at the + %edges + % ========================================================================= case 'delta' if default delta=-0.005; @@ -357,8 +374,8 @@ else OrderStrategy=val; end - % Image Quality Measure - % ========================================================================= + % Image Quality Measure + % ========================================================================= case 'qualmeas' if default QualMeasOpts={}; @@ -369,16 +386,16 @@ error('TIGRE:AwASD_POCS:InvalidInput','Invalid quality measurement parameters'); end end - % Non negative - % ========================================================================= + % Non negative + % ========================================================================= case 'nonneg' if default nonneg=true; else nonneg=val; end - % GPU Ids - % ========================================================================= + % GPU Ids + % ========================================================================= case 'gpuids' if default gpuids = GpuIds(); @@ -391,6 +408,12 @@ else redundancy_weights = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:AwASD_POCS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in AwASD_POCS()']); diff --git a/MATLAB/Algorithms/AwPCSD.m b/MATLAB/Algorithms/AwPCSD.m index ede7717b..a16769e5 100644 --- a/MATLAB/Algorithms/AwPCSD.m +++ b/MATLAB/Algorithms/AwPCSD.m @@ -1,4 +1,4 @@ -function [ f,errorSART,errorTV,errorL2,qualMeasOut] = AwPCSD(proj,geo,angles,maxiter,varargin) +function [ f,resL2,qualMeasOut] = AwPCSD(proj,geo,angles,maxiter,varargin) %AwPCSD solves the reconstruction problem using adaptive-weighted %projection-controlled steepest descent method % @@ -39,6 +39,9 @@ % 'redundancy_weighting': true or false. Default is true. Applies data % redundancy weighting to projections in the update step % (relevant for offset detector geometry) +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -56,19 +59,28 @@ % Coded by: Ander Biguri and Manasavee Lohvithee %-------------------------------------------------------------------------- %% parse inputs -[beta,beta_red,f,ng,verbose,epsilon,delta,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); +[beta,beta_red,f,ng,verbose,epsilon,delta,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,varargin); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end +qualMeasOut=zeros(length(QualMeasOpts),niter); if nargout>1 computeL2=true; else computeL2=false; end -errorL2=[]; +resL2=zeros(1,niter); + -errorSART=[]; -errorTV=[]; % [alphablocks,orig_index]=order_subsets(angles,blocksize,OrderStrategy); % @@ -113,6 +125,10 @@ DSD=geo.DSD; DSO=geo.DSO; while ~stop_criteria %POCS + % If quality is going to be measured, then we need to save previous image + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = f; % only store if necesary + end f0=f; if (iter==0 && verbose==1);tic;end iter=iter+1; @@ -146,24 +162,20 @@ end %Non-negativity projection on all pixels - f=max(f,0); + if nonneg + f=max(f,0); + end geo.offDetector=offDetector; geo.offOrigin=offOrigin; if measurequality - qualMeasOut(:,iter)=Measure_Quality(f0,f,QualMeasOpts); + qualMeasOut(:,iter)=Measure_Quality(res_prev,f,QualMeasOpts); end % Compute L2 error of actual image. Ax-b dd=im3Dnorm(Ax(f,geo,angles,'gpuids',gpuids)-proj,'L2'); - - %Compute errorSART - errorSARTnow=im3Dnorm(proj-Ax(f,geo,angles,'gpuids',gpuids),'L2'); - errorSART=[errorSART errorSARTnow]; - - % Compute change in the image after last SART iteration dp_vec=(f-f0); @@ -190,11 +202,6 @@ delta_p_first=im3Dnorm((Ax(f0,geo,angles,'interpolated','gpuids',gpuids))-proj,'L2'); end - %Compute errorTV - errorTVnow=im3Dnorm(proj-Ax(f,geo,angles,'gpuids',gpuids),'L2'); - errorTV=[errorTV errorTVnow]; - - % Reduce SART step beta=beta*beta_red; @@ -243,8 +250,8 @@ end -function [beta,beta_red,f0,ng,verbose,epsilon,delta,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,argin) -opts= {'lambda','lambda_red','init','tviter','verbose','maxl2err','delta','qualmeas','nonneg','gpuids','redundancy_weighting'}; +function [beta,beta_red,f0,ng,verbose,epsilon,delta,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,argin) +opts= {'lambda','lambda_red','init','tviter','verbose','maxl2err','delta','qualmeas','nonneg','gpuids','redundancy_weighting','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -291,8 +298,8 @@ warning('TIGRE:Verbose mode not available for older versions than MATLAB R2014b'); verbose=false; end - % Lambda - % ========================================================================= + % Lambda + % ========================================================================= case 'lambda' if default beta=1; @@ -302,8 +309,8 @@ end beta=val; end - % Lambda reduction - % ========================================================================= + % Lambda reduction + % ========================================================================= case 'lambda_red' if default beta_red=0.99; @@ -313,12 +320,12 @@ end beta_red=val; end - % Initial image - % ========================================================================= + % Initial image + % ========================================================================= case 'init' if default || strcmp(val,'none') f0=zeros(geo.nVoxel','single'); - + else if strcmp(val,'FDK') f0=FDK(proj, geo, angles); @@ -326,33 +333,33 @@ error('TIGRE:AwPCSD:InvalidInput','Invalid init') end end - % Number of iterations of TV - % ========================================================================= + % Number of iterations of TV + % ========================================================================= case 'tviter' if default ng=20; else ng=val; end - % Maximum L2 error to have a "good image" - % ========================================================================= + % Maximum L2 error to have a "good image" + % ========================================================================= case 'maxl2err' if default epsilon=im3Dnorm(FDK(proj,geo,angles))*0.2; %heuristic else epsilon=val; end - %Parameter to control the amount of smoothing for pixels at the - %edges - % ========================================================================= + %Parameter to control the amount of smoothing for pixels at the + %edges + % ========================================================================= case 'delta' if default delta=-0.005; else delta=val; end - % Image Quality Measure - % ========================================================================= + % Image Quality Measure + % ========================================================================= case 'qualmeas' if default QualMeasOpts={}; @@ -363,16 +370,16 @@ error('TIGRE:AwPCSD:InvalidInput','Invalid quality measurement parameters'); end end - % Non negative - % ========================================================================= + % Non negative + % ========================================================================= case 'nonneg' if default nonneg=true; else nonneg=val; end - % GPU Ids - % ========================================================================= + % GPU Ids + % ========================================================================= case 'gpuids' if default gpuids = GpuIds(); @@ -385,6 +392,12 @@ else redundancy_weights = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:AwPCSD:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in AwPCSD()']); diff --git a/MATLAB/Algorithms/B_ASD_POCS_beta.m b/MATLAB/Algorithms/B_ASD_POCS_beta.m index 897533b2..d6a91a34 100644 --- a/MATLAB/Algorithms/B_ASD_POCS_beta.m +++ b/MATLAB/Algorithms/B_ASD_POCS_beta.m @@ -61,6 +61,9 @@ % 'redundancy_weighting': true or false. Default is true. Applies data % redundancy weighting to projections in the update step % (relevant for offset detector geometry) +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -84,16 +87,23 @@ blocksize=1; [beta,beta_red,f,ng,verbose,alpha,alpha_red,rmax,epsilon,bregman,bregman_red,bregman_iter,OrderStrategy,nonneg,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); [alphablocks,orig_index]=order_subsets(angles,blocksize,OrderStrategy); angles_reorder=cell2mat(alphablocks); index_angles=cell2mat(orig_index); -if measurequality - qualMeasOut=zeros(length(QualMeasOpts),maxiter); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt end +if nargout<2 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end +qualMeasOut=zeros(length(QualMeasOpts),niter); % does detector rotation exists? @@ -136,6 +146,10 @@ DSD=geo.DSD; DSO=geo.DSO; while ~stop_criteria %POCS + % If quality is going to be measured, then we need to save previous image + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = f; % only store if necesary + end f0=f; if (iter==0 && verbose==1);tic;end iter=iter+1; @@ -167,11 +181,11 @@ f=max(f,0); end end - + if measurequality - qualMeasOut(:,iter)=Measure_Quality(f0,f,QualMeasOpts); + qualMeasOut(:,iter)=Measure_Quality(res_prev,f,QualMeasOpts); end - + geo.offDetector=offDetector; geo.offOrigin=offOrigin; geo.DSD=DSD; @@ -249,9 +263,9 @@ end -function [beta,beta_red,f0,ng,verbose,alpha,alpha_red,rmax,epsilon,bregman,bregman_red,bregman_iter,OrderStrategy,nonneg,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,argin) +function [beta,beta_red,f0,ng,verbose,alpha,alpha_red,rmax,epsilon,bregman,bregman_red,bregman_iter,OrderStrategy,nonneg,QualMeasOpts,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,argin) -opts= {'lambda','lambda_red','init','tviter','verbose','alpha','alpha_red','ratio','maxl2err','beta','beta_red','bregman_iter','orderstrategy','nonneg','qualmeas','gpuids','redundancy_weighting'}; +opts= {'lambda','lambda_red','init','tviter','verbose','alpha','alpha_red','ratio','maxl2err','beta','beta_red','bregman_iter','orderstrategy','nonneg','qualmeas','gpuids','redundancy_weighting','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -298,9 +312,9 @@ warning('TIGRE:Verbose mode not available for older versions than MATLAB R2014b'); verbose=false; end - % Lambda - % ========================================================================= - % Its called beta in ASD-POCS + % Lambda + % ========================================================================= + % Its called beta in ASD-POCS case 'lambda' if default beta=1; @@ -310,8 +324,8 @@ end beta=val; end - % Lambda reduction - % ========================================================================= + % Lambda reduction + % ========================================================================= case 'lambda_red' if default beta_red=0.99; @@ -321,70 +335,70 @@ end beta_red=val; end - % Initial image - % ========================================================================= + % Initial image + % ========================================================================= case 'init' if default || strcmp(val,'none') f0=zeros(geo.nVoxel','single'); - + else if strcmp(val,'FDK') f0=FDK(proj, geo, angles); else error('TIGRE:B_ASD_POCS_beta:InvalidInput','Invalid init') - + end end - % Number of iterations of TV - % ========================================================================= + % Number of iterations of TV + % ========================================================================= case 'tviter' if default ng=20; else ng=val; end - % TV hyperparameter - % ========================================================================= + % TV hyperparameter + % ========================================================================= case 'alpha' if default alpha=0.002; % 0.2 else alpha=val; end - % TV hyperparameter redution - % ========================================================================= + % TV hyperparameter redution + % ========================================================================= case 'alpha_red' if default alpha_red=0.95; else alpha_red=val; end - % Maximum update ratio - % ========================================================================= + % Maximum update ratio + % ========================================================================= case 'ratio' if default rmax=0.95; else rmax=val; end - % Maximum L2 error to have a "good image" - % ========================================================================= + % Maximum L2 error to have a "good image" + % ========================================================================= case 'maxl2err' if default epsilon=im3Dnorm(FDK(proj,geo,angles))*0.2; %heuristic else epsilon=val; end - % TV bregman hyperparameter - % ========================================================================= + % TV bregman hyperparameter + % ========================================================================= case 'beta' if default bregman=1; else bregman=val; end - % TV bregman hyperparameter redution - % ========================================================================= + % TV bregman hyperparameter redution + % ========================================================================= case 'beta_red' if default bregman_red=0.75; @@ -409,8 +423,8 @@ else nonneg=val; end - % Image Quality Measure - % ========================================================================= + % Image Quality Measure + % ========================================================================= case 'qualmeas' if default QualMeasOpts={}; @@ -419,7 +433,7 @@ QualMeasOpts=val; else error('TIGRE:B_ASD_POCS_beta:InvalidInput','Invalid quality measurement parameters'); - + end end case 'gpuids' @@ -434,6 +448,12 @@ else redundancy_weights = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:B_ASD_POCS_beta:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option']); diff --git a/MATLAB/Algorithms/CGLS.m b/MATLAB/Algorithms/CGLS.m index b1e5b4ad..db45931d 100644 --- a/MATLAB/Algorithms/CGLS.m +++ b/MATLAB/Algorithms/CGLS.m @@ -1,15 +1,15 @@ function [x,resL2,qualMeasOut]= CGLS(proj,geo,angles,niter,varargin) % CGLS solves the CBCT problem using the conjugate gradient least % squares -% +% % CGLS(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem % using the projection data PROJ taken over ANGLES angles, corresponding % to the geometry descrived in GEO, using NITER iterations. -% +% % CGLS(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The % possible options in OPT are: -% -% +% +% % 'Init' Describes diferent initialization techniques. % * 'none' : Initializes the image to zeros (default) % * 'FDK' : intializes image to FDK reconstrucition @@ -20,88 +20,123 @@ % image. Not recomended unless you really % know what you are doing. % 'InitImg' an image for the 'image' initialization. Avoid. +% +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. +% 'restart' true or false. By default the algorithm will restart when +% loss of ortogonality is found. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox -% -% Copyright (c) 2015, University of Bath and +% +% Copyright (c) 2015, University of Bath and % CERN-European Organization for Nuclear Research % All rights reserved. % -% License: Open Source under BSD. +% License: Open Source under BSD. % See the full license at % https://github.com/CERN/TIGRE/blob/master/LICENSE % % Contact: tigre.toolbox@gmail.com % Codes: https://github.com/CERN/TIGRE/ -% Coded by: Ander Biguri +% Coded by: Ander Biguri %-------------------------------------------------------------------------- %% -[verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); +[verbose,x,QualMeasOpts,gpuids,gt,restart]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + x0=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end qualMeasOut=zeros(length(QualMeasOpts),niter); - -% //doi: 10.1088/0031-9155/56/13/004 - -r=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); - -p=Atb(r,geo,angles,'matched','gpuids',gpuids); - -gamma=norm(p(:),2)^2; - resL2=zeros(1,niter); -for ii=1:niter - x0 = x; - if (ii==1 && verbose);tic;end - - q=Ax(p,geo,angles,'Siddon','gpuids',gpuids); - alpha=gamma/norm(q(:),2)^2; - x=x+alpha*p; - - aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); %expensive, is there any way to check this better? - resL2(ii)=im3Dnorm(aux,'L2'); - - if measurequality - qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); - end - if ii>1 && resL2(ii)>resL2(ii-1) - % OUT! - x=x-alpha*p; - if verbose - disp(['CGLS stoped in iteration N', num2str(ii),' due to divergence.']) - end - return; +% //doi: 10.1088/0031-9155/56/13/004 +iter=0; +remember=0; +while iter1 && resL2(iter)>resL2(iter-1) + % we lost orthogonality, lets restart the algorithm unless the + % user asked us not to. + + % undo bad step. + x=x-alpha*p; + % if the restart didn't work. + if remember==iter || ~restart + disp(['Algorithm stoped in iteration ', num2str(iter),' due to loss of ortogonality.']) + return; + end + remember=iter; + iter=iter-1; + if verbose + disp(['Orthogonality lost, restarting at iteration ', num2str(iter) ]) + end + break + + end + % If step is adecuate, then continue withg CGLS + r=r-alpha*q; + s=Atb(r,geo,angles,'matched','gpuids',gpuids); + + gamma1=norm(s(:),2)^2; + beta=gamma1/gamma; + gamma=gamma1; + p=s+beta*p; + + + if (iter==1 && verbose) + expected_time=toc*niter; + disp('CGLS'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end end - % If step is adecuatem, then continue withg CGLS - r=r-alpha*q; - s=Atb(r,geo,angles,'matched','gpuids',gpuids); - - gamma1=norm(s(:),2)^2; - beta=gamma1/gamma; - gamma=gamma1; - p=s+beta*p; - - - if (ii==1 && verbose) - expected_time=toc*niter; - disp('CGLS'); - disp(['Expected duration : ',secs2hms(expected_time)]); - disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); - disp(''); - end end - end %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids'}; +function [verbose,x,QualMeasOpts,gpuids,gt,restart]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','groundtruth','restart'}; defaults=ones(length(opts),1); % Check inputs @@ -116,7 +151,7 @@ if ~isempty(ind) defaults(ind)=0; else - error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); end end @@ -124,14 +159,14 @@ opt=opts{ii}; default=defaults(ii); % if one option isnot default, then extranc value from input - if default==0 + if default==0 ind=double.empty(0,1);jj=1; while isempty(ind) ind=find(isequal(opt,lower(argin{jj}))); jj=jj+1; end if isempty(ind) - error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + error('TIGRE:CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); end val=argin{jj}; end @@ -156,7 +191,7 @@ continue; end if isempty(x) - error('TIGRE:CGLS:InvalidInput','Invalid Init option') + error('TIGRE:CGLS:InvalidInput','Invalid Init option') end % % % % % % % ERROR case 'initimg' @@ -170,7 +205,7 @@ error('TIGRE:CGLS:InvalidInput','Invalid image for initialization'); end end - % ========================================================================= + % ========================================================================= case 'qualmeas' if default QualMeasOpts={}; @@ -181,7 +216,7 @@ error('TIGRE:CGLS:InvalidInput','Invalid quality measurement parameters'); end end - case 'verbose' + case 'verbose' if default verbose=1; else @@ -197,7 +232,19 @@ else gpuids = val; end - otherwise + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end + case 'restart' + if default + restart=true; + else + restart=val; + end + otherwise error('TIGRE:CGLS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); end end diff --git a/MATLAB/Algorithms/FISTA.m b/MATLAB/Algorithms/FISTA.m index 33e38e44..0cde93a5 100644 --- a/MATLAB/Algorithms/FISTA.m +++ b/MATLAB/Algorithms/FISTA.m @@ -6,10 +6,10 @@ % Processing Conference, 2018, pp.732-736. Lazy-start FISTA_mod % -% 'hyper': This parameter should approximate the largest -% eigenvalue in the A matrix in the equation Ax-b and Atb. +% 'hyper': This parameter should approximate the largest +% eigenvalue in the A matrix in the equation Ax-b and Atb. % Empirical tests show that for, the headphantom object: -% +% % geo.nVoxel = [64,64,64]' , hyper (approx=) 2.e8 % geo.nVoxel = [512,512,512]' , hyper (approx=) 2.e4 % Default: 2.e8 @@ -17,7 +17,7 @@ % • 'none' : Initializes the image to zeros (default) % • 'FDK' : intializes image to FDK reconstrucition % 'tviter': Number of iterations of Im3ddenoise to use. Default: 20 -% 'lambda': Multiplier for the tvlambda used, which is proportional to +% 'lambda': Multiplier for the tvlambda used, which is proportional to % L (hyper). Default: 0.1 % 'fista_p': P parameter for "faster" FISTA (say 1/50). Default: 1 (standard % FISTA) @@ -29,7 +29,9 @@ % parameters. Input should contain a cell array of desired % quality measurement names. Example: {'CC','RMSE','MSSIM'} % These will be computed in each iteration. - +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -46,10 +48,18 @@ % Codes: https://github.com/CERN/TIGRE/ % Coded by: Ander Biguri, Reuben Lindroos %-------------------------------------------------------------------------- -[verbose,res,tviter,hyper,lambda,p,q,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); -%res = zeros(geo.nVoxel','single'); -measurequality=~isempty(QualMeasOpts); +[verbose,res,tviter,hyper,lambda,p,q,QualMeasOpts,gpuids,gt]=parse_inputs(proj,geo,angles,varargin); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt +end +if nargout<2 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end qualMeasOut=zeros(length(QualMeasOpts),niter); @@ -58,26 +68,27 @@ bm = 1/L; t = 1; r = 1/4; -p = 1; -q = 1; + for ii = 1:niter - res0 = res; + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = res; % only store if necesary + end if (ii==1);tic;end % gradient descent step res = res + bm * 2 * Atb(proj - Ax(res,geo,angles, 'Siddon', 'gpuids', gpuids), geo, angles, 'matched', 'gpuids', gpuids); lambdaforTV = 2* bm* lambda; x_recold = x_rec; - x_rec = im3DDenoise(res,'TV',tviter,1/lambdaforTV, 'gpuids', gpuids); + x_rec = im3DDenoise(res,'TV',tviter,1/lambdaforTV, 'gpuids', gpuids); told = t; t = ( p+sqrt(q+r*t*t) ) / 2; res= x_rec + (told-1)/t * (x_rec - x_recold); - + if measurequality - qualMeasOut(:,ii)=Measure_Quality(res0,res,QualMeasOpts); + qualMeasOut(:,ii)=Measure_Quality(res_prev,res,QualMeasOpts); end - + if (ii==1)&&(verbose==1) expected_time=toc*niter; disp('FISTA'); @@ -90,8 +101,8 @@ end %% Parse inputs -function [verbose,f0,tviter,hyper,lambda,fista_p,fista_q,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,argin) -opts = {'lambda','init','tviter','verbose','hyper','fista_p','fista_q','qualmeas','gpuids'}; +function [verbose,f0,tviter,hyper,lambda,fista_p,fista_q,QualMeasOpts,gpuids,gt]=parse_inputs(proj,geo,angles,argin) +opts = {'lambda','init','tviter','verbose','hyper','fista_p','fista_q','qualmeas','gpuids','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -137,8 +148,8 @@ warning('TIGRE: Verbose mode not available for older versions than MATLAB R2014b'); verbose=false; end - % Initial image - % ========================================================================= + % Initial image + % ========================================================================= case 'init' if default || strcmp(val,'none') f0=zeros(geo.nVoxel','single'); @@ -149,7 +160,7 @@ error('TIGRE:FISTA:InvalidInput','Invalid init') end end - % % % % % % % hyperparameter, LAMBDA + % % % % % % % hyperparameter, LAMBDA case 'lambda' if default lambda=0.1; @@ -158,18 +169,18 @@ else lambda=val; end - % hyperparameter - % ========================================================== + % hyperparameter + % ========================================================== case 'hyper' if default hyper = 2.e8; elseif length(val)>1 || ~isnumeric( val) error('TIGRE:FISTA:InvalidInput','Invalid lambda') - else + else hyper = val; end - % Number of iterations of TV - % ========================================================================= + % Number of iterations of TV + % ========================================================================= case 'tviter' if default tviter = 20; @@ -178,28 +189,28 @@ else tviter = val; end - % FISTA parameter p - % ========================================================================= + % FISTA parameter p + % ========================================================================= case 'fista_p' if default - fista_p = 1; % standard FISTA, 1/50 for "faster" FISTA + fista_p = 1/50; % standard FISTA, 1/50 for "faster" FISTA elseif length(val)>1 || ~isnumeric( val) error('TIGRE:FISTA:InvalidInput','Invalid lambda') else fista_q = val; end - % Number of iterations of TV - % ========================================================================= + % Number of iterations of TV + % ========================================================================= case 'fista_q' if default - fista_q = 1; % standard FISTA, 1/20 for "faster" FISTA + fista_q = 1/20; % standard FISTA, 1/20 for "faster" FISTA elseif length(val)>1 || ~isnumeric( val) error('TIGRE:FISTA:InvalidInput','Invalid lambda') else fista_q = val; end - % Image Quality Measure - % ========================================================================= + % Image Quality Measure + % ========================================================================= case 'qualmeas' if default QualMeasOpts={}; @@ -210,14 +221,20 @@ error('TIGRE:FISTA:InvalidInput','Invalid quality measurement parameters'); end end - % GPU IDs - % ========================================================================= + % GPU IDs + % ========================================================================= case 'gpuids' if default gpuids = GpuIds(); else gpuids = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:FISTA:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in FISTA()']); end diff --git a/MATLAB/Algorithms/IRN_TV_CGLS.m b/MATLAB/Algorithms/IRN_TV_CGLS.m index b65e04f9..03dcdaec 100644 --- a/MATLAB/Algorithms/IRN_TV_CGLS.m +++ b/MATLAB/Algorithms/IRN_TV_CGLS.m @@ -1,15 +1,15 @@ function [x_out,resL2,qualMeasOut]= IRN_TV_CGLS(proj,geo,angles,niter,varargin) % IRN_TV_CGLS solves the IRN_TV_CGLS problem using the conjugate gradient least % squares with Total Variation regularization, using an inner outer scheme -% +% % IRN_TV_CGLS(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem % using the projection data PROJ taken over ANGLES angles, corresponding % to the geometry descrived in GEO, using NITER iterations. -% +% % IRN_TV_CGLS(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The % possible options in OPT are: -% -% 'lambda' Value of regularization parameter lambda, default 10. +% +% 'lambda' Value of regularization parameter lambda, default 10. % 'Init' Describes diferent initialization techniques. % * 'none' : Initializes the image to zeros (default) % * 'FDK' : intializes image to FDK reconstrucition @@ -20,21 +20,26 @@ % image. Not recomended unless you really % know what you are doing. % 'InitImg' an image for the 'image' initialization. Avoid. +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. +% 'restart' true or false. By default the algorithm will restart when +% loss of ortogonality is found. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox -% -% Copyright (c) 2015, University of Bath and +% +% Copyright (c) 2015, University of Bath and % CERN-European Organization for Nuclear Research % All rights reserved. % -% License: Open Source under BSD. +% License: Open Source under BSD. % See the full license at % https://github.com/CERN/TIGRE/blob/master/LICENSE % % Contact: tigre.toolbox@gmail.com % Codes: https://github.com/CERN/TIGRE/ -% Coded by: Malena Sabate Landman, Ander Biguri +% Coded by: Malena Sabate Landman, Ander Biguri %-------------------------------------------------------------------------- %% @@ -43,20 +48,33 @@ % % Paul Rodriguez and Brendt Wohlberg % % 10.1109/ACSSC.2006.354879 -[verbose,x0,QualMeasOpts,gpuids,lambda,niter_outer]=parse_inputs(proj,geo,angles,varargin); +[verbose,x0,QualMeasOpts,gpuids,lambda,niter_outer,gt,restart]=parse_inputs(proj,geo,angles,varargin); x=x0; +niter_break=round(niter/niter_outer); -measurequality=~isempty(QualMeasOpts); -qualMeasOut=zeros(length(QualMeasOpts),niter*niter_outer); -resL2=zeros(1,niter*niter_outer); - -for iii = 1:niter_outer - if (iii==1 && verbose);tic;end - +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + x_prev=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end +qualMeasOut=zeros(length(QualMeasOpts),niter); +resL2 = zeros(1,niter); + +remember=[0]; +iter=0; +% for iii = 1:niter_outer +while iterresL2(iter-1) + % we lost orthogonality, lets restart the algorithm unless the + % user asked us not to. + iter + niter_break + % undo bad step. + x=x-alpha*p; + % if the restart didn't work. + if remember==iter || ~restart + disp(['Algorithm stoped in iteration ', num2str(iter),' due to loss of ortogonality.']) + return; + end + remember=iter; + iter=iter-1; + if verbose + disp(['Orthogonality lost, restarting at iteration ', num2str(iter) ]) + end + break + end + % If step is adecuate, then continue withg CGLS r_aux_1 = r_aux_1-alpha*q_aux_1; r_aux_2=r_aux_2-alpha*q_aux_2; - + s_aux_1 = Atb(r_aux_1 ,geo,angles,'matched','gpuids',gpuids); s_aux_2 = sqrt(lambda) * Ltx (W, r_aux_2); s = s_aux_1 + s_aux_2; - + gamma1=norm(s(:),2)^2; beta=gamma1/gamma; gamma=gamma1; p=s+beta*p; - - - end - - if (iii==1 && verbose) - expected_time=toc*niter_outer; - disp('IRN_TV_CGLS'); - disp(['Expected duration : ',secs2hms(expected_time)]); - disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); - disp(''); + + % replaces double explicit loops. Breaks the inner loop when its + % time to do a cold restart. + if mod(iter,niter_break)==0 + break + end + if (iter==1 && verbose) + expected_time=toc*niter; + disp('IRN_TV_CGLS'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end end + + end end % % % Non-sense now, just giving output of the right dimensions % % function out = Lx (sizeD3_1, sizeD3_2, x) % % % L = speye(3*sizeD3_1,sizeD3_2 ); -% % if prod(size(x)) == sizeD3_2 && 3*prod(size(x(:,:,1:end-1))) == sizeD3_1 +% % if prod(size(x)) == sizeD3_2 && 3*prod(size(x(:,:,1:end-1))) == sizeD3_1 % % out = cat(3,x(:,:,1:end-1),x(:,:,1:end-1),x(:,:,1:end-1)); -% % else +% % else % % error('wrong dimensions') % % end % % end -% % +% % % % function out = Ltx (sizeD3_1, sizeD3_2, x) % % % L = speye(3*sizeD3_1,sizeD3_2 ); -% % if prod(size(x)) == sizeD3_1 +% % if prod(size(x)) == sizeD3_1 % % out = x(:,:,1:512); -% % else +% % else % % error('wrong dimensions') % % end % % end @@ -147,59 +191,59 @@ % How to make this efficient? function W = build_weights ( x) - % Directional discrete derivatives - % Reflective boundary conditions - Dxx=zeros(size(x),'single'); - Dyx=zeros(size(x),'single'); - Dzx=zeros(size(x),'single'); - - Dxx(1:end-1,:,:)=x(1:end-1,:,:)-x(2:end,:,:); - Dyx(:,1:end-1,:)=x(:,1:end-1,:)-x(:,2:end,:); - Dzx(:,:,1:end-1)=x(:,:,1:end-1)-x(:,:,2:end); +% Directional discrete derivatives +% Reflective boundary conditions +Dxx=zeros(size(x),'single'); +Dyx=zeros(size(x),'single'); +Dzx=zeros(size(x),'single'); + +Dxx(1:end-1,:,:)=x(1:end-1,:,:)-x(2:end,:,:); +Dyx(:,1:end-1,:)=x(:,1:end-1,:)-x(:,2:end,:); +Dzx(:,:,1:end-1)=x(:,:,1:end-1)-x(:,:,2:end); - W = (Dxx.^2+Dyx.^2+Dzx.^2+1e-6).^(-1/4); % Fix this... +W = (Dxx.^2+Dyx.^2+Dzx.^2+1e-6).^(-1/4); % Fix this... end function out = Lx (W, x) - % Directional discrete derivatives - % Reflective boundary conditions - Dxx=zeros(size(x),'single'); - Dyx=zeros(size(x),'single'); - Dzx=zeros(size(x),'single'); - - Dxx(1:end-1,:,:)=x(1:end-1,:,:)-x(2:end,:,:); - Dyx(:,1:end-1,:)=x(:,1:end-1,:)-x(:,2:end,:); - Dzx(:,:,1:end-1)=x(:,:,1:end-1)-x(:,:,2:end); - % Build weights - is it better to find the right rotation and add - % tensors? - out = cat(4,W .* Dxx, W .* Dyx, W .* Dzx); +% Directional discrete derivatives +% Reflective boundary conditions +Dxx=zeros(size(x),'single'); +Dyx=zeros(size(x),'single'); +Dzx=zeros(size(x),'single'); + +Dxx(1:end-1,:,:)=x(1:end-1,:,:)-x(2:end,:,:); +Dyx(:,1:end-1,:)=x(:,1:end-1,:)-x(:,2:end,:); +Dzx(:,:,1:end-1)=x(:,:,1:end-1)-x(:,:,2:end); +% Build weights - is it better to find the right rotation and add +% tensors? +out = cat(4,W .* Dxx, W .* Dyx, W .* Dzx); end function out = Ltx (W, x) - Wx_1 = W .* x(:,:,:,1); - Wx_2 = W .* x(:,:,:,2); - Wx_3 = W .* x(:,:,:,3); +Wx_1 = W .* x(:,:,:,1); +Wx_2 = W .* x(:,:,:,2); +Wx_3 = W .* x(:,:,:,3); - % Left here, but this is how to make a transpose - DxtWx_1=Wx_1; - DytWx_2=Wx_2; - DztWx_3=Wx_3; - - DxtWx_1(2:end-1,:,:)=Wx_1(2:end-1,:,:)-Wx_1(1:end-2,:,:); - DxtWx_1(end,:,:)=-Wx_1(end-1,:,:); - - DytWx_2(:,2:end-1,:)=Wx_2(:,2:end-1,:)-Wx_2(:,1:end-2,:); - DytWx_2(:,end,:)=-Wx_2(:,end-1,:); - - DztWx_3(:,:,2:end-1)=Wx_3(:,:,2:end-1)-Wx_3(:,:,1:end-2); - DztWx_3(:,:,end)=-Wx_3(:,:,end-1); +% Left here, but this is how to make a transpose +DxtWx_1=Wx_1; +DytWx_2=Wx_2; +DztWx_3=Wx_3; + +DxtWx_1(2:end-1,:,:)=Wx_1(2:end-1,:,:)-Wx_1(1:end-2,:,:); +DxtWx_1(end,:,:)=-Wx_1(end-1,:,:); + +DytWx_2(:,2:end-1,:)=Wx_2(:,2:end-1,:)-Wx_2(:,1:end-2,:); +DytWx_2(:,end,:)=-Wx_2(:,end-1,:); + +DztWx_3(:,:,2:end-1)=Wx_3(:,:,2:end-1)-Wx_3(:,:,1:end-2); +DztWx_3(:,:,end)=-Wx_3(:,:,end-1); - out = DxtWx_1 + DytWx_2 + DztWx_3; +out = DxtWx_1 + DytWx_2 + DztWx_3; end %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids,lambda,niter_outer]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids','lambda','niter_outer'}; +function [verbose,x,QualMeasOpts,gpuids,lambda,niter_outer,gt,restart]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','lambda','niter_outer','groundtruth','restart'}; defaults=ones(length(opts),1); % Check inputs @@ -214,7 +258,7 @@ if ~isempty(ind) defaults(ind)=0; else - error('TIGRE:IRN_TV_CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + error('TIGRE:IRN_TV_CGLS:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); end end @@ -222,14 +266,14 @@ opt=opts{ii}; default=defaults(ii); % if one option isnot default, then extranc value from input - if default==0 + if default==0 ind=double.empty(0,1);jj=1; while isempty(ind) ind=find(isequal(opt,lower(argin{jj}))); jj=jj+1; end if isempty(ind) - error('TIGRE:IRN_TV_CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + error('TIGRE:IRN_TV_CGLS:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); end val=argin{jj}; end @@ -254,7 +298,7 @@ continue; end if isempty(x) - error('TIGRE:IRN_TV_CGLS:InvalidInput','Invalid Init option') + error('TIGRE:IRN_TV_CGLS:InvalidInput','Invalid Init option') end case 'niter_outer' if default @@ -274,7 +318,7 @@ error('TIGRE:IRN_TV_CGLS:InvalidInput','Invalid image for initialization'); end end - % ========================================================================= + % ========================================================================= case 'qualmeas' if default QualMeasOpts={}; @@ -285,7 +329,7 @@ error('TIGRE:IRN_TV_CGLS:InvalidInput','Invalid quality measurement parameters'); end end - case 'verbose' + case 'verbose' if default verbose=1; else @@ -307,7 +351,19 @@ else lambda=val; end - otherwise + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end + case 'restart' + if default + restart=true; + else + restart=val; + end + otherwise error('TIGRE:IRN_TV_CGLS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in IRN_TV_CGLS()']); end end diff --git a/MATLAB/Algorithms/LSMR.m b/MATLAB/Algorithms/LSMR.m index 47750133..d19e2720 100644 --- a/MATLAB/Algorithms/LSMR.m +++ b/MATLAB/Algorithms/LSMR.m @@ -1,15 +1,15 @@ function [x,resL2,qualMeasOut]= LSMR(proj,geo,angles,niter,varargin) % LSMR solves the CBCT problem using LSMR. -% +% % LSMR(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem % using the projection data PROJ taken over ALPHA angles, corresponding % to the geometry descrived in GEO, using NITER iterations. -% +% % LSMR(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The % possible options in OPT are: -% -% 'lambda' Value of parameter lambda, default 0. +% +% 'lambda' Value of parameter lambda, default 0. % 'Init' Describes diferent initialization techniques. % * 'none' : Initializes the image to zeros (default) % * 'FDK' : intializes image to FDK reconstrucition @@ -20,164 +20,207 @@ % image. Not recomended unless you really % know what you are doing. % 'InitImg' an image for the 'image' initialization. Avoid. +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. +% 'restart' true or false. By default the algorithm will restart when +% loss of ortogonality is found. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox -% -% Copyright (c) 2015, University of Bath and +% +% Copyright (c) 2015, University of Bath and % CERN-European Organization for Nuclear Research % All rights reserved. % -% License: Open Source under BSD. +% License: Open Source under BSD. % See the full license at % https://github.com/CERN/TIGRE/blob/master/LICENSE % % Contact: tigre.toolbox@gmail.com % Codes: https://github.com/CERN/TIGRE/ -% Coded by: Malena Sabate Landman, Ander Biguri +% Coded by: Malena Sabate Landman, Ander Biguri %-------------------------------------------------------------------------- %% -[verbose,x,QualMeasOpts,gpuids,lambda]=parse_inputs(proj,geo,angles,varargin); +[verbose,x,QualMeasOpts,gpuids,lambda,gt,restart]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + x0=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end qualMeasOut=zeros(length(QualMeasOpts),niter); - +resL2 = zeros(1,niter); % David Chin-Lung Fong and Michael Saunders //doi.org/10.1137/10079687X -% Enumeration as given in the paper for 'Algorithm LSMR' -% (1) Initialize -u=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); -normr = norm(u(:),2); -beta = normr; -u = u/beta; - - -v=Atb(u,geo,angles,'matched','gpuids',gpuids); -alpha = norm(v(:),2); % msl: do we want to check if it is 0? -v = v/alpha; -alphabar = alpha; -zetabar = alpha * beta; -rho = 1; -rhobar = 1; -cbar = 1; -sbar = 0; -h = v; -hbar = 0; - -% Compute the residual norm ||r_k|| -betadd = beta; -betad = 0; -rhod = 1; -tautilda = 0; -thetatilda = 0; -zeta = 0; -d = 0; - -% msl: is this error the residual norm ? -resL2 = zeros(1,niter); - -% (2) Start iterations -for ii=1:niter - x0 = x; - if (ii==1 && verbose);tic;end +% Enumeration as given in the paper for 'Algorithm LSMR' +iter=0; +remember=0; +while iter= 2, construct and apply \tilde{P} to compute ||r_k|| - rhotilda = sqrt(rhod^2 + thetabar^2); - ctilda = rhod / rhotilda; - stilda = thetabar / rhotilda; - thetatildapre = thetatilda; - thetatilda = stilda * rhobar; - rhod = ctilda * rhobar; - % betatilda = ctilda * betad + stilda * betahat; % msl: in the orinal paper, but not used - betad = -stilda * betad + ctilda * betahat; - - % (10) Update \tilde{t}_k by forward substitution - tautilda = (zetapre - thetatildapre*tautilda) / rhotilda; - taud = (zeta - thetatilda*tautilda) / rhod; + v=Atb(u,geo,angles,'matched','gpuids',gpuids); + alpha = norm(v(:),2); % msl: do we want to check if it is 0? + v = v/alpha; - % (11) Compute ||r_k|| - d = d + betacheck^2; - gamma_var = d + (betad - taud)^2 + betadd^2; - resL2(ii) = sqrt(gamma_var); + alphabar = alpha; + zetabar = alpha * beta; + rho = 1; + rhobar = 1; + cbar = 1; + sbar = 0; + h = v; + hbar = 0; - % ||A^T r_k || is just |zetabar| - - - - % (6) Test for convergence. - % msl: I still need to implement this. - % msl: There are suggestions on the original paper. Let's talk about it! + % Compute the residual norm ||r_k|| + betadd = beta; + betad = 0; + rhod = 1; + tautilda = 0; + thetatilda = 0; + zeta = 0; + d = 0; - if measurequality % msl: what is this?? - qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); + % (2) Start iterations + for ii=iter:niter + iter=iter+1; + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + x0 = x; % only store if necesary + end + if (iter==1 && verbose);tic;end + + % (3) Continue the bidiagonalization + u = Ax(v,geo,angles,'Siddon','gpuids',gpuids) - alpha*u; + beta = norm(u(:),2); + u = u / beta; + + v = Atb(u,geo,angles,'matched','gpuids',gpuids) - beta*v; + + alpha = norm(v(:),2); + v = v / alpha; + + % (4) Construct and apply rotation \hat{P}_k + alphahat = sqrt(alphabar^2 + lambda^2); + chat = alphabar/alphahat; + shat = lambda/alphahat; + + % (5) Construct and apply rotation P_k + rhopre = rho; + rho = sqrt(alphahat^2 + beta^2); + c = alphahat / rho; + s = beta / rho; + theta = s * alpha; + alphabar = c * alpha; + + % (6) Construct and apply rotation \bar{P}_k + thetabar = sbar * rho; + rhobarpre = rhobar; + rhobar = sqrt((cbar*rho)^2 + theta^2); + cbar = cbar * rho / rhobar; + sbar = theta / rhobar; + zetapre = zeta; + zeta = cbar * zetabar; + zetabar = -sbar * zetabar; + + % (7) Update \bar{h}, x, h + hbar = h - (thetabar*rho/(rhopre*rhobarpre))*hbar; + x = x + (zeta / (rho*rhobar)) * hbar; + h = v - (theta / rho) * h; + + % (8) Apply rotation \hat{P}_k, P_k + betaacute = chat* betadd; + betacheck = - shat* betadd; + + % Computing ||r_k|| + + betahat = c * betaacute; + betadd = -s * betaacute; + + % Update estimated quantities of interest. + % (9) If k >= 2, construct and apply \tilde{P} to compute ||r_k|| + rhotilda = sqrt(rhod^2 + thetabar^2); + ctilda = rhod / rhotilda; + stilda = thetabar / rhotilda; + thetatildapre = thetatilda; + thetatilda = stilda * rhobar; + rhod = ctilda * rhobar; + % betatilda = ctilda * betad + stilda * betahat; % msl: in the orinal paper, but not used + betad = -stilda * betad + ctilda * betahat; + + % (10) Update \tilde{t}_k by forward substitution + tautilda = (zetapre - thetatildapre*tautilda) / rhotilda; + taud = (zeta - thetatilda*tautilda) / rhod; + + % (11) Compute ||r_k|| + d = d + betacheck^2; + gamma_var = d + (betad - taud)^2 + betadd^2; + aux = sqrt(gamma_var); % this is the residual teh algorithm follows, but we lose ortogonality, so we compute it explicitly + + % ||A^T r_k || is just |zetabar| + + + + % (6) Test for convergence. + % msl: I still need to implement this. + % msl: There are suggestions on the original paper. Let's talk about it! + + if measurequality + qualMeasOut(:,iter)=Measure_Quality(x0,x,QualMeasOpts); + end + % The following should never happen, but the reallity is that if we use + % the residual from the algorithm, it starts diverging from this explicit residual value. + % This is an interesting fact that I believe may be caused either by + % the mismatch of the backprojection w.r.t the real adjoint, or + % numerical issues related to doing several order of magnitude + % difference operations on single precission numbers. + aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); + resL2(iter)=im3Dnorm(aux,'L2'); + if iter>1 && resL2(iter)>resL2(iter-1) + % we lost orthogonality, lets restart the algorithm unless the + % user asked us not to. + + % undo bad step. + x=x-(zeta / (rho*rhobar)) * hbar; + % if the restart didn't work. + if remember==iter || ~restart + disp(['Algorithm stoped in iteration ', num2str(iter),' due to loss of ortogonality.']) + return; + end + remember=iter; + iter=iter-1; + if verbose + disp(['Orthogonality lost, restarting at iteration ', num2str(iter) ]) + end + break + end + + if (iter==1 && verbose) + expected_time=toc*niter; + disp('LSMR'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end end - - if (ii==1 && verbose) - expected_time=toc*niter; - disp('LSMR'); - disp(['Expected duration : ',secs2hms(expected_time)]); - disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); - disp(''); - end end - end - %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids, lambda]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids','lambda'}; +function [verbose,x,QualMeasOpts,gpuids, lambda,gt,restart]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','lambda','groundtruth','restart'}; defaults=ones(length(opts),1); % Check inputs @@ -192,7 +235,7 @@ if ~isempty(ind) defaults(ind)=0; else - error('TIGRE:LSMR:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + error('TIGRE:LSMR:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); end end @@ -200,14 +243,14 @@ opt=opts{ii}; default=defaults(ii); % if one option isnot default, then extranc value from input - if default==0 + if default==0 ind=double.empty(0,1);jj=1; while isempty(ind) ind=find(isequal(opt,lower(argin{jj}))); jj=jj+1; end if isempty(ind) - error('TIGRE:LSMR:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + error('TIGRE:LSMR:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); end val=argin{jj}; end @@ -232,7 +275,7 @@ continue; end if isempty(x) - error('TIGRE:LSMR:InvalidInput','Invalid Init option') + error('TIGRE:LSMR:InvalidInput','Invalid Init option') end % % % % % % % ERROR case 'initimg' @@ -246,7 +289,7 @@ error('TIGRE:LSMR:InvalidInput','Invalid image for initialization'); end end - % ========================================================================= + % ========================================================================= case 'qualmeas' if default QualMeasOpts={}; @@ -257,7 +300,7 @@ error('TIGRE:LSMR:InvalidInput','Invalid quality measurement parameters'); end end - case 'verbose' + case 'verbose' if default verbose=1; else @@ -279,7 +322,19 @@ else lambda = val; end - otherwise + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end + case 'restart' + if default + restart=true; + else + restart=val; + end + otherwise error('TIGRE:LSMR:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in LSMR()']); end end diff --git a/MATLAB/Algorithms/LSQR.m b/MATLAB/Algorithms/LSQR.m index fb047d66..4f5c8d76 100644 --- a/MATLAB/Algorithms/LSQR.m +++ b/MATLAB/Algorithms/LSQR.m @@ -1,16 +1,16 @@ function [x,resL2,qualMeasOut]= LSQR(proj,geo,angles,niter,varargin) -% LSQR solves the CBCT problem using LSQR. +% LSQR solves the CBCT problem using LSQR. % This is mathematically equivalent to CGLS. -% +% % LSQR(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem % using the projection data PROJ taken over ANGLES angles, corresponding % to the geometry descrived in GEO, using NITER iterations. -% +% % LSQR(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The % possible options in OPT are: -% -% +% +% % 'Init' Describes diferent initialization techniques. % * 'none' : Initializes the image to zeros (default) % * 'FDK' : intializes image to FDK reconstrucition @@ -21,15 +21,20 @@ % image. Not recomended unless you really % know what you are doing. % 'InitImg' an image for the 'image' initialization. Avoid. +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. +% 'restart' true or false. By default the algorithm will restart when +% loss of ortogonality is found. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox -% -% Copyright (c) 2015, University of Bath and +% +% Copyright (c) 2015, University of Bath and % CERN-European Organization for Nuclear Research % All rights reserved. % -% License: Open Source under BSD. +% License: Open Source under BSD. % See the full license at % https://github.com/CERN/TIGRE/blob/master/LICENSE % @@ -40,105 +45,132 @@ %% -[verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); +[verbose,x,QualMeasOpts,gpuids,gt,restart]=parse_inputs(proj,geo,angles,varargin); -% msl: no idea of what this is. Should I check? -measurequality=~isempty(QualMeasOpts); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + x0=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end qualMeasOut=zeros(length(QualMeasOpts),niter); +resL2=zeros(1,niter); % Paige and Saunders //doi.org/10.1145/355984.355989 % Enumeration as given in the paper for 'Algorithm LSQR' -% (1) Initialize -u=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); -normr = norm(u(:),2); -u = u/normr; - -beta = normr; -phibar = beta; - -v=Atb(u,geo,angles,'matched','gpuids',gpuids); - - -alpha = norm(v(:),2); % msl: do we want to check if it is 0? -v = v/alpha; -rhobar = alpha; -w = v; - -normAtr = beta*alpha; % Do we want this? ||A^T r_k|| -% msl: do we want to check for convergence? In well posed problems it would -% make sense, not sure now. - -resL2=zeros(1,niter); % msl: is this error the residual norm ? - -% (2) Start iterations -for ii=1:niter - x0 = x; - if (ii==1 && verbose);tic;end +iter=0; +remember=0; +while iter1 && resL2(ii)>resL2(ii-1) % msl: not checked - % OUT! - x=x-alpha*v; - if verbose - disp(['CGLS stoped in iteration N', num2str(ii),' due to divergence.']) - end - return; + alpha = norm(v(:),2); % msl: do we want to check if it is 0? + v = v/alpha; + rhobar = alpha; + w = v; + + % (2) Start iterations + for ii=iter:niter + iter=iter+1; + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + x0 = x; % only store if necesary + end + if (iter==1 && verbose);tic;end + + % (3)(a) + u = Ax(v,geo,angles,'Siddon','gpuids',gpuids) - alpha*u; + beta = norm(u(:),2); + u = u / beta; + + % (3)(b) + v = Atb(u,geo,angles,'matched','gpuids',gpuids) - beta*v; + alpha = norm(v(:),2); + v = v / alpha; + + % (4)(a-g) + rho = sqrt(rhobar^2 + beta^2); + c = rhobar / rho; + s = beta / rho; + theta = s * alpha; + rhobar = - c * alpha; + phi = c * phibar; + phibar = s * phibar; + + % (5) Update x, w + x = x + (phi / rho) * w; + w = v - (theta / rho) * w; + + % % Update estimated quantities of interest. + % % msl: We can also compute cheaply estimates of ||x||, ||A||, cond(A) + % normr = normr*abs(s); % ||r_k|| = ||b - A x_k|| + % % Only exact if we do not have orth. loss + % normAtr = phibar * alpha * abs(c); % msl: Do we want this? ||A^T r_k|| + + % (6) Test for convergence. + % msl: I still need to implement this. + % msl: There are suggestions on the original paper. Let's talk about it! + + if measurequality + qualMeasOut(:,iter)=Measure_Quality(x0,x,QualMeasOpts); + end + + % The following should never happen, but the reallity is that if we use + % the residual from the algorithm, it starts diverging from this explicit residual value. + % This is an interesting fact that I believe may be caused either by + % the mismatch of the backprojection w.r.t the real adjoint, or + % numerical issues related to doing several order of magnitude + % difference operations on single precission numbers. + aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); + resL2(iter)=im3Dnorm(aux,'L2'); + if iter>1 && resL2(iter)>resL2(iter-1) + % we lost orthogonality, lets restart the algorithm unless the + % user asked us not to. + + % undo bad step. + x=x-(phi / rho) * w; + % if the restart didn't work. + if remember==iter || ~restart + disp(['Algorithm stoped in iteration ', num2str(iter),' due to loss of ortogonality.']) + return; + end + remember=iter; + iter=iter-1; + if verbose + disp(['Orthogonality lost, restarting at iteration ', num2str(iter) ]) + end + break + end + + if (iter==1 && verbose) + expected_time=toc*niter; + disp('LSQR'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end end - - if (ii==1 && verbose) - expected_time=toc*niter; - disp('LSQR'); - disp(['Expected duration : ',secs2hms(expected_time)]); - disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); - disp(''); - end + end end - - %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids'}; +function [verbose,x,QualMeasOpts,gpuids,gt,restart]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','groundtruth','restart'}; defaults=ones(length(opts),1); % Check inputs @@ -153,7 +185,7 @@ if ~isempty(ind) defaults(ind)=0; else - error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); end end @@ -161,14 +193,14 @@ opt=opts{ii}; default=defaults(ii); % if one option isnot default, then extranc value from input - if default==0 + if default==0 ind=double.empty(0,1);jj=1; while isempty(ind) ind=find(isequal(opt,lower(argin{jj}))); jj=jj+1; end if isempty(ind) - error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); end val=argin{jj}; end @@ -193,7 +225,7 @@ continue; end if isempty(x) - error('TIGRE:LSQR:InvalidInput','Invalid Init option') + error('TIGRE:LSQR:InvalidInput','Invalid Init option') end % % % % % % % ERROR case 'initimg' @@ -207,7 +239,7 @@ error('TIGRE:LSQR:InvalidInput','Invalid image for initialization'); end end - % ========================================================================= + % ========================================================================= case 'qualmeas' if default QualMeasOpts={}; @@ -218,7 +250,7 @@ error('TIGRE:LSQR:InvalidInput','Invalid quality measurement parameters'); end end - case 'verbose' + case 'verbose' if default verbose=1; else @@ -234,7 +266,19 @@ else gpuids = val; end - otherwise + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end + case 'restart' + if default + restart=true; + else + restart=val; + end + otherwise error('TIGRE:LSQR:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); end end diff --git a/MATLAB/Algorithms/MLEM.m b/MATLAB/Algorithms/MLEM.m index a14ee4a8..6a803c8e 100644 --- a/MATLAB/Algorithms/MLEM.m +++ b/MATLAB/Algorithms/MLEM.m @@ -16,7 +16,9 @@ % parameters. Input should contain a cell array of desired % quality measurement names. Example: {'CC','RMSE','MSSIM'} % These will be computed in each iteration. - +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -33,30 +35,38 @@ % Codes: https://github.com/CERN/TIGRE/ % Coded by: Ander Biguri %-------------------------------------------------------------------------- -[verbose,res,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); - -if measurequality - qualMeasOut=zeros(length(QualMeasOpts),niter); +[verbose,res,QualMeasOpts,gpuids,gt]=parse_inputs(proj,geo,angles,varargin); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt +end +if nargout<2 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; end +qualMeasOut=zeros(length(QualMeasOpts),niter); + res = max(res,0); -W=Atb(ones(size(proj),'single'),geo,angles,'matched','gpuids',gpuids); -W(W<=0.) = inf; +% Projection weight, W +W=computeW(geo,angles,gpuids); -tic for ii=1:niter - res0 = res; - + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = res; % only store if necesary + end + if (ii==1);tic;end + den = Ax(res,geo,angles,'gpuids',gpuids); den(den<=0.)=inf; - auxMLEM=proj./den; - imgupdate = Atb(auxMLEM, geo,angles,'matched','gpuids',gpuids)./W; + imgupdate = Atb(proj./den, geo,angles,'matched','gpuids',gpuids)./W; res = max(res.*imgupdate,0.); if measurequality - qualMeasOut(:,ii)=Measure_Quality(res0,res,QualMeasOpts); + qualMeasOut(:,ii)=Measure_Quality(res_prev,res,QualMeasOpts); end if (ii==1)&&(verbose==1) @@ -71,8 +81,8 @@ end %% Parse inputs -function [verbose,f0,QualMeasOpts,gpuids]=parse_inputs(proj,geo,angles,argin) -opts = {'verbose','init','qualmeas','gpuids'}; +function [verbose,f0,QualMeasOpts,gpuids,gt]=parse_inputs(proj,geo,angles,argin) +opts = {'verbose','init','qualmeas','gpuids','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -150,6 +160,12 @@ else gpuids = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:MLEM:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in MLEM()']); end diff --git a/MATLAB/Algorithms/OS_ASD_POCS.m b/MATLAB/Algorithms/OS_ASD_POCS.m index 6c536a80..bfa205b1 100644 --- a/MATLAB/Algorithms/OS_ASD_POCS.m +++ b/MATLAB/Algorithms/OS_ASD_POCS.m @@ -55,6 +55,9 @@ % 'redundancy_weighting': true or false. Default is true. Applies data % redundancy weighting to projections in the update step % (relevant for offset detector geometry) +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -73,8 +76,18 @@ %-------------------------------------------------------------------------- %% parse inputs -[beta,beta_red,f,ng,verbose,alpha,alpha_red,rmax,epsilon,blocksize,OrderStrategy,nonneg,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); +[beta,beta_red,f,ng,verbose,alpha,alpha_red,rmax,epsilon,blocksize,OrderStrategy,nonneg,QualMeasOpts,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,varargin); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt +end +if nargout<2 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end +qualMeasOut=zeros(length(QualMeasOpts),niter); % first order the projection angles [alphablocks,orig_index]=order_subsets(angles,blocksize,OrderStrategy); @@ -108,8 +121,6 @@ W = W.*W_r; % include redundancy weighting in W end -qualMeasOut=zeros(length(QualMeasOpts),maxiter); - stop_criteria=0; @@ -120,7 +131,10 @@ DSD=geo.DSD; DSO=geo.DSO; while ~stop_criteria %POCS - f0=f; + % If quality is going to be measured, then we need to save previous image + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = f; % only store if necesary + end if (iter==0 && verbose==1);tic;end iter=iter+1; for jj=1:length(alphablocks) @@ -164,7 +178,7 @@ % Save copy of image. fres=f; if measurequality - qualMeasOut(:,iter)=Measure_Quality(f0,f,QualMeasOpts); + qualMeasOut(:,iter)=Measure_Quality(res_prev,f,QualMeasOpts); end % compute L2 error of actual image. Ax-b dd=im3Dnorm(Ax(f,geo,angles,'gpuids',gpuids)-proj,'L2'); @@ -229,9 +243,9 @@ end -function [beta,beta_red,f0,ng,verbose,alpha,alpha_red,rmax,epsilon,block_size,OrderStrategy,nonneg,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,argin) +function [beta,beta_red,f0,ng,verbose,alpha,alpha_red,rmax,epsilon,block_size,OrderStrategy,nonneg,QualMeasOpts,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,argin) -opts= {'lambda','lambda_red','init','initimg','tviter','verbose','alpha','alpha_red','ratio','maxl2err','blocksize','orderstrategy','blocksize','nonneg','qualmeas','gpuids','redundancy_weighting'}; +opts= {'lambda','lambda_red','init','initimg','tviter','verbose','alpha','alpha_red','ratio','maxl2err','blocksize','orderstrategy','blocksize','nonneg','qualmeas','gpuids','redundancy_weighting','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -278,9 +292,9 @@ warning('TIGRE: Verbose mode not available for older versions than MATLAB R2014b'); verbose=false; end - % Lambda - % ========================================================================= - % Its called beta in OS_ASD_POCS + % Lambda + % ========================================================================= + % Its called beta in OS_ASD_POCS case 'lambda' if default beta=1; @@ -290,8 +304,8 @@ end beta=val; end - % Lambda reduction - % ========================================================================= + % Lambda reduction + % ========================================================================= case 'lambda_red' if default beta_red=0.99; @@ -301,22 +315,22 @@ end beta_red=val; end - % Initial image - % ========================================================================= + % Initial image + % ========================================================================= case 'init' if default || strcmp(val,'none') f0=zeros(geo.nVoxel','single'); - + else if strcmp(val,'FDK') f0=FDK(proj, geo, angles); else error('TIGRE:OS_ASD_POCS:InvalidInput','Invalid init') - + end end - % % % % % % % ERROR + % % % % % % % ERROR case 'initimg' if default continue; @@ -329,48 +343,48 @@ end end - % Number of iterations of TV - % ========================================================================= + % Number of iterations of TV + % ========================================================================= case 'tviter' if default ng=20; else ng=val; end - % TV hyperparameter - % ========================================================================= + % TV hyperparameter + % ========================================================================= case 'alpha' if default alpha=0.002; % 0.2 else alpha=val; end - % TV hyperparameter redution - % ========================================================================= + % TV hyperparameter redution + % ========================================================================= case 'alpha_red' if default alpha_red=0.95; else alpha_red=val; end - % Maximum update ratio - % ========================================================================= + % Maximum update ratio + % ========================================================================= case 'ratio' if default rmax=0.95; else rmax=val; end - % Maximum L2 error to have a "good image" - % ========================================================================= + % Maximum L2 error to have a "good image" + % ========================================================================= case 'maxl2err' if default epsilon=im3Dnorm(FDK(proj,geo,angles),'L2')*0.2; %heuristic else epsilon=val; end - % Block size for OS-SART - % ========================================================================= + % Block size for OS-SART + % ========================================================================= case 'blocksize' if default block_size=20; @@ -380,24 +394,24 @@ end block_size=val; end - % Order strategy - % ========================================================================= + % Order strategy + % ========================================================================= case 'orderstrategy' if default OrderStrategy='random'; else OrderStrategy=val; end - % Non negative - % ========================================================================= + % Non negative + % ========================================================================= case 'nonneg' if default nonneg=true; else nonneg=val; end - % Image Quality Measure - % ========================================================================= + % Image Quality Measure + % ========================================================================= case 'qualmeas' if default QualMeasOpts={}; @@ -408,8 +422,8 @@ error('TIGRE:OS_ASD_POCS:InvalidInput','Invalid quality measurement parameters'); end end - % GPU Ids - % ========================================================================= + % GPU Ids + % ========================================================================= case 'gpuids' if default gpuids = GpuIds(); @@ -422,6 +436,12 @@ else redundancy_weights = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:OS_ASD_POCS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option']); diff --git a/MATLAB/Algorithms/OS_AwASD_POCS.m b/MATLAB/Algorithms/OS_AwASD_POCS.m index ef88c4ef..b188777e 100644 --- a/MATLAB/Algorithms/OS_AwASD_POCS.m +++ b/MATLAB/Algorithms/OS_AwASD_POCS.m @@ -59,6 +59,9 @@ % 'redundancy_weighting': true or false. Default is true. Applies data % redundancy weighting to projections in the update step % (relevant for offset detector geometry) +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -76,8 +79,18 @@ % Coded by: Ander Biguri and Manasavee Lohvithee %% parse inputs -[beta,beta_red,f,ng,verbose,alpha,alpha_red,rmax,epsilon,delta,blocksize,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); +[beta,beta_red,f,ng,verbose,alpha,alpha_red,rmax,epsilon,delta,blocksize,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,varargin); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt +end +if nargout<2 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end +qualMeasOut=zeros(length(QualMeasOpts),niter); [alphablocks,orig_index]=order_subsets(angles,blocksize,OrderStrategy); % does detector rotation exists? @@ -85,7 +98,6 @@ geo.rotDetector=[0;0;0]; end -%[proj,geo] = doOffsetWang(proj,geo); %% Create weigthing matrices for the SART step % the reason we do this, instead of calling the SART fucntion is not to @@ -125,7 +137,10 @@ DSO=geo.DSO; while ~stop_criteria %POCS - f0=f; + % If quality is going to be measured, then we need to save previous image + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = f; % only store if necesary + end if (iter==0 && verbose==1);tic;end iter=iter+1; @@ -146,7 +161,9 @@ geo.DSO=DSO(jj); end f=f+beta* bsxfun(@times,1./V(:,:,jj),Atb(W(:,:,orig_index{jj}).*(proj(:,:,orig_index{jj})-Ax(f,geo,alphablocks{:,jj},'gpuids',gpuids)),geo,alphablocks{:,jj},'gpuids',gpuids)); - f(f<0)=0; + if nonneg + f(f<0)=0; + end end geo.offDetector=offDetector; @@ -155,7 +172,7 @@ geo.DSO=DSO; geo.rotDetector=rotDetector; if measurequality - qualMeasOut(:,iter)=Measure_Quality(f0,f,QualMeasOpts); + qualMeasOut(:,iter)=Measure_Quality(res_prev,f,QualMeasOpts); end % compute L2 error of actual image. Ax-b @@ -201,7 +218,7 @@ %Define c_alpha as in equation 21 in the journal c=dot(dg_vec(:),dp_vec(:))/(dg*dp); %This c is examined to see if it is close to -1.0 - %disp(['Iteration = ' num2str(iter) ', c = ' num2str(c)]); + %disp(['Iteration = ' num2str(iter) ', c = ' num2str(c)]); if (c<-0.99 && dd<=epsilon) || beta<0.005|| iter>maxiter if verbose disp(['Stopping criteria met']); @@ -224,8 +241,8 @@ end -function [beta,beta_red,f0,ng,verbose,alpha,alpha_red,rmax,epsilon,delta,block_size,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,argin) -opts= {'lambda','lambda_red','init','tviter','verbose','alpha','alpha_red','ratio','maxl2err','delta','blocksize','orderstrategy','qualmeas','nonneg','gpuids','redundancy_weighting'}; +function [beta,beta_red,f0,ng,verbose,alpha,alpha_red,rmax,epsilon,delta,block_size,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,argin) +opts= {'lambda','lambda_red','init','tviter','verbose','alpha','alpha_red','ratio','maxl2err','delta','blocksize','orderstrategy','qualmeas','nonneg','gpuids','redundancy_weighting','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -272,8 +289,8 @@ warning('TIGRE:Verbose mode not available for older versions than MATLAB R2014b'); verbose=false; end - % Lambda - % ========================================================================= + % Lambda + % ========================================================================= case 'lambda' if default beta=1; @@ -283,8 +300,8 @@ end beta=val; end - % Lambda reduction - % ========================================================================= + % Lambda reduction + % ========================================================================= case 'lambda_red' if default beta_red=0.99; @@ -294,8 +311,8 @@ end beta_red=val; end - % Initial image - % ========================================================================= + % Initial image + % ========================================================================= case 'init' if default || strcmp(val,'none') f0=zeros(geo.nVoxel','single'); @@ -306,57 +323,57 @@ error('TIGRE:OS_AwASD_POCS:InvalidInput','Invalid init') end end - % Number of iterations of TV - % ========================================================================= + % Number of iterations of TV + % ========================================================================= case 'tviter' if default ng=20; else ng=val; end - % TV hyperparameter - % ========================================================================= + % TV hyperparameter + % ========================================================================= case 'alpha' if default alpha=0.002; % 0.2 else alpha=val; end - % TV hyperparameter redution - % ========================================================================= + % TV hyperparameter redution + % ========================================================================= case 'alpha_red' if default alpha_red=0.95; else alpha_red=val; end - % Maximum update ratio - % ========================================================================= + % Maximum update ratio + % ========================================================================= case 'ratio' if default rmax=0.95; else rmax=val; end - % Maximum L2 error to have a "good image" - % ========================================================================= + % Maximum L2 error to have a "good image" + % ========================================================================= case 'maxl2err' if default epsilon=im3Dnorm(FDK(proj,geo,angles),'L2')*0.2; %heuristic else epsilon=val; end - %Parameter to control the amount of smoothing for pixels at the - %edges - % ========================================================================= + %Parameter to control the amount of smoothing for pixels at the + %edges + % ========================================================================= case 'delta' if default delta=-0.005; else delta=val; end - % Block size for OS-SART - % ========================================================================= + % Block size for OS-SART + % ========================================================================= case 'blocksize' if default block_size=20; @@ -366,16 +383,16 @@ end block_size=val; end - % Order strategy - % ========================================================================= + % Order strategy + % ========================================================================= case 'orderstrategy' if default OrderStrategy='random'; else OrderStrategy=val; end - % Image Quality Measure - % ========================================================================= + % Image Quality Measure + % ========================================================================= case 'qualmeas' if default QualMeasOpts={}; @@ -404,6 +421,12 @@ else redundancy_weights = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:OS_AwASD_POCS:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in OS_AwASD_POCS()']); diff --git a/MATLAB/Algorithms/OS_AwPCSD.m b/MATLAB/Algorithms/OS_AwPCSD.m index 56190660..3da96e7d 100644 --- a/MATLAB/Algorithms/OS_AwPCSD.m +++ b/MATLAB/Algorithms/OS_AwPCSD.m @@ -1,4 +1,4 @@ -function [ f,errorSART,errorTV,errorL2,qualMeasOut] = OS_AwPCSD(proj,geo,angles,maxiter,varargin) +function [ f,resL2,qualMeasOut] = OS_AwPCSD(proj,geo,angles,maxiter,varargin) %OS_AwPCSD solves the reconstruction problem using adaptive-weighted %projection-controlled steepest descent method % @@ -51,6 +51,9 @@ % 'redundancy_weighting': true or false. Default is true. Applies data % redundancy weighting to projections in the update step % (relevant for offset detector geometry) +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -68,20 +71,25 @@ % Coded by: Ander Biguri and Manasavee Lohvithee %-------------------------------------------------------------------------- %% parse inputs -[beta,beta_red,f,ng,verbose,epsilon,delta,blocksize,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); - -measurequality=~isempty(QualMeasOpts); +[beta,beta_red,f,ng,verbose,epsilon,delta,blocksize,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,varargin); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end +qualMeasOut=zeros(length(QualMeasOpts),niter); +resL2=zeros(1,niter); if nargout>1 computeL2=true; else computeL2=false; end -errorL2=[]; - - -errorSART=[]; -errorTV=[]; [alphablocks,orig_index]=order_subsets(angles,blocksize,OrderStrategy); % @@ -130,7 +138,10 @@ DSD=geo.DSD; DSO=geo.DSO; while ~stop_criteria %POCS - f0=f; + % If quality is going to be measured, then we need to save previous image + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = f; % only store if necesary + end if (iter==0 && verbose==1);tic;end iter=iter+1; @@ -163,24 +174,19 @@ end %Non-negativity projection on all pixels - f=max(f,0); - + if nonneg + f=max(f,0); + end geo.offDetector=offDetector; geo.offOrigin=offOrigin; if measurequality - qualMeasOut(:,iter)=Measure_Quality(f0,f,QualMeasOpts); + qualMeasOut(:,iter)=Measure_Quality(res_prev,f,QualMeasOpts); end % Compute L2 error of actual image. Ax-b dd=im3Dnorm(Ax(f,geo,angles,'gpuids',gpuids)-proj,'L2'); - - %Compute errorSART - errorSARTnow=im3Dnorm(proj-Ax(f,geo,angles,'gpuids',gpuids),'L2'); - errorSART=[errorSART errorSARTnow]; - - % Compute change in the image after last SART iteration dp_vec=(f-f0); @@ -207,11 +213,6 @@ delta_p_first=im3Dnorm((Ax(f0,geo,angles,'interpolated','gpuids',gpuids))-proj,'L2'); end - %Compute errorTV - errorTVnow=im3Dnorm(proj-Ax(f,geo,angles,'gpuids',gpuids),'L2'); - errorTV=[errorTV errorTVnow]; - - % Reduce SART step beta=beta*beta_red; @@ -236,15 +237,15 @@ if computeL2 geo.offOrigin=offOrigin; geo.offDetector=offDetector; - errornow=im3Dnorm(proj-Ax(f,geo,angles,'gpuids',gpuids),'L2'); % Compute error norm2 of b-Ax + resL2(ii)=im3Dnorm(proj-Ax(f,geo,angles,'gpuids',gpuids),'L2'); % Compute error norm2 of b-Ax % If the error is not minimized. - if iter~=1 && errornow>errorL2(end) + if iter~=1 && resL2(ii)>resL2(ii-1) if verbose disp(['Convergence criteria met, exiting on iteration number:', num2str(iter)]); end return; end - errorL2=[errorL2 errornow]; + end @@ -260,8 +261,8 @@ end -function [beta,beta_red,f0,ng,verbose,epsilon,delta,block_size,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,argin) -opts= {'lambda','lambda_red','init','tviter','verbose','maxl2err','delta','blocksize','orderstrategy','qualmeas','nonneg','gpuids','redundancy_weighting'}; +function [beta,beta_red,f0,ng,verbose,epsilon,delta,block_size,OrderStrategy,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,argin) +opts= {'lambda','lambda_red','init','tviter','verbose','maxl2err','delta','blocksize','orderstrategy','qualmeas','nonneg','gpuids','redundancy_weighting','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -308,8 +309,8 @@ warning('TIGRE:Verbose mode not available for older versions than MATLAB R2014b'); verbose=false; end - % Lambda - % ========================================================================= + % Lambda + % ========================================================================= case 'lambda' if default beta=1; @@ -319,8 +320,8 @@ end beta=val; end - % Lambda reduction - % ========================================================================= + % Lambda reduction + % ========================================================================= case 'lambda_red' if default beta_red=0.99; @@ -330,12 +331,12 @@ end beta_red=val; end - % Initial image - % ========================================================================= + % Initial image + % ========================================================================= case 'init' if default || strcmp(val,'none') f0=zeros(geo.nVoxel','single'); - + else if strcmp(val,'FDK') f0=FDK(proj, geo, angles); @@ -343,35 +344,35 @@ error('TIGRE:MLEM:InvalidInput','Invalid init') end end - % Number of iterations of TV - % ========================================================================= + % Number of iterations of TV + % ========================================================================= case 'tviter' if default ng=20; else ng=val; end - % Maximum L2 error to have a "good image" - % ========================================================================= + % Maximum L2 error to have a "good image" + % ========================================================================= case 'maxl2err' if default epsilon=im3Dnorm(FDK(proj,geo,angles),'L2')*0.2; %heuristic else epsilon=val; end - % ========================================================================= - % Parameter to control the amount of smoothing for pixels at the - % edges - % ========================================================================= + % ========================================================================= + % Parameter to control the amount of smoothing for pixels at the + % edges + % ========================================================================= case 'delta' if default delta=-0.005; else delta=val; end - % ========================================================================= - % Block size for OS-SART - % ========================================================================= + % ========================================================================= + % Block size for OS-SART + % ========================================================================= case 'blocksize' if default block_size=20; @@ -381,18 +382,18 @@ end block_size=val; end - % Order strategy - % ========================================================================= + % Order strategy + % ========================================================================= case 'orderstrategy' if default OrderStrategy='random'; else OrderStrategy=val; end - % ========================================================================= - % Image Quality Measure - % ========================================================================= - case 'qualmeas' + % ========================================================================= + % Image Quality Measure + % ========================================================================= + case 'qualmeas' if default QualMeasOpts={}; else @@ -402,16 +403,16 @@ error('TIGRE:OS_AwPCSD:InvalidInput','Invalid quality measurement parameters'); end end - % Non negative - % ========================================================================= + % Non negative + % ========================================================================= case 'nonneg' if default nonneg=true; else nonneg=val; end - % GPU Ids - % ========================================================================= + % GPU Ids + % ========================================================================= case 'gpuids' if default gpuids = GpuIds(); @@ -424,6 +425,12 @@ else redundancy_weights = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:OS_AwPCSD:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in OS_AwPCSD()']); diff --git a/MATLAB/Algorithms/OS_SART.m b/MATLAB/Algorithms/OS_SART.m index 64c4acf9..24e961a7 100644 --- a/MATLAB/Algorithms/OS_SART.m +++ b/MATLAB/Algorithms/OS_SART.m @@ -1,4 +1,4 @@ -function [res,errorL2,qualMeasOut]=OS_SART(proj,geo,angles,niter,varargin) +function [res,resL2,qualMeasOut]=OS_SART(proj,geo,angles,niter,varargin) % OS_SART solves Cone Beam CT image reconstruction using Oriented Subsets % Simultaneous Algebraic Reconstruction Technique algorithm % @@ -43,7 +43,9 @@ % 'redundancy_weighting': true or false. Default is true. Applies data % redundancy weighting to projections in the update step % (relevant for offset detector geometry) -% +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. % OUTPUTS: % % [img] will output the reconstructed image @@ -71,11 +73,23 @@ %% Deal with input parameters -[blocksize,lambda,res,lambdared,verbose,QualMeasOpts,OrderStrategy,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); +[blocksize,lambda,res,lambdared,verbose,QualMeasOpts,OrderStrategy,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,varargin); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end qualMeasOut=zeros(length(QualMeasOpts),niter); +resL2=zeros(1,niter); + + if nargout>1 computeL2=true; else @@ -120,7 +134,6 @@ ynesterov_prev=ynesterov; end %% Iterate -errorL2=[]; offOrigin=geo.offOrigin; offDetector=geo.offDetector; rotDetector=geo.rotDetector; @@ -135,8 +148,8 @@ if (ii==1 && verbose==1);tic;end % If quality is going to be measured, then we need to save previous image % THIS TAKES MEMORY! - if measurequality - res_prev=res; + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = x; % only store if necesary end @@ -182,15 +195,6 @@ end - % If quality is being measured - if measurequality - - % Can save quality measure for every iteration here - % See if some image quality measure should be used for every - % iteration? - qualMeasOut(:,ii)=Measure_Quality(res_prev,res,QualMeasOpts); - end - % reduce hyperparameter if nesterov gamma=(1-lambda); @@ -199,23 +203,27 @@ else lambda=lambda*lambdared; end - + + if measurequality + qualMeasOut(:,ii)=Measure_Quality(res_prev,res,QualMeasOpts); + end + if computeL2 || nesterov % Compute error norm2 of b-Ax geo.offOrigin=offOrigin; geo.offDetector=offDetector; geo.DSD=DSD; geo.rotDetector=rotDetector; - errornow=im3Dnorm(proj-Ax(res,geo,angles,'Siddon','gpuids',gpuids),'L2'); + resL2(ii)=im3Dnorm(proj-Ax(res,geo,angles,'Siddon','gpuids',gpuids),'L2'); % If the error is not minimized - if ii~=1 && errornow>errorL2(end) % This 1.1 is for multigrid, we need to focus to only that case + if ii~=1 && resL2(ii)>resL2(ii-1) if verbose disp(['Convergence criteria met, exiting on iteration number:', num2str(ii)]); end return end % Store Error - errorL2=[errorL2 errornow]; + end % If timing was asked for if ii==1 && verbose==1 @@ -267,8 +275,8 @@ end %% Parse inputs -function [block_size,lambda,res,lambdared,verbose,QualMeasOpts,OrderStrategy,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,alpha,argin) -opts={'blocksize','lambda','init','initimg','verbose','lambda_red','qualmeas','orderstrategy','nonneg','gpuids','redundancy_weighting'}; +function [block_size,lambda,res,lambdared,verbose,QualMeasOpts,OrderStrategy,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,alpha,argin) +opts={'blocksize','lambda','init','initimg','verbose','lambda_red','qualmeas','orderstrategy','nonneg','gpuids','redundancy_weighting','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -314,7 +322,7 @@ warning('TIGRE: Verbose mode not available for older versions than MATLAB R2014b'); verbose=false; end - % % % % % % % hyperparameter, LAMBDA + % % % % % % % hyperparameter, LAMBDA case 'lambda' if default lambda=1; @@ -411,6 +419,12 @@ else redundancy_weights = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:OS_SART:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in OS_SART_CBCT()']); end diff --git a/MATLAB/Algorithms/PCSD.m b/MATLAB/Algorithms/PCSD.m index 8e6d243f..5407b787 100644 --- a/MATLAB/Algorithms/PCSD.m +++ b/MATLAB/Algorithms/PCSD.m @@ -31,6 +31,9 @@ % 'redundancy_weighting': true or false. Default is true. Applies data % redundancy weighting to projections in the update step % (relevant for offset detector geometry) +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -49,13 +52,19 @@ %-------------------------------------------------------------------------- %% parse inputs -[beta,beta_red,f,ng,verbose,epsilon,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); +[beta,beta_red,f,ng,verbose,epsilon,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); - -if measurequality - qualMeasOut=zeros(length(QualMeasOpts),maxiter); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt +end +if nargout<2 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; end +qualMeasOut=zeros(length(QualMeasOpts),niter); % does detector rotation exists? if ~isfield(geo,'rotDetector') @@ -104,7 +113,10 @@ DSO=geo.DSO; %% while ~stop_criteria %POCS - f0=f; + % If quality is going to be measured, then we need to save previous image + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = f; % only store if necesary + end if (iter==0 && verbose==1);tic;end iter=iter+1; @@ -136,7 +148,9 @@ end %Non-negativity projection on all pixels - f=max(f,0); + if nonneg + f=max(f,0); + end geo.offDetector=offDetector; geo.offOrigin=offOrigin; @@ -144,7 +158,7 @@ geo.DSO=DSO; geo.rotDetector=rotDetector; if measurequality - qualMeasOut(:,iter)=Measure_Quality(f0,f,QualMeasOpts); + qualMeasOut(:,iter)=Measure_Quality(res_prev,f,QualMeasOpts); end % Compute L2 error of actual image. Ax-b diff --git a/MATLAB/Algorithms/SART.m b/MATLAB/Algorithms/SART.m index 5d3fc71f..478cb10a 100644 --- a/MATLAB/Algorithms/SART.m +++ b/MATLAB/Algorithms/SART.m @@ -1,4 +1,4 @@ -function [res,errorL2,qualMeasOut]=SART(proj,geo,angles,niter,varargin) +function [res,resL2,qualMeasOut]=SART(proj,geo,angles,niter,varargin) % SART solves Cone Beam CT image reconstruction using Oriented Subsets % Simultaneous Algebraic Reconstruction Technique algorithm % @@ -40,6 +40,9 @@ % 'redundancy_weighting': true or false. Default is true. Applies data % redundancy weighting to projections in the update step % (relevant for offset detector geometry) +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -59,15 +62,26 @@ %% Deal with input parameters blocksize=1; -[lambda,res,lambdared,verbose,QualMeasOpts,OrderStrategy,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); +[lambda,res,lambdared,verbose,QualMeasOpts,OrderStrategy,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end +qualMeasOut=zeros(length(QualMeasOpts),niter); + +resL2=zeros(1,niter); if nargout>1 computeL2=true; else computeL2=false; end -errorL2=[]; [alphablocks,orig_index]=order_subsets(angles,blocksize,OrderStrategy); index_angles=cell2mat(orig_index); @@ -120,8 +134,8 @@ if (ii==1 && verbose==1);tic;end % If quality is going to be measured, then we need to save previous image % THIS TAKES MEMORY! - if measurequality - res_prev=res; + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = res; % only store if necesary end % reorder angles @@ -157,7 +171,7 @@ ynesterov=res+ bsxfun(@times,1./V(:,:,jj),Atb(W(:,:,index_angles(:,jj)).*(proj(:,:,index_angles(:,jj))-Ax(res,geo,angles_reorder(:,jj),'gpuids',gpuids)),geo,angles_reorder(:,jj),'gpuids',gpuids)); res=(1-gamma)*ynesterov+gamma*ynesterov_prev; else - res=res+lambda* bsxfun(@times,1./V(:,:,jj),Atb(W(:,:,index_angles(:,jj)).*(proj(:,:,index_angles(:,jj))-Ax(res,geo,angles_reorder(:,jj),'gpuids',gpuids)),geo,angles_reorder(:,jj),'gpuids',gpuids)); + res=res+lambda* bsxfun(@times,1./V(:,:,jj),Atb(W(:,:,index_angles(:,jj)).*(proj(:,:,index_angles(:,jj))-Ax(res,geo,angles_reorder(:,jj),'gpuids',gpuids)),geo,angles_reorder(:,jj),'gpuids',gpuids)); end if nonneg res=max(res,0); @@ -166,7 +180,6 @@ % If quality is being measured if measurequality - % HERE GOES qualMeasOut(:,ii)=Measure_Quality(res,res_prev,QualMeasOpts); end @@ -182,15 +195,14 @@ geo.offDetector=offDetector; geo.DSD=DSD; geo.rotDetector=rotDetector; - errornow=im3Dnorm(proj-Ax(res,geo,angles,'gpuids',gpuids),'L2'); % Compute error norm2 of b-Ax + resL2(ii)=im3Dnorm(proj-Ax(res,geo,angles,'gpuids',gpuids),'L2'); % Compute error norm2 of b-Ax % If the error is not minimized. - if ii~=1 && errornow>errorL2(end) + if ii~=1 && resL2(ii)>resL2(ii-1) if verbose disp(['Convergence criteria met, exiting on iteration number:', num2str(ii)]); end return end - errorL2=[errorL2 errornow]; end if (ii==1 && verbose==1) @@ -241,8 +253,8 @@ end -function [lambda,res,lambdared,verbose,QualMeasOpts,OrderStrategy,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,alpha,argin) -opts={'lambda','init','initimg','verbose','lambda_red','qualmeas','orderstrategy','nonneg','gpuids','redundancy_weighting'}; +function [lambda,res,lambdared,verbose,QualMeasOpts,OrderStrategy,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,alpha,argin) +opts={'lambda','init','initimg','verbose','lambda_red','qualmeas','orderstrategy','nonneg','gpuids','redundancy_weighting','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -375,6 +387,12 @@ else redundancy_weights = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:SART:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option']); end diff --git a/MATLAB/Algorithms/SART_TV.m b/MATLAB/Algorithms/SART_TV.m index 50ae10bf..6b6b7bc7 100644 --- a/MATLAB/Algorithms/SART_TV.m +++ b/MATLAB/Algorithms/SART_TV.m @@ -1,4 +1,4 @@ -function [res,errorL2,qualMeasOut]=SART_TV(proj,geo,angles,niter,varargin) +function [res,resL2,qualMeasOut]=SART_TV(proj,geo,angles,niter,varargin) % SART_TV solves Cone Beam CT image reconstruction using Oriented Subsets % Simultaneous Algebraic Reconstruction Technique algorithm % @@ -47,6 +47,9 @@ % 'redundancy_weighting': true or false. Default is true. Applies data % redundancy weighting to projections in the update step % (relevant for offset detector geometry) +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -65,16 +68,25 @@ %-------------------------------------------------------------------------- %% Deal with input parameters -[lambda,res,lamdbared,verbose,QualMeasOpts,TViter,TVlambda,OrderStrategy,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); +[lambda,res,lamdbared,verbose,QualMeasOpts,TViter,TVlambda,OrderStrategy,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,varargin); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end qualMeasOut=zeros(length(QualMeasOpts),niter); +resL2=zeros(1,niter); if nargout>1 computeL2=true; else computeL2=false; end -errorL2=[]; blocksize=1; [alphablocks,orig_index]=order_subsets(angles,blocksize,OrderStrategy); @@ -116,8 +128,8 @@ if (ii==1 && verbose==1);tic;end % If quality is going to be measured, then we need to save previous image % THIS TAKES MEMORY! - if measurequality - res_prev=res; + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = res; % only store if necesary end @@ -150,7 +162,6 @@ % If quality is being measured if measurequality - % HERE GOES qualMeasOut(:,ii)=Measure_Quality(res,res_prev,QualMeasOpts); end @@ -164,15 +175,14 @@ geo.offDetector=offDetector; geo.DSD=DSD; geo.rotDetector=rotDetector; - errornow=im3Dnorm(proj(:,:,index_angles)-Ax(res,geo,angles,'gpuids',gpuids),'L2'); % Compute error norm2 of b-Ax + resL2(ii)=im3Dnorm(proj(:,:,index_angles)-Ax(res,geo,angles,'gpuids',gpuids),'L2'); % Compute error norm2 of b-Ax % If the error is not minimized. - if ii~=1 && errornow>errorL2(end) + if ii~=1 && resL2(ii)>resL2(ii-1) if verbose disp(['Convergence criteria met, exiting on iteration number:', num2str(ii)]); end return end - errorL2=[errorL2 errornow]; end if (ii==1 && verbose==1) @@ -223,8 +233,8 @@ end -function [lambda,res,lamdbared,verbose,QualMeasOpts,TViter,TVlambda,OrderStrategy,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,alpha,argin) -opts={'lambda','init','initimg','verbose','lambda_red','qualmeas','tviter','tvlambda','orderstrategy','nonneg','gpuids','redundancy_weighting'}; +function [lambda,res,lamdbared,verbose,QualMeasOpts,TViter,TVlambda,OrderStrategy,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,alpha,argin) +opts={'lambda','init','initimg','verbose','lambda_red','qualmeas','tviter','tvlambda','orderstrategy','nonneg','gpuids','redundancy_weighting','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -351,10 +361,10 @@ OrderStrategy=val; end - case 'nonneg' + case 'nonneg' if default nonneg=true; - else + else nonneg=val; end case 'gpuids' @@ -369,6 +379,12 @@ else redundancy_weights = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:SART_TV:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in SART()']); end diff --git a/MATLAB/Algorithms/SIRT.m b/MATLAB/Algorithms/SIRT.m index ad62f448..9edc5e94 100644 --- a/MATLAB/Algorithms/SIRT.m +++ b/MATLAB/Algorithms/SIRT.m @@ -1,4 +1,4 @@ -function [res,errorL2,qualMeasOut]=SIRT(proj,geo,angles,niter,varargin) +function [res,resL2,qualMeasOut]=SIRT(proj,geo,angles,niter,varargin) % SIRT solves Cone Beam CT image reconstruction using Oriented Subsets % Simultaneous Algebraic Reconstruction Technique algorithm % @@ -35,6 +35,9 @@ % 'redundancy_weighting': true or false. Default is true. Applies data % redundancy weighting to projections in the update step % (relevant for offset detector geometry) +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -54,10 +57,21 @@ %% Deal with input parameters -[lambda,res,lambdared,verbose,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); -measurequality=~isempty(QualMeasOpts); +[lambda,res,lambdared,verbose,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,varargin); + +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + res_prev=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end qualMeasOut=zeros(length(QualMeasOpts),niter); +resL2=zeros(1,niter); if nargout>1 computeL2=true; else @@ -100,15 +114,13 @@ end %% Iterate -errorL2=[]; - % TODO : Add options for Stopping criteria for ii=1:niter if (ii==1 && verbose==1);tic;end % If quality is going to be measured, then we need to save previous image % THIS TAKES MEMORY! - if measurequality - res_prev=res; + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + res_prev = x; % only store if necesary end % --------- Memory expensive----------- % proj_err=proj-Ax(res,geo,angles); % (b-Ax) @@ -146,15 +158,15 @@ end if computeL2 || nesterov - errornow=im3Dnorm(proj-Ax(res,geo,angles,'gpuids',gpuids),'L2','gpuids',gpuids); % Compute error norm2 of b-Ax + resL2(ii)=im3Dnorm(proj-Ax(res,geo,angles,'gpuids',gpuids),'L2','gpuids',gpuids); % Compute error norm2 of b-Ax % If the error is not minimized. - if ii~=1 && errornow>errorL2(end) + if ii~=1 && resL2(ii)>resL2(ii-1) if verbose disp(['Convergence criteria met, exiting on iteration number:', num2str(ii)]); end return end - errorL2=[errorL2 errornow]; + end @@ -209,8 +221,8 @@ end -function [lambda,res,lambdared,verbose,QualMeasOpts,nonneg,gpuids,redundancy_weights]=parse_inputs(proj,geo,alpha,argin) -opts={'lambda','init','initimg','verbose','lambda_red','qualmeas','nonneg','gpuids','redundancy_weighting'}; +function [lambda,res,lambdared,verbose,QualMeasOpts,nonneg,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,alpha,argin) +opts={'lambda','init','initimg','verbose','lambda_red','qualmeas','nonneg','gpuids','redundancy_weighting','groundtruth'}; defaults=ones(length(opts),1); % Check inputs nVarargs = length(argin); @@ -337,6 +349,12 @@ else redundancy_weights = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:SIRT:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in SIRT()']); end diff --git a/MATLAB/Algorithms/hybrid_LSQR.m b/MATLAB/Algorithms/hybrid_LSQR.m index 6ba49d71..d96caf8d 100644 --- a/MATLAB/Algorithms/hybrid_LSQR.m +++ b/MATLAB/Algorithms/hybrid_LSQR.m @@ -1,15 +1,16 @@ function [x,resL2,lambda_vec, qualMeasOut]= hybrid_LSQR(proj,geo,angles,niter,varargin) -% hybrid_LSQR solves the CBCT problem using LSQR. -% +% hybrid_LSQR solves the CBCT problem using LSQR. +% % hybrid_LSQR(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem % using the projection data PROJ taken over ANGLES angles, corresponding % to the geometry descrived in GEO, using NITER iterations. -% +% % hybrid_LSQR(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The % possible options in OPT are: -% -% +% +% 'lambda' Value of parameter lambda, default autocomputed. +% 'Noiselevel' the expected nosie level, in %, replaces lambda. % 'Init' Describes diferent initialization techniques. % * 'none' : Initializes the image to zeros (default) % * 'FDK' : intializes image to FDK reconstrucition @@ -20,15 +21,18 @@ % image. Not recomended unless you really % know what you are doing. % 'InitImg' an image for the 'image' initialization. Avoid. +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox -% -% Copyright (c) 2015, University of Bath and +% +% Copyright (c) 2015, University of Bath and % CERN-European Organization for Nuclear Research % All rights reserved. % -% License: Open Source under BSD. +% License: Open Source under BSD. % See the full license at % https://github.com/CERN/TIGRE/blob/master/LICENSE % @@ -39,7 +43,7 @@ %% -[verbose,x0,QualMeasOpts,gpuids, lambda, NoiseLevel]=parse_inputs(proj,geo,angles,varargin); +[verbose,x0,QualMeasOpts,gpuids, lambda, NoiseLevel,gt]=parse_inputs(proj,geo,angles,varargin); %%% PARAMETER CHOICE HIERARCHY: given lambda, DP, GCV @@ -48,15 +52,24 @@ RegParam = 'gcv'; % Malena: if this is not good, we can use alternative formulations else - RegParam = 'discrepit'; - % Malena: if this is slow, we can use an adaptive method + RegParam = 'discrepit'; + % Malena: if this is slow, we can use an adaptive method end else RegParam = 'given_lambda'; end -measurequality=~isempty(QualMeasOpts); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + x_prev=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end qualMeasOut=zeros(length(QualMeasOpts),niter); - +resL2 = zeros(1,niter); % Paige and Saunders //doi.org/10.1145/355984.355989 % This should be one to avoid loosing orthogonality, but can be switched @@ -71,7 +84,7 @@ lambda_vec = zeros(niter,1); % Enumeration as given in the paper for 'Algorithm LSQR' -% (1) Initialize +% (1) Initialize u = proj-Ax(x0,geo,angles,'Siddon','gpuids',gpuids); normr = norm(u(:),2); u = u/normr; @@ -79,13 +92,18 @@ beta = normr; proj_rhs(1) = normr; -resL2 = zeros(1,niter); -% (2) Start iterations +% (2) Start iterations for ii=1:niter - + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + if ii==1 + x_prev=x0; + else + x_prev = x0 + reshape(V(:,1:ii)*y,size(x0)); % only store if necesary + end + end if (ii==1 && verbose);tic;end - + % Update V_ii v = Atb(u,geo,angles,'matched','gpuids',gpuids); @@ -100,7 +118,7 @@ alpha = norm(v(:),2); % msl: do we want to check if it is 0? v = v/alpha; V(:,ii) = v(:); - + % Update U_{ii+1} u = Ax(v,geo,angles,'Siddon','gpuids',gpuids) - alpha*u; if reorth % Maybe change to matrix operations! @@ -111,14 +129,14 @@ beta = norm(u(:),2); u = u / beta; U(:,ii+1) = u(:); - + % Update projected matrix B(ii,ii) = alpha; B(ii+1,ii) = beta; - % Malena. Proposed update: we should check algorithms breaks; + % Malena. Proposed update: we should check algorithms breaks; % 'if abs(alpha) <= eps || abs(beta) <= eps' - end and save - - % Solve the projected problem + + % Solve the projected problem % (using the SVD of the small projected matrix) Bk = B(1:ii+1,1:ii); [Uk, Sk, Vk] = svd(Bk); @@ -130,11 +148,11 @@ rhsk = proj_rhs(1:ii+1); rhskhat = Uk'*rhsk; lsqr_res = abs(rhskhat(ii+1))/normr; - - + + if strcmp(RegParam,'discrepit') eta = 1; - if lsqr_res > eta*NoiseLevel + if lsqr_res > eta*NoiseLevel lambda = 0; else lambda = fzero(@(l)discrepancy(l, Bk, rhsk, eta*NoiseLevel), [0, 1e10]); @@ -144,25 +162,34 @@ lambda = fminbnd(@(l)gcv(l, rhskhat, Sk), 0, double(Sk(1))); % Adapt from IRtools lambda_vec(ii) = lambda; % We should output this, maybe? - elseif strcmp(RegParam,'given_lambda') + elseif strcmp(RegParam,'given_lambda') lambda_vec(ii) = lambda; end - + Dk = Sk.^2 + lambda^2; rhskhat = Sk .* rhskhat(1:ii); yhat = rhskhat(1:ii)./Dk; y = Vk * yhat; - - resL2(ii)=norm(rhsk - Bk*y); % residual norm - - + +% resL2(ii)=norm(rhsk - Bk*y); % residual norm + x = x0 + reshape(V(:,1:ii)*y,size(x0)); + if measurequality + qualMeasOut(:,ii)=Measure_Quality(x_prev,x,QualMeasOpts); + end + aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); + resL2(ii)=im3Dnorm(aux,'L2'); + if ii>1 && resL2(ii)>resL2(ii-1) + disp(['Algorithm stoped in iteration ', num2str(ii),' due to loss of ortogonality.']) + return; + end + if (ii==1 && verbose) - expected_time=toc*niter; + expected_time=toc*niter; disp('hybrid LSQR'); disp(['Expected duration : ',secs2hms(expected_time)]); - disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); disp(''); - end + end end x = x0 + reshape(V(:,1:ii)*y,size(x0)); @@ -179,14 +206,14 @@ xl = [A; l*eye(n)]\[b; zeros(n,1)]; out = (norm(A*xl -b)/norm(b))^2 - nnoise^2; end -end +end function out = gcv(lambda, bhat, s) % GCV for the projected problem - no weights % If Bk is the projected matrix and Bk=Uk*Sk*Vk^T % lambda is the regularisation parameter -% bhat is Uk'*bk -% s=diag(Sk) +% bhat is Uk'*bk +% s=diag(Sk) m = length(bhat); n = length(s); @@ -201,12 +228,12 @@ out = (sum(t2) + t0) / ((sum(t1)+m-n)^2); -end +end %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids,lambda,NoiseLevel]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids','lambda','noiselevel'}; +function [verbose,x,QualMeasOpts,gpuids,lambda,NoiseLevel,gt]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','lambda','noiselevel','groundtruth'}; defaults=ones(length(opts),1); % Check inputs @@ -221,7 +248,7 @@ if ~isempty(ind) defaults(ind)=0; else - error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); end end @@ -229,14 +256,14 @@ opt=opts{ii}; default=defaults(ii); % if one option isnot default, then extranc value from input - if default==0 + if default==0 ind=double.empty(0,1);jj=1; while isempty(ind) ind=find(isequal(opt,lower(argin{jj}))); jj=jj+1; end if isempty(ind) - error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + error('TIGRE:LSQR:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); end val=argin{jj}; end @@ -261,7 +288,7 @@ continue; end if isempty(x) - error('TIGRE:LSQR:InvalidInput','Invalid Init option') + error('TIGRE:LSQR:InvalidInput','Invalid Init option') end % % % % % % % ERROR case 'initimg' @@ -288,7 +315,7 @@ NoiseLevel=val; end - % ========================================================================= + % ========================================================================= case 'qualmeas' if default QualMeasOpts={}; @@ -299,7 +326,7 @@ error('TIGRE:LSQR:InvalidInput','Invalid quality measurement parameters'); end end - case 'verbose' + case 'verbose' if default verbose=1; else @@ -315,7 +342,13 @@ else gpuids = val; end - otherwise + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end + otherwise error('TIGRE:LSQR:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); end end diff --git a/MATLAB/Algorithms/hybrid_fLSQR_TV.m b/MATLAB/Algorithms/hybrid_fLSQR_TV.m index ad47d877..d0d8b5f8 100644 --- a/MATLAB/Algorithms/hybrid_fLSQR_TV.m +++ b/MATLAB/Algorithms/hybrid_fLSQR_TV.m @@ -1,4 +1,4 @@ -function [x,errorL2,lambda_vec, qualMeasOut]= hybrid_fLSQR_TV(proj,geo,angles,niter,varargin) +function [x,errorL2, qualMeasOut,lambda_vec]= hybrid_fLSQR_TV(proj,geo,angles,niter,varargin) % LSQR solves the CBCT problem using LSQR. % This is mathematically equivalent to CGLS. @@ -34,13 +34,13 @@ % https://github.com/CERN/TIGRE/blob/master/LICENSE % % Contact: tigre.toolbox@gmail.com -% Codes: max(K) https://github.com/CERN/TIGRE/ +% Codes: https://github.com/CERN/TIGRE/ % Coded by: Malena Sabate Landman, Ander Biguri %-------------------------------------------------------------------------- %% -[verbose,x0,QualMeasOpts,gpuids, lambda, NoiseLevel]=parse_inputs(proj,geo,angles,varargin); +[verbose,x0,QualMeasOpts,gpuids, lambda, NoiseLevel,gt]=parse_inputs(proj,geo,angles,varargin); %%% PARAMETER CHOICE HIERARCHY: given lambda, DP, GCV @@ -56,14 +56,23 @@ RegParam = 'given_lambda'; end -% msl: no idea of what this is. Should I check? -measurequality=~isempty(QualMeasOpts); +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + x_prev=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end qualMeasOut=zeros(length(QualMeasOpts),niter); +resL2 = zeros(1,niter); % Paige and Saunders //doi.org/10.1145/355984.355989 % Initialise matrices -U = single(zeros(prod(size(proj)), niter+1)); +U = single(zeros(numel(proj), niter+1)); V = single(zeros(prod(geo.nVoxel), niter)); % Malena: Check if prod(geo.nVoxel) is correct, I want size of object Z = single(zeros(prod(geo.nVoxel), niter)); % Flexible basis M = zeros(niter+1,niter); % Projected matrix 1 @@ -111,7 +120,9 @@ % (2) Start iterations for ii=1:niter - + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + x_prev = x; % only store if necesary + end if (ii==1 && verbose);tic;end % Update V, Z, and projected matrix T @@ -128,8 +139,6 @@ aux_z = lsqr(@(x,tflag) Ltx(W, x, tflag), z(:), [], 50); z(:) = lsqr(@(x,tflag) Lx(W, x, tflag), aux_z, [], 50); z(:) = mvpE(k_aux, z(:), 'notransp'); -% z(:) = lsqr(@(x,tflag)mvpEt(k_aux, x, tflag), v(:), [], 25); -% z(:) = lsqr(@(x,tflag)mvpE(k_aux, x, tflag), z(:), [], 25); z = single(z); V(:,ii) = v(:); @@ -202,13 +211,18 @@ % msl: I still need to implement this. % msl: There are suggestions on the original paper. Let's talk about it! - if measurequality - qualMeasOut(:,ii)=Measure_Quality(x0,x,QualMeasOpts); + if measurequality + qualMeasOut(:,ii)=Measure_Quality(x_prev,x,QualMeasOpts); end - + aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); + resL2(ii)=im3Dnorm(aux,'L2'); +% if ii>1 && resL2(ii)>resL2(ii-1) +% disp(['Algorithm stoped in iteration ', num2str(ii),' due to loss of ortogonality.']) +% return; +% end if (ii==1 && verbose) expected_time=toc*niter; - disp('LSQR'); + disp('hybrid fLSQR TV'); disp(['Expected duration : ',secs2hms(expected_time)]); disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); disp(''); @@ -374,8 +388,8 @@ %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids, lambda, NoiseLevel]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids','lambda','noiselevel'}; +function [verbose,x,QualMeasOpts,gpuids, lambda, NoiseLevel,gt]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','lambda','noiselevel','groundtruth'}; defaults=ones(length(opts),1); % Check inputs @@ -484,6 +498,12 @@ else gpuids = val; end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end otherwise error('TIGRE:LSQR:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); end diff --git a/MATLAB/Utilities/Quality_measures/Measure_Quality.m b/MATLAB/Utilities/Quality_measures/Measure_Quality.m index 53044d13..33ce3233 100644 --- a/MATLAB/Utilities/Quality_measures/Measure_Quality.m +++ b/MATLAB/Utilities/Quality_measures/Measure_Quality.m @@ -21,21 +21,19 @@ opt=QualMeasOpts{ii}; switch opt - %%%%%%%%RMSE case 'RMSE' q=RMSE(res_prev,res); - %%%%%%CC case 'CC' q=CC(res_prev,res); - %%%%%%MSSIM case 'MSSIM' q=MSSIM(res_prev,res); case 'UQI' q=UQI(res_prev,res); - + case 'error_norm' + q=im3Dnorm(res_prev-res,'L2'); end From 78785453bd5c32fdd5e89cd17a3cce33773937f4 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Mon, 17 Oct 2022 14:45:22 +0100 Subject: [PATCH 24/42] Add Readme for algorithms as a rough guide --- MATLAB/Algorithms/README.md | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 MATLAB/Algorithms/README.md diff --git a/MATLAB/Algorithms/README.md b/MATLAB/Algorithms/README.md new file mode 100644 index 00000000..33ec5f7f --- /dev/null +++ b/MATLAB/Algorithms/README.md @@ -0,0 +1,65 @@ +Algorithm Classification +=== + +There are many algorithms in TIGRE and it can be hard to chose were to start. +So let me introduce a short classification of the algorithms in TIGRE and a small suggestion on where to start. +In any case, always check the demos, as all features and algorithms of TIGRE should be showcased there. + + +There are two main ways we can classify the algorithms. First is by the way they solve the data minimization problem, +i.e. the way they make sure the image reocnstructed matches the measured data. +The second is by their regularization, i.e. by the additional constraints that we add to the problem. A common one, and the most +used one in TIGRE is Total Variation (TV), that tries to make the images "piecewise flat". + + +# Gradient descend + +Tradititonal iterative algorithms use gradient descend-like algorithm (kaczman method, ART), to solve the image. + +Non regularized: + - SIRT + - OS-SART + - SART +Not based on the maths of gradient descend, but similar (solves the proximal of the problem, using gradient descend) + - FISTA + +Regularized with TV (check demo Algorithms04) + - ASD-POCS + - B-ASD-POCS-beta + - OS-ASD-POCS + - AwASDS-POCS + - PCSD + - AwPCSD + - SART-TV (this is practically FISTA-TV) + +**Were do I start?**: For any problem, the first iterative algorithm to try is possibly OS-SART. Good convergence rate with relatively good speed per iteartion. +For TV, start with ASD-POCS, but caution, the hyperparameters have massive influence on the quality of the reconstruction. + +# Maximum likelihood + +This assumes the noise and the data follows Poisson distribution, so mostly useful for very low dose scans. + + - MLEM + +# Krylov Subspace algorithms + +These are fast coverging iterative algorithms. The have some issues with regards of semiconvergence and loss of ortogonality, so in some cases they may not produce best results, +but the main advantage is that few iterations should produce a good image. + +Non regularized: + - CGLS + - LSQR + - LSMR + - BA-GMRES + - AB-GMRES +Tikhonov regularization + - hybrid LSQR + - LSMR with non-zero lambda +TV regularization + - IRN-CGLS-TV + - hybrid-fLSQR-TV + +**Were do I start?**: LSQR. If LSQR doesn't provide stable solutions, BA-GMRES and AB-GMRES are supposed to fix that. For regularized solutions, IRN-CGLS-TV, as the hybrid-fLSQR-TV only works for very small images, +it requires large computational and memory resources. + + \ No newline at end of file From caf08d00643640bf209fd1fc59cb11ac00c13885 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Mon, 17 Oct 2022 16:29:33 +0100 Subject: [PATCH 25/42] Add (broken) versions of GMRES --- MATLAB/Algorithms/AB_GMRES.m | 248 +++++++++++++++++++++++++++++++++++ MATLAB/Algorithms/BA_GMRES.m | 235 +++++++++++++++++++++++++++++++++ MATLAB/Algorithms/README.md | 2 +- 3 files changed, 484 insertions(+), 1 deletion(-) create mode 100644 MATLAB/Algorithms/AB_GMRES.m create mode 100644 MATLAB/Algorithms/BA_GMRES.m diff --git a/MATLAB/Algorithms/AB_GMRES.m b/MATLAB/Algorithms/AB_GMRES.m new file mode 100644 index 00000000..8057cae4 --- /dev/null +++ b/MATLAB/Algorithms/AB_GMRES.m @@ -0,0 +1,248 @@ +function [x,resL2,qualMeasOut]= AB_GMRES(proj,geo,angles,niter,varargin) + +% AB_GMRES solves the CBCT problem using AB_GMRES. +% This is a stable Krylov method for when the backprojector is not adjoint +% +% AB_GMRES(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% using the projection data PROJ taken over ANGLES angles, corresponding +% to the geometry descrived in GEO, using NITER iterations. +% +% AB_GMRES(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% possible options in OPT are: +% +% +% 'Init' Describes diferent initialization techniques. +% * 'none' : Initializes the image to zeros (default) +% * 'FDK' : intializes image to FDK reconstrucition +% * 'multigrid': Initializes image by solving the problem in +% small scale and increasing it when relative +% convergence is reached. +% * 'image' : Initialization using a user specified +% image. Not recomended unless you really +% know what you are doing. +% 'InitImg' an image for the 'image' initialization. Avoid. +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. +% 'restart' true or false. By default the algorithm will restart when +% loss of ortogonality is found. +%-------------------------------------------------------------------------- +%-------------------------------------------------------------------------- +% This file is part of the TIGRE Toolbox +% +% Copyright (c) 2015, University of Bath and +% CERN-European Organization for Nuclear Research +% All rights reserved. +% +% License: Open Source under BSD. +% See the full license at +% https://github.com/CERN/TIGRE/blob/master/LICENSE +% +% Contact: tigre.toolbox@gmail.com +% Codes: https://github.com/CERN/TIGRE/ +% Coded by: Malena Sabate Landman, Ander Biguri +%-------------------------------------------------------------------------- + +%% + +[verbose,x,QualMeasOpts,gpuids,gt,restart]=parse_inputs(proj,geo,angles,varargin); + +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + x0=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end +if nargout==1 + warning("Divergence caused by numerical issues not checked, due to its high computational cost for AB-GMRES") +else + warning("AB-GMRES takes very long to compute residual norms and quality measures and error norms"); +end + +qualMeasOut=zeros(length(QualMeasOpts),niter); +resL2=zeros(1,niter); + + +% Per Cristian Hansen: +% GMRES methods for tomographic reconstruction with an unmatched back projector + +w=zeros(prod(size(proj)),niter,'single'); +r=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); +w(:,1) = r(:)/norm(r(:),2); + +e1=zeros(1,niter); +e1(1)=1; +for k=1:niter + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + x0 = x; % only store if necesary + end + if (k==1 && verbose);tic;end + + qk=Ax(Atb(reshape(w(:,k),geo.nDetector(1),geo.nDetector(2),length(angles)),geo,angles,'matched','gpuids',gpuids),geo,angles,'Siddon','gpuids',gpuids); + h=zeros(k+1,k); + for ii=1:k + h(ii,k)=sum(qk(:).*w(:,ii)); + qk(:)=qk(:)-h(ii,k)*w(:,ii); + end + h(k+1,k)=norm(qk(:),2); + w(:,k)=qk(:)/h(k+1,k); + y=h\(e1*norm(r(:),2)); + if measurequality + qualMeasOut(:,k)=Measure_Quality(x0,compute_res(x,w(:,1:end-1),y(1),geo,angles,gpuids) ,QualMeasOpts); + end + + if nargout>1 + x=compute_res(x,w(:,1:end-1),y(1),geo,angles,gpuids) ; + aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); + resL2(k)=im3Dnorm(aux,'L2'); + if k>1 && resL2(k)>resL2(k-1) + disp(['Algorithm stoped in iteration ', num2str(k),' due to loss of ortogonality.']) + end + + end + + if (k==1 && verbose) + expected_time=toc*niter; + disp('AB-GMRES'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end +end +x=compute_res(x,w,y(1),geo,angles,gpuids); + + + +end + +% x is not explicit in this algorith, and its quite expensive to compute +function x=compute_res(x0,w,y,geo,angles,gpuIds) + +x=x0; +for ii=1:size(w,2) + x=x+Atb(reshape(w(:,ii),geo.nDetector(1),geo.nDetector(2),length(angles)),geo,angles,'matched','gpuIds',gpuIds)*y; +end + +end + +%% parse inputs' +function [verbose,x,QualMeasOpts,gpuids,gt,restart]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','groundtruth','restart'}; +defaults=ones(length(opts),1); + +% Check inputs +nVarargs = length(argin); +if mod(nVarargs,2) + error('TIGRE:AB-GMRES:InvalidInput','Invalid number of inputs') +end + +% check if option has been passed as input +for ii=1:2:nVarargs + ind=find(ismember(opts,lower(argin{ii}))); + if ~isempty(ind) + defaults(ind)=0; + else + error('TIGRE:AB-GMRES:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + end +end + +for ii=1:length(opts) + opt=opts{ii}; + default=defaults(ii); + % if one option isnot default, then extranc value from input + if default==0 + ind=double.empty(0,1);jj=1; + while isempty(ind) + ind=find(isequal(opt,lower(argin{jj}))); + jj=jj+1; + end + if isempty(ind) + error('TIGRE:AB-GMRES:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + end + val=argin{jj}; + end + + switch opt + case 'init' + x=[]; + if default || strcmp(val,'none') + x=zeros(geo.nVoxel','single'); + continue; + end + if strcmp(val,'FDK') + x=FDK(proj,geo,angles); + continue; + end + if strcmp(val,'multigrid') + x=init_multigrid(proj,geo,angles); + continue; + end + if strcmp(val,'image') + initwithimage=1; + continue; + end + if isempty(x) + error('TIGRE:AB-GMRES:InvalidInput','Invalid Init option') + end + % % % % % % % ERROR + case 'initimg' + if default + continue; + end + if exist('initwithimage','var') + if isequal(size(val),geo.nVoxel') + x=single(val); + else + error('TIGRE:AB-GMRES:InvalidInput','Invalid image for initialization'); + end + end + % ========================================================================= + case 'qualmeas' + if default + QualMeasOpts={}; + else + if iscellstr(val) + QualMeasOpts=val; + else + error('TIGRE:AB-GMRES:InvalidInput','Invalid quality measurement parameters'); + end + end + case 'verbose' + if default + verbose=1; + else + verbose=val; + end + if ~is2014bOrNewer + warning('TIGRE:AB-GMRES:Verbose mode not available for older versions than MATLAB R2014b'); + verbose=false; + end + case 'gpuids' + if default + gpuids = GpuIds(); + else + gpuids = val; + end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end + case 'restart' + if default + restart=true; + else + restart=val; + end + otherwise + error('TIGRE:AB-GMRES:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); + end +end + + +end \ No newline at end of file diff --git a/MATLAB/Algorithms/BA_GMRES.m b/MATLAB/Algorithms/BA_GMRES.m new file mode 100644 index 00000000..2250ddfe --- /dev/null +++ b/MATLAB/Algorithms/BA_GMRES.m @@ -0,0 +1,235 @@ +function [x,resL2,qualMeasOut]= BA_GMRES(proj,geo,angles,niter,varargin) + +% BA_GMRES solves the CBCT problem using BA_GMRES. +% This is a stable Krylov method for when the backprojector is not adjoint +% +% BA_GMRES(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem +% using the projection data PROJ taken over ANGLES angles, corresponding +% to the geometry descrived in GEO, using NITER iterations. +% +% BA_GMRES(PROJ,GEO,ANGLES,NITER,OPT,VAL,...) uses options and values for solving. The +% possible options in OPT are: +% +% +% 'Init' Describes diferent initialization techniques. +% * 'none' : Initializes the image to zeros (default) +% * 'FDK' : intializes image to FDK reconstrucition +% * 'multigrid': Initializes image by solving the problem in +% small scale and increasing it when relative +% convergence is reached. +% * 'image' : Initialization using a user specified +% image. Not recomended unless you really +% know what you are doing. +% 'InitImg' an image for the 'image' initialization. Avoid. +% 'groundTruth' an image as grounf truth, to be used if quality measures +% are requested, to plot their change w.r.t. this known +% data. +%-------------------------------------------------------------------------- +%-------------------------------------------------------------------------- +% This file is part of the TIGRE Toolbox +% +% Copyright (c) 2015, University of Bath and +% CERN-European Organization for Nuclear Research +% All rights reserved. +% +% License: Open Source under BSD. +% See the full license at +% https://github.com/CERN/TIGRE/blob/master/LICENSE +% +% Contact: tigre.toolbox@gmail.com +% Codes: https://github.com/CERN/TIGRE/ +% Coded by: Malena Sabate Landman, Ander Biguri +%-------------------------------------------------------------------------- + +%% + +[verbose,x,QualMeasOpts,gpuids,gt]=parse_inputs(proj,geo,angles,varargin); + +measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); +if ~any(isnan(gt(:))) + QualMeasOpts{end+1}='error_norm'; + x0=gt; + clear gt +end +if nargout<3 && measurequality + warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") + measurequality=false; +end + +qualMeasOut=zeros(length(QualMeasOpts),niter); +resL2=zeros(1,niter); + + +% Per Cristian Hansen: +% GMRES methods for tomographic reconstruction with an unmatched back projector + +w=zeros(prod(geo.nVoxel,niter,'single'); +r=Atb(proj,geo,angles,'matched','gpuids',gpuids)-Atb(Ax(x,geo,angles,'Siddon','gpuids',gpuids),geo,angles,'matched','gpuids',gpuids); +w(:,1) = r(:)/norm(r(:),2); + +e1=zeros(1,niter); +e1(1)=1; +for k=1:niter + if measurequality && ~strcmp(QualMeasOpts,'error_norm') + x0 = x; % only store if necesary + end + if (k==1 && verbose);tic;end + + qk=Atb(Ax(reshape(w(:,k),geo.nVoxel(1),geo.nVoxel(2),geo.nVoxel(3)),geo,angles,'matched',gpuids),geo,angles,'Siddon','gpuids',gpuids); + h=zeros(k+1,k); + for ii=1:k + h(ii,k)=sum(qk(:).*w(:,ii)); + qk(:)=qk(:)-h(ii,k)*w(:,ii); + end + h(k+1,k)=norm(qk(:),2); + w(:,k)=qk(:)/h(k+1,k); + y=h\(e1*norm(r(:),2)); + if measurequality + qualMeasOut(:,k)=Measure_Quality(x0,compute_res(x,w(:,1:end-1),y(1),geo,angles) ,QualMeasOpts); + end + + if nargout>1 + x=compute_res(x,w(:,1:end-1),y(1),geo,angles) ; + aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); + resL2(k)=im3Dnorm(aux,'L2'); + if k>1 && resL2(k)>resL2(k-1) + disp(['Algorithm stoped in iteration ', num2str(k),' due to loss of ortogonality.']) + end + + end + + if (k==1 && verbose) + expected_time=toc*niter; + disp('BA-GMRES'); + disp(['Expected duration : ',secs2hms(expected_time)]); + disp(['Expected finish time: ',datestr(datetime('now')+seconds(expected_time))]); + disp(''); + end +end +x=compute_res(x,w,y(1),geo,angles); + + + +end + +% x is not explicit in this algorith, and its quite expensive to compute +function x=compute_res(x0,w,y,geo,angles) + +x=x0; +for ii=1:size(w,2) + x=x+reshape(w(:,ii),geo.nVoxel,length(angles))*y; +end + +end + +%% parse inputs' +function [verbose,x,QualMeasOpts,gpuids,gt]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','groundtruth'}; +defaults=ones(length(opts),1); + +% Check inputs +nVarargs = length(argin); +if mod(nVarargs,2) + error('TIGRE:BA-GMRES:InvalidInput','Invalid number of inputs') +end + +% check if option has been passed as input +for ii=1:2:nVarargs + ind=find(ismember(opts,lower(argin{ii}))); + if ~isempty(ind) + defaults(ind)=0; + else + error('TIGRE:BA-GMRES:InvalidInput',['Optional parameter "' argin{ii} '" does not exist' ]); + end +end + +for ii=1:length(opts) + opt=opts{ii}; + default=defaults(ii); + % if one option isnot default, then extranc value from input + if default==0 + ind=double.empty(0,1);jj=1; + while isempty(ind) + ind=find(isequal(opt,lower(argin{jj}))); + jj=jj+1; + end + if isempty(ind) + error('TIGRE:BA-GMRES:InvalidInput',['Optional parameter "' argin{jj} '" does not exist' ]); + end + val=argin{jj}; + end + + switch opt + case 'init' + x=[]; + if default || strcmp(val,'none') + x=zeros(geo.nVoxel','single'); + continue; + end + if strcmp(val,'FDK') + x=FDK(proj,geo,angles); + continue; + end + if strcmp(val,'multigrid') + x=init_multigrid(proj,geo,angles); + continue; + end + if strcmp(val,'image') + initwithimage=1; + continue; + end + if isempty(x) + error('TIGRE:BA-GMRES:InvalidInput','Invalid Init option') + end + % % % % % % % ERROR + case 'initimg' + if default + continue; + end + if exist('initwithimage','var') + if isequal(size(val),geo.nVoxel') + x=single(val); + else + error('TIGRE:BA-GMRES:InvalidInput','Invalid image for initialization'); + end + end + % ========================================================================= + case 'qualmeas' + if default + QualMeasOpts={}; + else + if iscellstr(val) + QualMeasOpts=val; + else + error('TIGRE:BA-GMRES:InvalidInput','Invalid quality measurement parameters'); + end + end + case 'verbose' + if default + verbose=1; + else + verbose=val; + end + if ~is2014bOrNewer + warning('TIGRE:BA-GMRES:Verbose mode not available for older versions than MATLAB R2014b'); + verbose=false; + end + case 'gpuids' + if default + gpuids = GpuIds(); + else + gpuids = val; + end + case 'groundtruth' + if default + gt=nan; + else + gt=val; + end + otherwise + error('TIGRE:BA-GMRES:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); + end +end + + +end \ No newline at end of file diff --git a/MATLAB/Algorithms/README.md b/MATLAB/Algorithms/README.md index 33ec5f7f..c8605beb 100644 --- a/MATLAB/Algorithms/README.md +++ b/MATLAB/Algorithms/README.md @@ -59,7 +59,7 @@ TV regularization - IRN-CGLS-TV - hybrid-fLSQR-TV -**Were do I start?**: LSQR. If LSQR doesn't provide stable solutions, BA-GMRES and AB-GMRES are supposed to fix that. For regularized solutions, IRN-CGLS-TV, as the hybrid-fLSQR-TV only works for very small images, +**Were do I start?**: LSQR. If LSQR doesn't provide stable solutions, BA-GMRES and AB-GMRES are supposed to fix that, but they require many copies (one per iteration) of the iamge, so they are very memory consuming. For regularized solutions, IRN-CGLS-TV, as the hybrid-fLSQR-TV only works for very small images, it requires large computational and memory resources. \ No newline at end of file From d0125c1639da60cf11df0e65e10591eb2d86171d Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Tue, 18 Oct 2022 09:57:25 +0100 Subject: [PATCH 26/42] Update AB_GMRES --- MATLAB/Algorithms/AB_GMRES.m | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/MATLAB/Algorithms/AB_GMRES.m b/MATLAB/Algorithms/AB_GMRES.m index 8057cae4..2f7f2aee 100644 --- a/MATLAB/Algorithms/AB_GMRES.m +++ b/MATLAB/Algorithms/AB_GMRES.m @@ -70,12 +70,10 @@ % Per Cristian Hansen: % GMRES methods for tomographic reconstruction with an unmatched back projector -w=zeros(prod(size(proj)),niter,'single'); +w=zeros(numel(proj),niter+1,'single'); r=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); w(:,1) = r(:)/norm(r(:),2); -e1=zeros(1,niter); -e1(1)=1; for k=1:niter if measurequality && ~strcmp(QualMeasOpts,'error_norm') x0 = x; % only store if necesary @@ -83,24 +81,26 @@ if (k==1 && verbose);tic;end qk=Ax(Atb(reshape(w(:,k),geo.nDetector(1),geo.nDetector(2),length(angles)),geo,angles,'matched','gpuids',gpuids),geo,angles,'Siddon','gpuids',gpuids); - h=zeros(k+1,k); + e1=zeros(k+1,1); + e1(1)=1; + h=zeros(k+1,1); for ii=1:k - h(ii,k)=sum(qk(:).*w(:,ii)); - qk(:)=qk(:)-h(ii,k)*w(:,ii); + h(ii)=sum(qk(:).*w(:,ii)); + qk(:)=qk(:)-h(ii)*w(:,ii); end - h(k+1,k)=norm(qk(:),2); - w(:,k)=qk(:)/h(k+1,k); + h(k+1)=norm(qk(:),2); + w(:,k+1)=qk(:)/h(k+1); y=h\(e1*norm(r(:),2)); if measurequality qualMeasOut(:,k)=Measure_Quality(x0,compute_res(x,w(:,1:end-1),y(1),geo,angles,gpuids) ,QualMeasOpts); end if nargout>1 - x=compute_res(x,w(:,1:end-1),y(1),geo,angles,gpuids) ; - aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); - resL2(k)=im3Dnorm(aux,'L2'); + resL2(k)=im3Dnorm(proj-Ax(compute_res(x,w(:,1:end-1),y(1),geo,angles,gpuids),geo,angles,'Siddon','gpuids',gpuids),'L2'); if k>1 && resL2(k)>resL2(k-1) + x=compute_res(x,w(:,1:end-1),y(1),geo,angles,gpuids); disp(['Algorithm stoped in iteration ', num2str(k),' due to loss of ortogonality.']) + return; end end From 2ac4fe1d8a18ae18f5b0cd4667c8ff44c45834a6 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Tue, 18 Oct 2022 10:16:10 +0100 Subject: [PATCH 27/42] Update AB-GMRES --- MATLAB/Algorithms/AB_GMRES.m | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/MATLAB/Algorithms/AB_GMRES.m b/MATLAB/Algorithms/AB_GMRES.m index 2f7f2aee..94128954 100644 --- a/MATLAB/Algorithms/AB_GMRES.m +++ b/MATLAB/Algorithms/AB_GMRES.m @@ -73,7 +73,7 @@ w=zeros(numel(proj),niter+1,'single'); r=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); w(:,1) = r(:)/norm(r(:),2); - +h=zeros(niter+1,niter); for k=1:niter if measurequality && ~strcmp(QualMeasOpts,'error_norm') x0 = x; % only store if necesary @@ -83,14 +83,13 @@ qk=Ax(Atb(reshape(w(:,k),geo.nDetector(1),geo.nDetector(2),length(angles)),geo,angles,'matched','gpuids',gpuids),geo,angles,'Siddon','gpuids',gpuids); e1=zeros(k+1,1); e1(1)=1; - h=zeros(k+1,1); for ii=1:k - h(ii)=sum(qk(:).*w(:,ii)); - qk(:)=qk(:)-h(ii)*w(:,ii); + h(ii,k)=sum(qk(:).*w(:,ii)); + qk(:)=qk(:)-h(ii,k)*w(:,ii); end - h(k+1)=norm(qk(:),2); - w(:,k+1)=qk(:)/h(k+1); - y=h\(e1*norm(r(:),2)); + h(k+1,k)=norm(qk(:),2); + w(:,k+1)=qk(:)/h(k+1,k); + y=h(1:k+1,1:k)\(e1*norm(r(:),2)); if measurequality qualMeasOut(:,k)=Measure_Quality(x0,compute_res(x,w(:,1:end-1),y(1),geo,angles,gpuids) ,QualMeasOpts); end From f365b52202a4bc025fda38bf556b7ba63561be61 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Tue, 18 Oct 2022 13:11:11 +0100 Subject: [PATCH 28/42] working version of GMRES --- MATLAB/Algorithms/AB_GMRES.m | 13 ++++++------- MATLAB/Algorithms/BA_GMRES.m | 29 ++++++++++++++--------------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/MATLAB/Algorithms/AB_GMRES.m b/MATLAB/Algorithms/AB_GMRES.m index 94128954..a4104657 100644 --- a/MATLAB/Algorithms/AB_GMRES.m +++ b/MATLAB/Algorithms/AB_GMRES.m @@ -91,11 +91,11 @@ w(:,k+1)=qk(:)/h(k+1,k); y=h(1:k+1,1:k)\(e1*norm(r(:),2)); if measurequality - qualMeasOut(:,k)=Measure_Quality(x0,compute_res(x,w(:,1:end-1),y(1),geo,angles,gpuids) ,QualMeasOpts); + qualMeasOut(:,k)=Measure_Quality(x0,compute_res(x,w(:,1:k),y,geo,angles,gpuids) ,QualMeasOpts); end - + if nargout>1 - resL2(k)=im3Dnorm(proj-Ax(compute_res(x,w(:,1:end-1),y(1),geo,angles,gpuids),geo,angles,'Siddon','gpuids',gpuids),'L2'); + resL2(k)=im3Dnorm(proj-Ax(compute_res(x,w(:,1:k),y,geo,angles,gpuids),geo,angles,'Siddon','gpuids',gpuids),'L2'); if k>1 && resL2(k)>resL2(k-1) x=compute_res(x,w(:,1:end-1),y(1),geo,angles,gpuids); disp(['Algorithm stoped in iteration ', num2str(k),' due to loss of ortogonality.']) @@ -112,18 +112,17 @@ disp(''); end end -x=compute_res(x,w,y(1),geo,angles,gpuids); +x=compute_res(x,w(:,1:end-1),y,geo,angles,gpuids); end % x is not explicit in this algorith, and its quite expensive to compute -function x=compute_res(x0,w,y,geo,angles,gpuIds) +function x=compute_res(x,w,y,geo,angles,gpuIds) -x=x0; for ii=1:size(w,2) - x=x+Atb(reshape(w(:,ii),geo.nDetector(1),geo.nDetector(2),length(angles)),geo,angles,'matched','gpuIds',gpuIds)*y; + x=x+Atb(reshape(w(:,ii),geo.nDetector(1),geo.nDetector(2),length(angles)),geo,angles,'matched','gpuIds',gpuIds)*y(ii); end end diff --git a/MATLAB/Algorithms/BA_GMRES.m b/MATLAB/Algorithms/BA_GMRES.m index 2250ddfe..4a91a2fc 100644 --- a/MATLAB/Algorithms/BA_GMRES.m +++ b/MATLAB/Algorithms/BA_GMRES.m @@ -63,37 +63,38 @@ % Per Cristian Hansen: % GMRES methods for tomographic reconstruction with an unmatched back projector -w=zeros(prod(geo.nVoxel,niter,'single'); +w=zeros(prod(geo.nVoxel),niter+1,'single'); r=Atb(proj,geo,angles,'matched','gpuids',gpuids)-Atb(Ax(x,geo,angles,'Siddon','gpuids',gpuids),geo,angles,'matched','gpuids',gpuids); w(:,1) = r(:)/norm(r(:),2); -e1=zeros(1,niter); -e1(1)=1; +h=zeros(niter+1,niter); for k=1:niter if measurequality && ~strcmp(QualMeasOpts,'error_norm') x0 = x; % only store if necesary end if (k==1 && verbose);tic;end - qk=Atb(Ax(reshape(w(:,k),geo.nVoxel(1),geo.nVoxel(2),geo.nVoxel(3)),geo,angles,'matched',gpuids),geo,angles,'Siddon','gpuids',gpuids); - h=zeros(k+1,k); + qk=Atb(Ax(reshape(w(:,k),geo.nVoxel.'),geo,angles,'Siddon','gpuids',gpuids),geo,angles,'matched','gpuids',gpuids); + e1=zeros(k+1,1); + e1(1)=1; for ii=1:k h(ii,k)=sum(qk(:).*w(:,ii)); qk(:)=qk(:)-h(ii,k)*w(:,ii); end h(k+1,k)=norm(qk(:),2); - w(:,k)=qk(:)/h(k+1,k); - y=h\(e1*norm(r(:),2)); + w(:,k+1)=qk(:)/h(k+1,k); + y=h(1:k+1,1:k)\(e1*norm(r(:),2)); if measurequality - qualMeasOut(:,k)=Measure_Quality(x0,compute_res(x,w(:,1:end-1),y(1),geo,angles) ,QualMeasOpts); + qualMeasOut(:,k)=Measure_Quality(x0,compute_res(x,w(:,1:k),y,geo,angles) ,QualMeasOpts); end if nargout>1 - x=compute_res(x,w(:,1:end-1),y(1),geo,angles) ; - aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); + aux=proj-Ax(compute_res(x,w(:,1:k),y,geo,angles),geo,angles,'Siddon','gpuids',gpuids); resL2(k)=im3Dnorm(aux,'L2'); if k>1 && resL2(k)>resL2(k-1) + x=compute_res(x,w(:,1:k),y,geo,angles); disp(['Algorithm stoped in iteration ', num2str(k),' due to loss of ortogonality.']) + return end end @@ -106,18 +107,16 @@ disp(''); end end -x=compute_res(x,w,y(1),geo,angles); +x=compute_res(x,w(:,end-1),y,geo,angles); end -% x is not explicit in this algorith, and its quite expensive to compute -function x=compute_res(x0,w,y,geo,angles) +function x=compute_res(x,w,y,geo,angles) -x=x0; for ii=1:size(w,2) - x=x+reshape(w(:,ii),geo.nVoxel,length(angles))*y; + x=x+reshape(w(:,ii),geo.nVoxel.')*y(ii); end end From b64da19df3bcd3ebc62fda2c2ac7a9e7603da60b Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Tue, 18 Oct 2022 16:38:50 +0100 Subject: [PATCH 29/42] Added FDK preconditioning to GMRES --- MATLAB/Algorithms/AB_GMRES.m | 41 +++++++++++++++++++++++------------- MATLAB/Algorithms/BA_GMRES.m | 39 +++++++++++++++++++++++++--------- 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/MATLAB/Algorithms/AB_GMRES.m b/MATLAB/Algorithms/AB_GMRES.m index a4104657..ee583d73 100644 --- a/MATLAB/Algorithms/AB_GMRES.m +++ b/MATLAB/Algorithms/AB_GMRES.m @@ -24,8 +24,13 @@ % 'groundTruth' an image as grounf truth, to be used if quality measures % are requested, to plot their change w.r.t. this known % data. -% 'restart' true or false. By default the algorithm will restart when -% loss of ortogonality is found. +% 'backprojector' Descrives which backprojector to use +% +% *'FDK': This will use the FDK algorithm as a backprojector +% NOTE: not the backprojector TIGRE calls "FDK", but +% the actual algorithhm +% *'matched': (DEFAULT) uses the pseudo-matched backprojectors +% %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -45,7 +50,13 @@ %% -[verbose,x,QualMeasOpts,gpuids,gt,restart]=parse_inputs(proj,geo,angles,varargin); +[verbose,x,QualMeasOpts,gpuids,gt,bp]=parse_inputs(proj,geo,angles,varargin); + +if strcmpi(bp,'FDK') + backproject=@(proj,geo,angles,gpuids)FDK(proj,geo,angles,'gpuids',gpuids); +else + backproject=@(proj,geo,angles,gpuids)Atb(proj,geo,angles,'matched','gpuids',gpuids); +end measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); if ~any(isnan(gt(:))) @@ -80,7 +91,7 @@ end if (k==1 && verbose);tic;end - qk=Ax(Atb(reshape(w(:,k),geo.nDetector(1),geo.nDetector(2),length(angles)),geo,angles,'matched','gpuids',gpuids),geo,angles,'Siddon','gpuids',gpuids); + qk=Ax(backproject(reshape(w(:,k),geo.nDetector(1),geo.nDetector(2),length(angles)),geo,angles,gpuids),geo,angles,'Siddon','gpuids',gpuids); e1=zeros(k+1,1); e1(1)=1; for ii=1:k @@ -91,13 +102,13 @@ w(:,k+1)=qk(:)/h(k+1,k); y=h(1:k+1,1:k)\(e1*norm(r(:),2)); if measurequality - qualMeasOut(:,k)=Measure_Quality(x0,compute_res(x,w(:,1:k),y,geo,angles,gpuids) ,QualMeasOpts); + qualMeasOut(:,k)=Measure_Quality(x0,compute_res(x,w(:,1:k),y,geo,angles,backproject,gpuids) ,QualMeasOpts); end if nargout>1 - resL2(k)=im3Dnorm(proj-Ax(compute_res(x,w(:,1:k),y,geo,angles,gpuids),geo,angles,'Siddon','gpuids',gpuids),'L2'); + resL2(k)=im3Dnorm(proj-Ax(compute_res(x,w(:,1:k),y,geo,angles,backproject,gpuids),geo,angles,'Siddon','gpuids',gpuids),'L2'); if k>1 && resL2(k)>resL2(k-1) - x=compute_res(x,w(:,1:end-1),y(1),geo,angles,gpuids); + x=compute_res(x,w(:,1:end-1),y(1),geo,angles,backproject,gpuids); disp(['Algorithm stoped in iteration ', num2str(k),' due to loss of ortogonality.']) return; end @@ -112,24 +123,24 @@ disp(''); end end -x=compute_res(x,w(:,1:end-1),y,geo,angles,gpuids); +x=compute_res(x,w(:,1:end-1),y,geo,angles,backproject,gpuids); end % x is not explicit in this algorith, and its quite expensive to compute -function x=compute_res(x,w,y,geo,angles,gpuIds) +function x=compute_res(x,w,y,geo,angles,backproject,gpuIds) for ii=1:size(w,2) - x=x+Atb(reshape(w(:,ii),geo.nDetector(1),geo.nDetector(2),length(angles)),geo,angles,'matched','gpuIds',gpuIds)*y(ii); + x=x+backproject(reshape(w(:,ii),geo.nDetector(1),geo.nDetector(2),length(angles)),geo,angles,gpuIds)*y(ii); end end %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids,gt,restart]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids','groundtruth','restart'}; +function [verbose,x,QualMeasOpts,gpuids,gt,bp]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','groundtruth','backprojector'}; defaults=ones(length(opts),1); % Check inputs @@ -231,11 +242,11 @@ else gt=val; end - case 'restart' + case 'backprojector' if default - restart=true; + bp='matched'; else - restart=val; + bp=val; end otherwise error('TIGRE:AB-GMRES:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); diff --git a/MATLAB/Algorithms/BA_GMRES.m b/MATLAB/Algorithms/BA_GMRES.m index 4a91a2fc..e12db5ab 100644 --- a/MATLAB/Algorithms/BA_GMRES.m +++ b/MATLAB/Algorithms/BA_GMRES.m @@ -24,6 +24,13 @@ % 'groundTruth' an image as grounf truth, to be used if quality measures % are requested, to plot their change w.r.t. this known % data. +% 'backprojector' Descrives which backprojector to use +% +% *'FDK': This will use the FDK algorithm as a backprojector +% NOTE: not the backprojector TIGRE calls "FDK", but +% the actual algorithhm +% *'matched': (DEFAULT) uses the pseudo-matched backprojectors +% %-------------------------------------------------------------------------- %-------------------------------------------------------------------------- % This file is part of the TIGRE Toolbox @@ -43,7 +50,13 @@ %% -[verbose,x,QualMeasOpts,gpuids,gt]=parse_inputs(proj,geo,angles,varargin); +[verbose,x,QualMeasOpts,gpuids,gt,bp]=parse_inputs(proj,geo,angles,varargin); + +if strcmpi(bp,'FDK') + backproject=@(proj,geo,angles,gpuids)FDK(proj,geo,angles,'gpuids',gpuids); +else + backproject=@(proj,geo,angles,gpuids)Atb(proj,geo,angles,'matched','gpuids',gpuids); +end measurequality=~isempty(QualMeasOpts) | ~any(isnan(gt(:))); if ~any(isnan(gt(:))) @@ -64,7 +77,7 @@ % GMRES methods for tomographic reconstruction with an unmatched back projector w=zeros(prod(geo.nVoxel),niter+1,'single'); -r=Atb(proj,geo,angles,'matched','gpuids',gpuids)-Atb(Ax(x,geo,angles,'Siddon','gpuids',gpuids),geo,angles,'matched','gpuids',gpuids); +r=backproject(proj,geo,angles,gpuids)-backproject(Ax(x,geo,angles,'Siddon','gpuids',gpuids),geo,angles,gpuids); w(:,1) = r(:)/norm(r(:),2); h=zeros(niter+1,niter); @@ -74,7 +87,7 @@ end if (k==1 && verbose);tic;end - qk=Atb(Ax(reshape(w(:,k),geo.nVoxel.'),geo,angles,'Siddon','gpuids',gpuids),geo,angles,'matched','gpuids',gpuids); + qk=backproject(Ax(reshape(w(:,k),geo.nVoxel.'),geo,angles,'Siddon','gpuids',gpuids),geo,angles,gpuids); e1=zeros(k+1,1); e1(1)=1; for ii=1:k @@ -85,14 +98,14 @@ w(:,k+1)=qk(:)/h(k+1,k); y=h(1:k+1,1:k)\(e1*norm(r(:),2)); if measurequality - qualMeasOut(:,k)=Measure_Quality(x0,compute_res(x,w(:,1:k),y,geo,angles) ,QualMeasOpts); + qualMeasOut(:,k)=Measure_Quality(x0,compute_res(x,w(:,1:k),y,geo) ,QualMeasOpts); end if nargout>1 - aux=proj-Ax(compute_res(x,w(:,1:k),y,geo,angles),geo,angles,'Siddon','gpuids',gpuids); + aux=proj-Ax(compute_res(x,w(:,1:k),y,geo),geo,angles,'Siddon','gpuids',gpuids); resL2(k)=im3Dnorm(aux,'L2'); if k>1 && resL2(k)>resL2(k-1) - x=compute_res(x,w(:,1:k),y,geo,angles); + x=compute_res(x,w(:,1:k),y,geo); disp(['Algorithm stoped in iteration ', num2str(k),' due to loss of ortogonality.']) return end @@ -107,13 +120,13 @@ disp(''); end end -x=compute_res(x,w(:,end-1),y,geo,angles); +x=compute_res(x,w(:,end-1),y,geo); end -function x=compute_res(x,w,y,geo,angles) +function x=compute_res(x,w,y,geo) for ii=1:size(w,2) x=x+reshape(w(:,ii),geo.nVoxel.')*y(ii); @@ -122,8 +135,8 @@ end %% parse inputs' -function [verbose,x,QualMeasOpts,gpuids,gt]=parse_inputs(proj,geo,angles,argin) -opts= {'init','initimg','verbose','qualmeas','gpuids','groundtruth'}; +function [verbose,x,QualMeasOpts,gpuids,gt,bp]=parse_inputs(proj,geo,angles,argin) +opts= {'init','initimg','verbose','qualmeas','gpuids','groundtruth','backprojector'}; defaults=ones(length(opts),1); % Check inputs @@ -225,6 +238,12 @@ else gt=val; end + case 'backprojector' + if default + bp='matched'; + else + bp=val; + end otherwise error('TIGRE:BA-GMRES:InvalidInput',['Invalid input name:', num2str(opt),'\n No such option in CGLS()']); end From 506371f5b6f00c74ac546bec74820ea6fd44ce3b Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Tue, 18 Oct 2022 19:20:07 +0100 Subject: [PATCH 30/42] Update README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b5bb454e..5fd56725 100644 --- a/README.md +++ b/README.md @@ -51,13 +51,13 @@ TIGRE is a GPU-based CT reconstruction software repository that contains a wide - **Iterative algorithms** - - Gradient-based algorithms (SART, OS-SART, SIRT) with multiple tuning parameters (Nesterov acceleration, initialization, parameter reduction, ...) + - Gradient-based algorithms (SART, OS-SART, SIRT, ASD-POCS, OS-ASD-POCS, B-ASD-POCS-β, PCSD, AwPCSD, Aw-ASD-POCS) with multiple tuning parameters (Nesterov acceleration, initialization, parameter reduction, ...) - - Krylov subspace algorithms (CGLS) + - Krylov subspace algorithms (CGLS, LSQR, hybrid LSQR, LSMR, IRN-TV-CGLS, hybrid-fLSQR-TV, AB/BA-GMRES) - Statistical reconstruction (MLEM) - - Total variation regularization based algorithms: proximal-based (FISTA, SART-TV) and POCS-based (ASD-POCS, OS-ASD-POCS, B-ASD-POCS-β, PCSD, AwPCSD, Aw-ASD-POCS) + - Variational methods (FISTA, SART-TV) - TV denoising for 3D images. From 3b893750d09d13b417a8140601e63bf7eee6789c Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Fri, 21 Oct 2022 11:07:52 +0100 Subject: [PATCH 31/42] Couple of bug fixes --- MATLAB/Algorithms/OS_ASD_POCS.m | 3 ++- MATLAB/Algorithms/hybrid_LSQR.m | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/MATLAB/Algorithms/OS_ASD_POCS.m b/MATLAB/Algorithms/OS_ASD_POCS.m index bfa205b1..1332a933 100644 --- a/MATLAB/Algorithms/OS_ASD_POCS.m +++ b/MATLAB/Algorithms/OS_ASD_POCS.m @@ -87,7 +87,7 @@ warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") measurequality=false; end -qualMeasOut=zeros(length(QualMeasOpts),niter); +qualMeasOut=zeros(length(QualMeasOpts),maxiter); % first order the projection angles [alphablocks,orig_index]=order_subsets(angles,blocksize,OrderStrategy); @@ -135,6 +135,7 @@ if measurequality && ~strcmp(QualMeasOpts,'error_norm') res_prev = f; % only store if necesary end + f0=f; if (iter==0 && verbose==1);tic;end iter=iter+1; for jj=1:length(alphablocks) diff --git a/MATLAB/Algorithms/hybrid_LSQR.m b/MATLAB/Algorithms/hybrid_LSQR.m index d96caf8d..b40a3e10 100644 --- a/MATLAB/Algorithms/hybrid_LSQR.m +++ b/MATLAB/Algorithms/hybrid_LSQR.m @@ -1,4 +1,4 @@ -function [x,resL2,lambda_vec, qualMeasOut]= hybrid_LSQR(proj,geo,angles,niter,varargin) +function [x,resL2, qualMeasOut,lambda_vec]= hybrid_LSQR(proj,geo,angles,niter,varargin) % hybrid_LSQR solves the CBCT problem using LSQR. % From 878d53b6a74e1e825a648d0d199641c67eb6c10f Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Mon, 31 Oct 2022 14:20:22 +0000 Subject: [PATCH 32/42] Minor bug fixes. --- MATLAB/Algorithms/ASD_POCS.m | 2 +- MATLAB/Algorithms/IRN_TV_CGLS.m | 43 +++++++++++++++------------------ MATLAB/Algorithms/OS_SART.m | 2 +- MATLAB/Algorithms/SIRT.m | 2 +- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/MATLAB/Algorithms/ASD_POCS.m b/MATLAB/Algorithms/ASD_POCS.m index d28f699d..0afc1141 100644 --- a/MATLAB/Algorithms/ASD_POCS.m +++ b/MATLAB/Algorithms/ASD_POCS.m @@ -81,7 +81,7 @@ warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") measurequality=false; end -qualMeasOut=zeros(length(QualMeasOpts),niter); +qualMeasOut=zeros(length(QualMeasOpts),maxiter); [alphablocks,orig_index]=order_subsets(angles,blocksize,OrderStrategy); diff --git a/MATLAB/Algorithms/IRN_TV_CGLS.m b/MATLAB/Algorithms/IRN_TV_CGLS.m index 03dcdaec..21d621b6 100644 --- a/MATLAB/Algorithms/IRN_TV_CGLS.m +++ b/MATLAB/Algorithms/IRN_TV_CGLS.m @@ -1,4 +1,4 @@ -function [x_out,resL2,qualMeasOut]= IRN_TV_CGLS(proj,geo,angles,niter,varargin) +function [x_outer,resL2,qualMeasOut]= IRN_TV_CGLS(proj,geo,angles,niter,varargin) % IRN_TV_CGLS solves the IRN_TV_CGLS problem using the conjugate gradient least % squares with Total Variation regularization, using an inner outer scheme % @@ -90,7 +90,6 @@ p = p_aux_1 + p_aux_2; gamma=norm(p(:),2)^2; - while iterresL2(iter-1) - % we lost orthogonality, lets restart the algorithm unless the - % user asked us not to. - iter - niter_break - % undo bad step. - x=x-alpha*p; - % if the restart didn't work. - if remember==iter || ~restart - disp(['Algorithm stoped in iteration ', num2str(iter),' due to loss of ortogonality.']) - return; - end - remember=iter; - iter=iter-1; - if verbose - disp(['Orthogonality lost, restarting at iteration ', num2str(iter) ]) - end - break - end +% if mod(iter,niter_break)~=1 && resL2(iter)>resL2(iter-1) +% % we lost orthogonality, lets restart the algorithm unless the +% % user asked us not to. +% % undo bad step. +% x=x-alpha*p; +% % if the restart didn't work. +% if remember==iter || ~restart +% disp(['Algorithm stoped in iteration ', num2str(iter),' due to loss of ortogonality.']) +% return; +% end +% remember=iter; +% iter=iter-1; +% if verbose +% disp(['Orthogonality lost, restarting at iteration ', num2str(iter) ]) +% end +% break +% end % If step is adecuate, then continue withg CGLS r_aux_1 = r_aux_1-alpha*q_aux_1; @@ -165,6 +161,7 @@ disp(''); end end + x_outer=x; end @@ -302,7 +299,7 @@ end case 'niter_outer' if default - niter_outer=5; + niter_outer=10; else niter_outer=val; end diff --git a/MATLAB/Algorithms/OS_SART.m b/MATLAB/Algorithms/OS_SART.m index 24e961a7..90c203c3 100644 --- a/MATLAB/Algorithms/OS_SART.m +++ b/MATLAB/Algorithms/OS_SART.m @@ -149,7 +149,7 @@ % If quality is going to be measured, then we need to save previous image % THIS TAKES MEMORY! if measurequality && ~strcmp(QualMeasOpts,'error_norm') - res_prev = x; % only store if necesary + res_prev = res; % only store if necesary end diff --git a/MATLAB/Algorithms/SIRT.m b/MATLAB/Algorithms/SIRT.m index 9edc5e94..53232713 100644 --- a/MATLAB/Algorithms/SIRT.m +++ b/MATLAB/Algorithms/SIRT.m @@ -120,7 +120,7 @@ % If quality is going to be measured, then we need to save previous image % THIS TAKES MEMORY! if measurequality && ~strcmp(QualMeasOpts,'error_norm') - res_prev = x; % only store if necesary + res_prev = res; % only store if necesary end % --------- Memory expensive----------- % proj_err=proj-Ax(res,geo,angles); % (b-Ax) From 123268e3f67dc8abcd45d0f9334b081121683a1d Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Mon, 31 Oct 2022 16:05:48 +0000 Subject: [PATCH 33/42] Update gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 390d9a52..c0f350d9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ so.* *.jpg *.bmp *.tiff +*.png +*.mat ## Matlab ignore *.asv From ae528f589cf6813eb43372bd3654ba20853196f8 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Mon, 31 Oct 2022 16:06:23 +0000 Subject: [PATCH 34/42] Update demo8 --- MATLAB/Demos/d08_Algorithms03.m | 173 ++++++++++++++++++++------------ 1 file changed, 110 insertions(+), 63 deletions(-) diff --git a/MATLAB/Demos/d08_Algorithms03.m b/MATLAB/Demos/d08_Algorithms03.m index bb1535e2..c3786599 100644 --- a/MATLAB/Demos/d08_Algorithms03.m +++ b/MATLAB/Demos/d08_Algorithms03.m @@ -30,89 +30,136 @@ close all; %% Define Geometry -geo=defaultGeometry('nVoxel',[256,256,256]','nDetector',[256,256]); +geo=defaultGeometry('nVoxel',[128,128,128]','nDetector',[128,128]); %% Load data and generate projections % see previous demo for explanation angles=linspace(0,2*pi,100); head=headPhantom(geo.nVoxel); projections=Ax(head,geo,angles,'interpolated'); -noise_projections=addCTnoise(projections); +projections=addCTnoise(projections); -%% Usage CGLS -% -% -% CGLS has the common 4 inputs for iterative algorithms in TIGRE: -% -% Projections, geometry, angles, and number of iterations -% -% Additionally it contains optional initialization tehcniques, but we -% reccomend not using them. CGLS is already quite fast and using them may -% lead to divergence. -% The options are: -% 'Init' Describes diferent initialization techniques. -% � 'none' : Initializes the image to zeros (default) -% � 'FDK' : intializes image to FDK reconstrucition -% � 'multigrid': Initializes image by solving the problem in -% small scale and increasing it when relative -% convergence is reached. -% � 'image' : Initialization using a user specified -% image. Not recomended unless you really -% know what you are doing. -% 'InitImg' an image for the 'image' initialization. Avoid. - -% use CGLS -[imgCGLS, residual_CGLS]=CGLS(noise_projections,geo,angles,60); -% use LSQR -[imgLSQR, residual_LSQR]=LSQR(noise_projections,geo,angles,60); -% use hybrid LSQR (note, this algorithm requires tons of memory, -% [niter x size(image)] memory. Do not use for large images. -[imghLSQR, residual_hLSQR]=hybrid_LSQR(noise_projections,geo,angles,60); -% use LSMR -[imgLSMR, residual_LSMR]=LSMR(noise_projections,geo,angles,60); -% use LSMR with a lambda value -[imgLSMR_lambda, residual_LSMR_lambda]=LSMR(noise_projections,geo,angles,60,'lambda',10); -% SIRT for comparison. -[imgSIRT, residual_SIRT]=SIRT(noise_projections,geo,angles,60); +%% Algorithhms +% these algorithms are showcased and referenced in the paper: +% "On Krylov Methods for Large Scale CBCT Reconstruction", M Sabate Landman +% et al. To be published. + +niter=30; + +% We also introduce a new feature in this demo too: the posibility to add the +% ground Truth image for error norms, if you have it. This is only for +% algorithm study, as in real situations you don't have this (otherwise no +% need for recon, right?) but its good to show performance and +% semiconvergence. + +% SIRT, for comparison +[imgsirt,ressirt,errorsirt]=SIRT(projections,geo,angles,niter,'groundTruth',head); +% CGLS, traditional and mostly used Krylov algorithm +[imgcgls,rescgls,errcgls]=CGLS(projections,geo,angles,niter,'groundTruth',head); +% LSQR, stable version of CGLS +[imglsqr,reslsqr,errlsqr]=LSQR(projections,geo,angles,niter,'groundTruth',head); +% LSMR, a different krylov algorithm. It has Tikhonov regularization for +% stability, controled with the parameter 'lambda' +[imglsmr,reslsmr,errlsmr]=LSMR(projections,geo,angles,niter,'groundTruth',head,'lambda',0); +[imglsmr2,reslsmr2,errlsmr2]=LSMR(projections,geo,angles,niter,'groundTruth',head,'lambda',30); +% hybrid LSQR, a reorthogonalizing version of LSQR that can lead to better +% stability. +[imghlsqr,reshlsqr,errhlsqr]=hybrid_LSQR(projections,geo,angles,niter,'groundTruth',head); +% ABBA-GMRES are a set of algorithms that accept unmatched backprojectors, +% which TIGRE (and most CT libraries) have. +% read more at: "GMRES methods for tomographic reconstruction with an unmatched back projector", +% Per Christian Hansen, Ken Hayami, Keiichi Morikuni +[imgabgmres,resabgmres,errabgmres]=AB_GMRES(projections,geo,angles,niter,'groundTruth',head); +[imgbagmres,resbagmres,errbagmres]=BA_GMRES(projections,geo,angles,niter,'groundTruth',head); +% these can have other backprojectors, not just the standard ones. You can +% backproject with FDK for ultra-fast convergence, for example. +[imgabgmresfdk,resabgmresfdk,errabgmresfdk]=AB_GMRES(projections,geo,angles,niter,'groundTruth',head,'backprojector','FDK'); +[imgbagmresfdk,resbagmresfdk,errbagmresfdk]=BA_GMRES(projections,geo,angles,niter,'groundTruth',head,'backprojector','FDK'); %% plot results % -% We can see that CGLS gets to the same L2 error in less amount of -% iterations. +% Notice semiconvergence (this also happens with the other algorithms in +% other demos). i.e. residual goes down, but error goes up. +% Notice how LSMR with regularization doesn't suffer from this. +% Notice fast covnergence of ABBA with FDK backprojector, but not as good +% as the other algorithms after long iterations +% Notice that they are sometimes stopped early, due to divergence +% (unmatched backprojector+numerical issues) -len=max([length(residual_LSQR), - length(residual_CGLS), - length(residual_SIRT), - length(residual_LSMR), - length(residual_LSMR_lambda), - length(residual_hLSQR)]); +% clean up data: +errorsirt(errorsirt==0)=nan; +errcgls(errcgls==0)=nan; +errlsqr(errlsqr==0)=nan; +errlsmr(errlsmr==0)=nan; +errlsmr2(errlsmr2==0)=nan; +errhlsqr(errhlsqr==0)=nan; +errabgmres(errabgmres==0)=nan; +errbagmres(errbagmres==0)=nan; +errabgmresfdk(errabgmresfdk==0)=nan; +errbagmresfdk(errbagmresfdk==0)=nan; +%% plots N1: +set(0,'defaultTextInterpreter','latex'); +set(0,'DefaultTextFontName','Helvetica') +figure(1) +subplot(121) +semilogy(errorsirt/norm(head(:)),'linewidth',2); +hold on; +semilogy(errcgls/norm(head(:)),'linewidth',2); +semilogy(errlsqr/norm(head(:)),'linewidth',2); +semilogy(errlsmr/norm(head(:)),'linewidth',2); +semilogy(errlsmr2/norm(head(:)),'linewidth',2); +semilogy(errhlsqr/norm(head(:)),'linewidth',2); +semilogy(errabgmres/norm(head(:)),'linewidth',2); +semilogy(errbagmres/norm(head(:)),'linewidth',2); +semilogy(errabgmresfdk/norm(head(:)),'linewidth',2); +semilogy(errbagmresfdk/norm(head(:)),'linewidth',2); +xlim([1,niter]); +ylim([7*10^-2,10^0]) +xlabel("Iteration number") +ylabel("$\|x-x_{gt}\|/\|x_{gt}\|$",'interpreter','latex') +title("Error norm") +legend(["SIRT","CGLS","LSQR","LSMR $\lambda=0$","LSMR $\lambda=30$","hybrid LSQR","AB-GMRES","BA-GMRES","AB-GRMES ($B_{FDK}$)","BA-GRMES ($B_{FDK}$)"],'NumColumns',2,'interpreter','latex') +% set(gca,'fontsize',16) +set(gcf, 'Color', 'w'); +grid on -plot([[residual_SIRT nan(1,len-length(residual_SIRT))]; - [residual_CGLS nan(1,len-length(residual_CGLS))]; - [residual_LSQR nan(1,len-length(residual_LSQR))]; - [residual_hLSQR nan(1,len-length(residual_hLSQR))]; - [residual_LSMR nan(1,len-length(residual_LSMR))]; - [residual_LSMR_lambda nan(1,len-length(residual_LSMR_lambda))]]'); -title('Residual') -legend('SIRT','CGLS','LSQR','hybrid LSQR','LSMR','LSMR lambda') +subplot(122) -% plot images -plotImg([imgLSQR imgCGLS, imgLSMR;imghLSQR imgLSMR_lambda, imgSIRT],'Dim','Z','Step',2) -%plot errors -plotImg(abs([head-imgLSQR head-imgCGLS head-imgLSMR; head-imghLSQR head-imgLSMR_lambda head-imgSIRT]),'Dim','Z','Slice',64,'clims',[0, 0.3]) +semilogy(ressirt/norm(projections(:)),'linewidth',2); +hold on; +semilogy(rescgls/norm(projections(:)),'linewidth',2); +semilogy(reslsqr/norm(projections(:)),'linewidth',2); +semilogy(reslsmr/norm(projections(:)),'linewidth',2); +semilogy(reslsmr2/norm(projections(:)),'linewidth',2); +semilogy(reshlsqr/norm(projections(:)),'linewidth',2); +semilogy(resabgmres/norm(projections(:)),'linewidth',2); +semilogy(resbagmres/norm(projections(:)),'linewidth',2); +semilogy(resabgmresfdk/norm(projections(:)),'linewidth',2); +semilogy(resbagmresfdk/norm(projections(:)),'linewidth',2); +xlim([1,niter]); +ylim([9*10^-3,10^0]) +xlabel("Iteration number") +ylabel("$\|Ax-b\|/\|b\|$",'interpreter','latex') +title("Residual norm") +legend(["SIRT","CGLS","LSQR","LSMR $\lambda=0$","LSMR $\lambda=30$","hybrid LSQR","AB-GMRES","BA-GMRES","AB-GRMES ($B_{FDK}$)","BA-GRMES ($B_{FDK}$)"],'NumColumns',2,'interpreter','latex') +grid on +set(gcf, 'Color', 'w'); +set(gcf, 'Units', 'Inches', 'Position', [1, 1, 15/1.2, 7/1.2], 'PaperUnits', 'Inches', 'PaperSize', [9/1.2 7/1.2]) +% set(gca,'fontsize',16) %% Hybrid methods with different regularisation parameter choices + % you can explicitly defined the parameter in the mathematical terms -[imghLSQR_l10, residual_hLSQR_l10]=hybrid_LSQR(noise_projections,geo,angles,30, 'lambda', 10); +[imghLSQR_l10, residual_hLSQR_l10]=hybrid_LSQR(projections,geo,angles,30, 'lambda', 10); % You can give it a "noise level" (in %) instead, and it will chose the lamba inside -[imghLSQR_nl002, residual_hLSQR_nl002, lambda_vec_nl002]=hybrid_LSQR(noise_projections,geo,angles,30, 'NoiseLevel', 0.02); +[imghLSQR_nl002, residual_hLSQR_nl002,~, lambda_vec_nl002]=hybrid_LSQR(projections,geo,angles,30, 'NoiseLevel', 0.02); % if you don't give it any, it will use Generalized Cross Validation to approximate a good lambda -[imghLSQR_gcv, residual_hLSQR_gcv, lambda_vec_gcv]=hybrid_LSQR(noise_projections,geo,angles,30); +[imghLSQR_gcv, residual_hLSQR_gcv,~, lambda_vec_gcv]=hybrid_LSQR(projections,geo,angles,30); -% plot images -plotImg([imgCGLS imghLSQR_l10 imghLSQR_nl002 imghLSQR_gcv],'Dim','Z','Step',2) +%% plot images +plotImg([imgcgls imghLSQR_l10 imghLSQR_nl002 imghLSQR_gcv],'Dim','Z','Step',2) % Look at the parameters figure @@ -120,4 +167,4 @@ hold on plot(lambda_vec_gcv) title("lambda vs iteratios") -legend({"Noise Level", "Generalized Cross Validation"}) +legend(["Noise Level", "Generalized Cross Validation"]) From 2f525af2af396ec09c790103907fa4a5de2884ef Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Mon, 31 Oct 2022 16:39:23 +0000 Subject: [PATCH 35/42] Demo 4 update --- MATLAB/Algorithms/B_ASD_POCS_beta.m | 4 ++-- MATLAB/Demos/d08_Algorithms03.m | 3 +++ MATLAB/Demos/d09_Algorithms04.m | 35 +++++++++++++++++++---------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/MATLAB/Algorithms/B_ASD_POCS_beta.m b/MATLAB/Algorithms/B_ASD_POCS_beta.m index d6a91a34..5e8794a9 100644 --- a/MATLAB/Algorithms/B_ASD_POCS_beta.m +++ b/MATLAB/Algorithms/B_ASD_POCS_beta.m @@ -86,7 +86,7 @@ %% parse inputs blocksize=1; -[beta,beta_red,f,ng,verbose,alpha,alpha_red,rmax,epsilon,bregman,bregman_red,bregman_iter,OrderStrategy,nonneg,QualMeasOpts,gpuids,redundancy_weights]=parse_inputs(proj,geo,angles,varargin); +[beta,beta_red,f,ng,verbose,alpha,alpha_red,rmax,epsilon,bregman,bregman_red,bregman_iter,OrderStrategy,nonneg,QualMeasOpts,gpuids,redundancy_weights,gt]=parse_inputs(proj,geo,angles,varargin); [alphablocks,orig_index]=order_subsets(angles,blocksize,OrderStrategy); @@ -103,7 +103,7 @@ warning("Image metrics requested but none catched as output. Call the algorithm with 3 outputs to store them") measurequality=false; end -qualMeasOut=zeros(length(QualMeasOpts),niter); +qualMeasOut=zeros(length(QualMeasOpts),maxiter); % does detector rotation exists? diff --git a/MATLAB/Demos/d08_Algorithms03.m b/MATLAB/Demos/d08_Algorithms03.m index c3786599..8997187c 100644 --- a/MATLAB/Demos/d08_Algorithms03.m +++ b/MATLAB/Demos/d08_Algorithms03.m @@ -148,6 +148,7 @@ set(gcf, 'Color', 'w'); set(gcf, 'Units', 'Inches', 'Position', [1, 1, 15/1.2, 7/1.2], 'PaperUnits', 'Inches', 'PaperSize', [9/1.2 7/1.2]) % set(gca,'fontsize',16) +set(0,'defaultTextInterpreter','tex'); %% Hybrid methods with different regularisation parameter choices @@ -168,3 +169,5 @@ plot(lambda_vec_gcv) title("lambda vs iteratios") legend(["Noise Level", "Generalized Cross Validation"]) + +%% diff --git a/MATLAB/Demos/d09_Algorithms04.m b/MATLAB/Demos/d09_Algorithms04.m index 5ff74d90..838c92a8 100644 --- a/MATLAB/Demos/d09_Algorithms04.m +++ b/MATLAB/Demos/d09_Algorithms04.m @@ -195,8 +195,7 @@ % IRN_TV_CGLS %========================================================================== %========================================================================== -% MALENA, CAN YOU ADD A SMALL TEXT HERE? EXPLAINING THE ALGORITHM IN 3 -% LINES OR SO +% CGLS with TV regularization in an inner/outer iteration scheme % % 'lambda' hyperparameter in TV norm. It gives the ratio of % importance of the image vs the minimum total variation. @@ -208,24 +207,36 @@ % iterations than the other algorithms, this is an inherently % faster algorithm, both in convergence and time. -imgIRN_TV_CGLS=IRN_TV_CGLS(noise_projections,geo,angles,20,'lambda',15); +imgIRN_TV_CGLS=IRN_TV_CGLS(noise_projections,geo,angles,20,'lambda',5,'niter_outer',2); +% hybrid_fLSQR_TV +%========================================================================== +%========================================================================== +% flexyble hybrod LSQR. Has TV regularization with reorthogonalization. +% +% 'lambda' hyperparameter in TV norm. It gives the ratio of +% importance of the image vs the minimum total variation. +% default is 15. Lower means less TV denoising. +% +imgflsqr=hybrid_fLSQR_TV(projections,geo,angles,20,'lambda',5); + %% Lets visualize the results % Notice the smoother images due to TV regularization. % -% thorax OS-SART ASD-POCS +% head OS-SART ASD-POCS % -% OSC-TV B-ASD-POCS-beta SART-TV +% OS-ASD-POCS B-ASD-POCS-beta SART-TV +% +% IRN-TV-CGLS hybrid-fLSQR heads -plotImg([ imgOSASDPOCS imgBASDPOCSbeta imgSARTTV; imgIRN_TV_CGLS imgOSSART imgASDPOCS ] ,'Dim','Z','Step',2,'clims',[0 1]) +plotImg([imgIRN_TV_CGLS imgflsqr head; + imgOSASDPOCS imgBASDPOCSbeta imgSARTTV; + head, imgOSSART, imgASDPOCS ] ,'Dim','Z','Step',2,'clims',[0 1]) % error - -plotImg(abs([ head-imgOSASDPOCS head-imgBASDPOCSbeta head-imgSARTTV;head-imgIRN_TV_CGLS head-imgOSSART head-imgASDPOCS ]) ,'Dim','Z','Slice',64,'clims',[0 0.1]) - - - +plotImg(abs([head-imgIRN_TV_CGLS head-imgflsqr head-head; + head-imgOSASDPOCS head-imgBASDPOCSbeta head-imgSARTTV; + head-head, head-imgOSSART, head-imgASDPOCS ]) ,'Dim','Z','Slice',64,'clims',[0 0.1]) %% - % Obligatory XKCD reference: https://xkcd.com/833/ From eac34a35d09c7e47e5bd455813b800499d0e48c1 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Tue, 1 Nov 2022 18:29:42 +0000 Subject: [PATCH 36/42] Bug fixes on algos --- MATLAB/Algorithms/IRN_TV_CGLS.m | 35 +++-- MATLAB/Algorithms/LSQR.m | 2 +- Python/demos/d08_Algorithms03.py | 22 +++- Python/tigre/algorithms/__init__.py | 1 + .../tigre/algorithms/iterative_recon_alg.py | 3 +- .../algorithms/krylov_subspace_algorithms.py | 123 +++++++++++------- 6 files changed, 111 insertions(+), 75 deletions(-) diff --git a/MATLAB/Algorithms/IRN_TV_CGLS.m b/MATLAB/Algorithms/IRN_TV_CGLS.m index 21d621b6..47bf1e63 100644 --- a/MATLAB/Algorithms/IRN_TV_CGLS.m +++ b/MATLAB/Algorithms/IRN_TV_CGLS.m @@ -66,7 +66,6 @@ qualMeasOut=zeros(length(QualMeasOpts),niter); resL2 = zeros(1,niter); -remember=[0]; iter=0; % for iii = 1:niter_outer while iterresL2(iter-1) -% % we lost orthogonality, lets restart the algorithm unless the -% % user asked us not to. -% % undo bad step. -% x=x-alpha*p; -% % if the restart didn't work. -% if remember==iter || ~restart -% disp(['Algorithm stoped in iteration ', num2str(iter),' due to loss of ortogonality.']) -% return; -% end -% remember=iter; -% iter=iter-1; -% if verbose -% disp(['Orthogonality lost, restarting at iteration ', num2str(iter) ]) -% end -% break -% end + if mod(iter,niter_break)~=1 && resL2(iter)>resL2(iter-1) + % we lost orthogonality, lets restart the algorithm unless the + % user asked us not to. + % undo bad step. + x=x-alpha*p; + % if the restart didn't work. + if remember==iter || ~restart + disp(['Algorithm stoped in iteration ', num2str(iter),' due to loss of ortogonality.']) + return; + end + remember=iter; + iter=iter-1; + if verbose + disp(['Orthogonality lost, restarting at iteration ', num2str(iter) ]) + end + break + end % If step is adecuate, then continue withg CGLS r_aux_1 = r_aux_1-alpha*q_aux_1; diff --git a/MATLAB/Algorithms/LSQR.m b/MATLAB/Algorithms/LSQR.m index 4f5c8d76..bb8f5687 100644 --- a/MATLAB/Algorithms/LSQR.m +++ b/MATLAB/Algorithms/LSQR.m @@ -142,7 +142,7 @@ % user asked us not to. % undo bad step. - x=x-(phi / rho) * w; + x=x-(phi / rho) * (v-w)/((theta / rho)); % if the restart didn't work. if remember==iter || ~restart disp(['Algorithm stoped in iteration ', num2str(iter),' due to loss of ortogonality.']) diff --git a/Python/demos/d08_Algorithms03.py b/Python/demos/d08_Algorithms03.py index 9eb37078..09d18de4 100644 --- a/Python/demos/d08_Algorithms03.py +++ b/Python/demos/d08_Algorithms03.py @@ -69,9 +69,16 @@ # 'InitImg' an image for the 'image' initialization. Avoid. # use CGLS -imgCGLS, errL2CGLS = algs.cgls(noise_projections, geo, angles, 60, computel2=True) +imgCGLS, normL2CGLS = algs.cgls(noise_projections, geo, angles, 30, computel2=True) +# use LSQR +imgLSQR, normL2LSQR = algs.lsqr(noise_projections, geo, angles, 30, computel2=True) +# use LSMR +imgLSMR, normL2LSMR = algs.lsmr(noise_projections, geo, angles, 30, computel2=True,lmbda=0) +imgLSMR2, normL2LSMR2 = algs.lsmr(noise_projections, geo, angles, 30, computel2=True,lmbda=30) +# use LSQR +imghLSQR, normhL2LSQR = algs.hybrid_lsqr(noise_projections, geo, angles, 30, computel2=True) # SIRT for comparison. -imgSIRT, errL2SIRT = algs.sirt(noise_projections, geo, angles, 60, computel2=True) +imgSIRT, normL2SIRT = algs.sirt(noise_projections, geo, angles, 60, computel2=True) #%% plot results # @@ -79,13 +86,14 @@ # iterations. -plt.plot(np.vstack((errL2CGLS[0, :], errL2SIRT[0, :])).T) + +plt.plot(np.vstack((normL2CGLS[0, :], normL2SIRT[0, 0:30],normL2LSMR[0, :],normL2LSMR2[0, :],normhL2LSQR[0, :])).T) plt.title("L2 error") plt.xlabel("Iteration") -plt.ylabel("$ log_{10}(|Ax-b|) $") -plt.gca().legend(("CGLS", "SIRT")) +plt.ylabel("$ |Ax-b| $") +plt.gca().legend(("CGLS", "SIRT","LSMR lambda=0", "LSMR lambda=30","hybrid LSQR")) plt.show() # plot images -tigre.plotimg(np.concatenate([imgCGLS, imgSIRT], axis=1), dim="z", step=2) +tigre.plotimg(np.concatenate([np.concatenate([imgCGLS, imgSIRT, imgLSQR],axis=1),np.concatenate([imgLSMR, imgLSMR2, imghLSQR], axis=1)], axis=2), dim="z", step=2,clims=[0, 2]) # plot errors -tigre.plotimg(np.abs(np.concatenate([head - imgCGLS, head - imgSIRT], axis=1)), dim="z", slice=32) +tigre.plotimg(np.concatenate([np.concatenate([head-imgCGLS, head-imgSIRT, head-imgLSQR],axis=1),np.concatenate([head-imgLSMR, head-imgLSMR2, head-imghLSQR], axis=1)], axis=2), dim="z", slice=32) diff --git a/Python/tigre/algorithms/__init__.py b/Python/tigre/algorithms/__init__.py index 2f59475e..acebea8c 100644 --- a/Python/tigre/algorithms/__init__.py +++ b/Python/tigre/algorithms/__init__.py @@ -12,6 +12,7 @@ from .iterative_recon_alg import iterativereconalg from .krylov_subspace_algorithms import cgls from .krylov_subspace_algorithms import lsqr +from .krylov_subspace_algorithms import hybrid_lsqr from .krylov_subspace_algorithms import lsmr from .krylov_subspace_algorithms import irn_tv_cgls from .pocs_algorithms import asd_pocs diff --git a/Python/tigre/algorithms/iterative_recon_alg.py b/Python/tigre/algorithms/iterative_recon_alg.py index a03dc846..9a6a2d85 100644 --- a/Python/tigre/algorithms/iterative_recon_alg.py +++ b/Python/tigre/algorithms/iterative_recon_alg.py @@ -150,7 +150,8 @@ def __init__(self, proj, geo, angles, niter, **kwargs): name="Iterative Reconstruction", sup_kw_warning=False, gpuids=None, - niter_outer=15 + niter_outer=4, + restart=True ) allowed_keywords = [ "V", diff --git a/Python/tigre/algorithms/krylov_subspace_algorithms.py b/Python/tigre/algorithms/krylov_subspace_algorithms.py index 50cd3567..55e554f4 100644 --- a/Python/tigre/algorithms/krylov_subspace_algorithms.py +++ b/Python/tigre/algorithms/krylov_subspace_algorithms.py @@ -33,12 +33,7 @@ def __init__(self, proj, geo, angles, niter, **kwargs): self.re_init_at_iteration = 0 IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) - self.__r__ = self.proj - Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids) - self.__p__ = Atb(self.__r__, self.geo, self.angles, backprojection_type="matched", gpuids=self.gpuids) - p_norm = np.linalg.norm(self.__p__.ravel(), 2) - self.__gamma__ = p_norm * p_norm - - def reinitialise_cgls(self): + def initialize_algo(self): self.__r__ = self.proj - Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids) self.__p__ = Atb(self.__r__, self.geo, self.angles, backprojection_type="matched", gpuids=self.gpuids) p_norm = np.linalg.norm(self.__p__.ravel(), 2) @@ -46,8 +41,11 @@ def reinitialise_cgls(self): # Overide def run_main_iter(self): + self.l2l = np.zeros((1, self.niter), dtype=np.float32) avgtime = [] + + self.initialize_algo() for i in range(self.niter): if self.verbose: self._estimate_time_until_completion(i) @@ -61,27 +59,20 @@ def run_main_iter(self): self.res += alpha * self.__p__ avgtoc = default_timer() avgtime.append(abs(avgtic - avgtoc)) - for item in self.__dict__: - if ( - isinstance(getattr(self, item), np.ndarray) - and np.isnan(getattr(self, item)).any() - ): - raise ValueError("nan found for " + item + " at iteraton " + str(i)) - - aux = self.proj - tigre.Ax( - self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids - ) - self.l2l[0, i] = np.linalg.norm(aux) + + self.l2l[0, i] = np.linalg.norm(self.proj - tigre.Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids)) if i > 0 and self.l2l[0, i] > self.l2l[0, i - 1]: + self.res -= alpha * self.__p__ + if self.verbose: print("re-initilization of CGLS called at iteration:" + str(i)) - if self.re_init_at_iteration + 1 == i: - if self.verbose: - print("Algorithm exited with two consecutive reinitializations.") + if self.re_init_at_iteration + 1 == i or not self.restart: + print("CGLS exited due to divergence.") return self.res - self.res -= alpha * self.__p__ - self.reinitialise_cgls() - self.re_init_at_iteration = i + self.re_init_at_iteration=i + i=i-1 + self.initialize_algo() + break self.__r__ -= alpha * q s = tigre.Atb(self.__r__, self.geo, self.angles, backprojection_type="matched", gpuids=self.gpuids) @@ -118,6 +109,8 @@ def __init__(self, proj, geo, angles, niter, **kwargs): kwargs.update(dict(blocksize=angles.shape[0])) self.re_init_at_iteration = 0 IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) + + def initialize_algo(self): # Paige and Saunders //doi.org/10.1145/355984.355989 # Enumeration as given in the paper for 'Algorithm LSQR' @@ -139,6 +132,7 @@ def __init__(self, proj, geo, angles, niter, **kwargs): def run_main_iter(self): self.l2l = np.zeros((1, self.niter), dtype=np.float32) avgtime = [] + self.initialize_algo() for i in range(self.niter): if self.verbose: self._estimate_time_until_completion(i) @@ -174,7 +168,20 @@ def run_main_iter(self): if self.Quameasopts is not None: self.error_measurement(res_prev, i) - + + self.l2l[0, i] = np.linalg.norm(self.proj - tigre.Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids)) + if i > 0 and self.l2l[0, i] > self.l2l[0, i - 1]: + self.res -= (phi / rho) * (self.__v__-self.__w__)/((theta / rho)) + if self.verbose: + print("re-initilization of LSQR called at iteration:" + str(i)) + if self.re_init_at_iteration + 1 == i or not self.restart: + print("LSQR exited due to divergence.") + return self.res + self.re_init_at_iteration=i + i=i-1 + self.initialize_algo() + break + lsqr = decorator(LSQR, name="lsqr") class hybrid_LSQR(IterativeReconAlg): @@ -191,15 +198,15 @@ def __init__(self, proj, geo, angles, niter, **kwargs): kwargs.update(dict(blocksize=angles.shape[0])) self.re_init_at_iteration = 0 IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) - # Paige and Saunders //doi.org/10.1145/355984.355989 + self.__U__ = np.zeros((self.niter+1,np.prod(self.geo.nDetector)*len(self.angles)),dtype=np.float32) + self.__V__ = np.zeros((self.niter,(np.prod(self.geo.nVoxel))),dtype=np.float32) + self.__B__ = np.zeros((self.niter,self.niter+1),dtype=np.float32) #% Projected matrix + self.__proj_rhs__ = np.zeros((self.niter+1,1),dtype=np.float32) #% Projected right hand side + def initialize_algo(self): + # Paige and Saunders //doi.org/10.1145/355984.355989 # Enumeration as given in the paper for 'Algorithm LSQR' - # % Initialise matrices - self.__U__ = np.zeros((niter+1,np.prod(geo.nDetector)*len(angles)),dtype=np.float32) - self.__V__ = np.zeros(niter,(np.prod(geo.nVoxel)),dtype=np.float32) - self.__B__ = np.zeros((niter,niter+1),dtype=np.float32); #% Projected matrix - self.__proj_rhs__ = np.zeros((niter+1,1),dtype=np.float32); #% Projected right hand side # (1) Initialize self.__u__=self.proj - Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids) @@ -215,11 +222,11 @@ def __init__(self, proj, geo, angles, niter, **kwargs): def run_main_iter(self): self.l2l = np.zeros((1, self.niter), dtype=np.float32) avgtime = [] + self.initialize_algo() for i in range(self.niter): if self.verbose: self._estimate_time_until_completion(i) - if self.Quameasopts is not None: - res_prev = copy.deepcopy(self.res) + avgtic = default_timer() v = Atb(self.__u__,self.geo,self.angles,backprojection_type="matched",gpuids=self.gpuids) @@ -240,44 +247,50 @@ def run_main_iter(self): self.__u__ = tigre.Ax(v, self.geo, self.angles, "Siddon", gpuids=self.gpuids) - alpha*self.__u__ for j in range(i-1): - self.__u__=np.reshape(self.__u__ .ravel()-(self.__U__[j]*self.__u__.ravel())*self.__U_[j],self.__u__.shape) + self.__u__=np.reshape(self.__u__.ravel()-(self.__U__[j]*self.__u__.ravel())*self.__U__[j],self.__u__.shape) self.__beta__ = np.linalg.norm(self.__u__.ravel(), 2) - u = u / self.__beta__ + self.__u__ = self.__u__ / self.__beta__ self.__U__[i+1] = self.__u__.ravel() #% Update projected matrix self.__B__[i,i] = alpha - self.__B__[i,i+i] = self.__beta__ + self.__B__[i,i+1] = self.__beta__ #% Malena. Proposed update: we should check algorithms breaks; #% 'if abs(alpha) <= eps || abs(beta) <= eps' - end and save #% Solve the projected problem #% (using the SVD of the small projected matrix) - Bk = self.__B__[0:i,0:i+1] - Uk, Sk, Vk = np.linalg.svd(Bk) + Bk = self.__B__[0:i+1,0:i+2] + Uk, Sk, Vk = np.linalg.svd(np.transpose(Bk)) + if i==0: - Sk = Sk[0,0] - else: - Sk = np.diag(Sk) + Sk = Sk[0] - rhsk = self.__proj_rhs__[0:i+1] + rhsk = self.__proj_rhs__[0:i+2] + rhskhat = np.matmul(Uk,rhsk) # AB: transpose Uk?? - Dk = Sk**2 + self.lmbda**2 - rhskhat = Sk * rhskhat[0:i] - yhat = rhskhat[0:i]/Dk - y = np.matmul(Vk, yhat) - self.l2l[i]=np.linalg.norm(rhsk - Bk*y)# % residual norm + rhskhat = Sk * rhskhat[0:i+1,0] + yhat = rhskhat[0:i+1]/Dk + y = np.matmul(Vk, yhat) + + self.l2l[0, i] = np.linalg.norm(self.proj - tigre.Ax(self.res + np.reshape(np.matmul(np.transpose(self.__V__[0:i+1]),y),self.res.shape), self.geo, self.angles, "Siddon", gpuids=self.gpuids)) + if i > 0 and self.l2l[0, i] > self.l2l[0, i - 1]: + if self.re_init_at_iteration + 1 == i or not self.restart: + print("hybrid LSQR exited due to divergence.") + return self.res + np.reshape(np.matmul(np.transpose(self.__V__[0:i+1]),y),self.res.shape) + #% Test for convergence. #% msl: I still need to implement this. #% msl: There are suggestions on the original paper. Let's talk about it! - self.res = self.res + np.reshape(np.matmul(self.__V__,y),self.res.shape) + self.res = self.res + np.reshape(np.matmul(np.transpose(self.__V__),y),self.res.shape) +hybrid_lsqr = decorator(hybrid_LSQR, name="hybrid_lsqr") class LSMR(IterativeReconAlg): __doc__ = ( @@ -293,6 +306,8 @@ def __init__(self, proj, geo, angles, niter, **kwargs): kwargs.update(dict(blocksize=angles.shape[0])) self.re_init_at_iteration = 0 IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) + + def initialize_algo(self): #% David Chin-Lung Fong and Michael Saunders //doi.org/10.1137/10079687X #% Enumeration as given in the paper for 'Algorithm LSMR' #% (1) Initialize @@ -326,6 +341,7 @@ def __init__(self, proj, geo, angles, niter, **kwargs): def run_main_iter(self): self.l2l = np.zeros((1, self.niter), dtype=np.float32) avgtime = [] + self.initialize_algo() for i in range(self.niter): if self.verbose: self._estimate_time_until_completion(i) @@ -397,7 +413,6 @@ def run_main_iter(self): #% (11) Compute ||r_k|| self.__d__ = self.__d__ + betacheck**2 gamma_var = self.__d__ + (self.__betad__ - taud)**2 + betadd**2 - self.l2l[0, i] = np.sqrt(gamma_var) avgtoc = default_timer() avgtime.append(abs(avgtic - avgtoc)) @@ -405,6 +420,18 @@ def run_main_iter(self): if self.Quameasopts is not None: self.error_measurement(res_prev, i) + self.l2l[0, i] = np.linalg.norm(self.proj - tigre.Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids)) + if i > 0 and self.l2l[0, i] > self.l2l[0, i - 1]: + self.res -= (self.__zeta__ / (self.__rho__*self.__rhobar__)) * self.__hbar__ + if self.verbose: + print("re-initilization of LSMR called at iteration:" + str(i)) + if self.re_init_at_iteration + 1 == i or not self.restart: + print("LSMR exited due to divergence.") + return self.res + self.re_init_at_iteration=iter + iter=iter-1 + self.initialize_algo() + break lsmr = decorator(LSMR, name="lsmr") class IRN_TV_CGLS(IterativeReconAlg): From 055a26cf3ba33962ae0fd2d13fb4d6952ba3b9a4 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Wed, 2 Nov 2022 14:05:09 +0000 Subject: [PATCH 37/42] Fix hybrid_LSQR --- .../algorithms/krylov_subspace_algorithms.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Python/tigre/algorithms/krylov_subspace_algorithms.py b/Python/tigre/algorithms/krylov_subspace_algorithms.py index 55e554f4..3163e9ad 100644 --- a/Python/tigre/algorithms/krylov_subspace_algorithms.py +++ b/Python/tigre/algorithms/krylov_subspace_algorithms.py @@ -264,32 +264,36 @@ def run_main_iter(self): #% (using the SVD of the small projected matrix) Bk = self.__B__[0:i+1,0:i+2] Uk, Sk, Vk = np.linalg.svd(np.transpose(Bk)) - + if i==0: Sk = Sk[0] rhsk = self.__proj_rhs__[0:i+2] - - rhskhat = np.matmul(Uk,rhsk) # AB: transpose Uk?? + rhskhat = np.matmul(np.transpose(Uk),rhsk) # Dk = Sk**2 + self.lmbda**2 rhskhat = Sk * rhskhat[0:i+1,0] yhat = rhskhat[0:i+1]/Dk - y = np.matmul(Vk, yhat) - + y = np.matmul(np.transpose(Vk), yhat) + + print(Dk) + print(yhat) + print(Vk) + print(y) + self.l2l[0, i] = np.linalg.norm(self.proj - tigre.Ax(self.res + np.reshape(np.matmul(np.transpose(self.__V__[0:i+1]),y),self.res.shape), self.geo, self.angles, "Siddon", gpuids=self.gpuids)) if i > 0 and self.l2l[0, i] > self.l2l[0, i - 1]: if self.re_init_at_iteration + 1 == i or not self.restart: - print("hybrid LSQR exited due to divergence.") + print("hybrid LSQR exited due to divergence at iteration "+str(i)) return self.res + np.reshape(np.matmul(np.transpose(self.__V__[0:i+1]),y),self.res.shape) #% Test for convergence. #% msl: I still need to implement this. #% msl: There are suggestions on the original paper. Let's talk about it! - + print(y) self.res = self.res + np.reshape(np.matmul(np.transpose(self.__V__),y),self.res.shape) - + return self.res hybrid_lsqr = decorator(hybrid_LSQR, name="hybrid_lsqr") class LSMR(IterativeReconAlg): From 8bba4224c489e475f80415ad73a140a4616ab8a8 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Wed, 2 Nov 2022 14:43:30 +0000 Subject: [PATCH 38/42] Remove debug prints --- Python/tigre/algorithms/krylov_subspace_algorithms.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Python/tigre/algorithms/krylov_subspace_algorithms.py b/Python/tigre/algorithms/krylov_subspace_algorithms.py index 3163e9ad..08e83423 100644 --- a/Python/tigre/algorithms/krylov_subspace_algorithms.py +++ b/Python/tigre/algorithms/krylov_subspace_algorithms.py @@ -276,10 +276,7 @@ def run_main_iter(self): yhat = rhskhat[0:i+1]/Dk y = np.matmul(np.transpose(Vk), yhat) - print(Dk) - print(yhat) - print(Vk) - print(y) + self.l2l[0, i] = np.linalg.norm(self.proj - tigre.Ax(self.res + np.reshape(np.matmul(np.transpose(self.__V__[0:i+1]),y),self.res.shape), self.geo, self.angles, "Siddon", gpuids=self.gpuids)) From 01aff2c09c83f124772a084cfffbd516635439eb Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Thu, 3 Nov 2022 15:40:23 +0000 Subject: [PATCH 39/42] Add ABBA GMRES to python --- Python/demos/d08_Algorithms03.py | 19 ++- Python/tigre/algorithms/__init__.py | 2 + .../algorithms/krylov_subspace_algorithms.py | 123 +++++++++++++++++- 3 files changed, 138 insertions(+), 6 deletions(-) diff --git a/Python/demos/d08_Algorithms03.py b/Python/demos/d08_Algorithms03.py index 09d18de4..ceb0026e 100644 --- a/Python/demos/d08_Algorithms03.py +++ b/Python/demos/d08_Algorithms03.py @@ -68,7 +68,7 @@ # know what you are doing. # 'InitImg' an image for the 'image' initialization. Avoid. -# use CGLS +# # use CGLS imgCGLS, normL2CGLS = algs.cgls(noise_projections, geo, angles, 30, computel2=True) # use LSQR imgLSQR, normL2LSQR = algs.lsqr(noise_projections, geo, angles, 30, computel2=True) @@ -77,6 +77,15 @@ imgLSMR2, normL2LSMR2 = algs.lsmr(noise_projections, geo, angles, 30, computel2=True,lmbda=30) # use LSQR imghLSQR, normhL2LSQR = algs.hybrid_lsqr(noise_projections, geo, angles, 30, computel2=True) + +# AB/BA-GMRES +imgabgmres, normhabgmres = algs.ab_gmres(noise_projections, geo, angles, 30, computel2=True) +imgbagmres, normhbagmres = algs.ba_gmres(noise_projections, geo, angles, 30, computel2=True) +# AB/BA-GMRES with FDK backprojector +imgabgmresfdk, normhabgmresfdk = algs.ab_gmres(noise_projections, geo, angles, 30, computel2=True,backprojection="FDK") +imgbagmresfdk, normhbagmresfdk = algs.ba_gmres(noise_projections, geo, angles, 30, computel2=True,backprojection="FDK") + + # SIRT for comparison. imgSIRT, normL2SIRT = algs.sirt(noise_projections, geo, angles, 60, computel2=True) @@ -87,13 +96,13 @@ -plt.plot(np.vstack((normL2CGLS[0, :], normL2SIRT[0, 0:30],normL2LSMR[0, :],normL2LSMR2[0, :],normhL2LSQR[0, :])).T) +plt.plot(np.vstack((normL2CGLS[0, :], normL2SIRT[0, 0:30],normL2LSMR[0, :],normL2LSMR2[0, :],normhL2LSQR[0, :],normhabgmres[0,:],normhbagmres[0,:],normhabgmresfdk[0,:],normhbagmresfdk[0,:])).T) plt.title("L2 error") plt.xlabel("Iteration") plt.ylabel("$ |Ax-b| $") -plt.gca().legend(("CGLS", "SIRT","LSMR lambda=0", "LSMR lambda=30","hybrid LSQR")) +plt.gca().legend(("CGLS", "SIRT","LSMR lambda=0", "LSMR lambda=30","hybrid LSQR","AB-GMRES","BA-GMRES","AB-GMRES FDK","BA-GMRES FDK")) plt.show() # plot images -tigre.plotimg(np.concatenate([np.concatenate([imgCGLS, imgSIRT, imgLSQR],axis=1),np.concatenate([imgLSMR, imgLSMR2, imghLSQR], axis=1)], axis=2), dim="z", step=2,clims=[0, 2]) +tigre.plotimg(np.concatenate([np.concatenate([imgCGLS, imgSIRT, imgLSQR,imgabgmres,imgabgmresfdk],axis=1),np.concatenate([imgLSMR, imgLSMR2, imghLSQR,imgbagmres,imgbagmresfdk], axis=1)], axis=2), dim="z", step=2,clims=[0, 2]) # plot errors -tigre.plotimg(np.concatenate([np.concatenate([head-imgCGLS, head-imgSIRT, head-imgLSQR],axis=1),np.concatenate([head-imgLSMR, head-imgLSMR2, head-imghLSQR], axis=1)], axis=2), dim="z", slice=32) +tigre.plotimg(np.concatenate([np.concatenate([head-imgCGLS, head-imgSIRT, head-imgLSQR, head-imgabgmres, head-imgabgmresfdk],axis=1),np.concatenate([head-imgLSMR, head-imgLSMR2, head-imghLSQR,head-imgbagmres,head-imgbagmresfdk], axis=1)], axis=2), dim="z", slice=32) diff --git a/Python/tigre/algorithms/__init__.py b/Python/tigre/algorithms/__init__.py index acebea8c..40f77c86 100644 --- a/Python/tigre/algorithms/__init__.py +++ b/Python/tigre/algorithms/__init__.py @@ -15,6 +15,8 @@ from .krylov_subspace_algorithms import hybrid_lsqr from .krylov_subspace_algorithms import lsmr from .krylov_subspace_algorithms import irn_tv_cgls +from .krylov_subspace_algorithms import ab_gmres +from .krylov_subspace_algorithms import ba_gmres from .pocs_algorithms import asd_pocs from .pocs_algorithms import os_asd_pocs from .pocs_algorithms import awasd_pocs diff --git a/Python/tigre/algorithms/krylov_subspace_algorithms.py b/Python/tigre/algorithms/krylov_subspace_algorithms.py index 08e83423..ba637e6a 100644 --- a/Python/tigre/algorithms/krylov_subspace_algorithms.py +++ b/Python/tigre/algorithms/krylov_subspace_algorithms.py @@ -8,7 +8,7 @@ from tigre.algorithms.iterative_recon_alg import decorator from tigre.utilities.Atb import Atb from tigre.utilities.Ax import Ax - +import tigre.algorithms as algs if hasattr(time, "perf_counter"): default_timer = time.perf_counter @@ -562,3 +562,124 @@ def run_main_iter(self): self.error_measurement(res_prev, outer) irn_tv_cgls = decorator(IRN_TV_CGLS, name="irn_tv_cgls") + + +class AB_GMRES(IterativeReconAlg): + __doc__ = ( + " AB_GMRES solves the CBCT problem using preconditioned GMRES\n" + " AB_GMRES(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem\n" + " using the projection data PROJ taken over ALPHA angles, corresponding\n" + " to the geometry descrived in GEO, using NITER iterations." + ) + IterativeReconAlg.__doc__ + + def __init__(self, proj, geo, angles, niter, **kwargs): + # Don't precompute V and W. + kwargs.update(dict(W=None, V=None)) + kwargs.update(dict(blocksize=angles.shape[0])) + self.re_init_at_iteration = 0 + IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) + backproject=kwargs.get("backprojector","matched") + if backproject is "matched": + self.backproject=Atb + elif backproject is "FDK": + self.backproject=algs.fdk + + def __compute_res__(self,x,w,y): + y=y.astype(np.float32) + for i in range(w.shape[0]): + x=x+self.backproject(np.reshape(w[i],self.proj.shape),self.geo,self.angles,gpuids=self.gpuids)*y[i] + return x + + def run_main_iter(self): + + self.l2l = np.zeros((1, self.niter), dtype=np.float32) + w = np.zeros((self.niter+1,np.prod(self.geo.nDetector)*len(self.angles)),dtype=np.float32) + r=self.proj - Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids) + w[0] = r.ravel()/np.linalg.norm(r.ravel(), 2) + h=np.zeros((self.niter,self.niter+1),dtype=np.float32) + for k in range(self.niter): + if self.verbose: + self._estimate_time_until_completion(k) + + qk=Ax(self.backproject(np.reshape(w[k],self.proj.shape),self.geo,self.angles,gpuids=self.gpuids),self.geo, self.angles, "Siddon", gpuids=self.gpuids) + e1=np.zeros(k+2) + e1[0]=1 + for i in range(k+1): + h[k,i]=sum(qk.ravel()*w[i]) + qk=qk.ravel()-h[k,i]*w[i] + + h[k,k+1]=np.linalg.norm(qk.ravel(),2) + w[k+1]=qk.ravel()/h[k,k+1] + y=np.linalg.lstsq(np.transpose(h[0:k+1,0:k+2]),e1*np.linalg.norm(r.ravel(),2),rcond=None) + y=y[0] + self.l2l[0, i] = np.linalg.norm((self.proj - tigre.Ax(self.__compute_res__(self.res,w[0:k+1],y),self.geo,self.angles, "Siddon",gpuids=self.gpuids)).ravel(),2) + if i > 0 and self.l2l[0, i] > self.l2l[0, i - 1]: + if self.re_init_at_iteration + 1 == i or not self.restart: + print("AB-GMRES exited due to divergence at iteration "+str(i)) + return self.__compute_res__(self.res,w[0:k+1],y) + + self.res=self.__compute_res__(self.res,w[0:-1],y) + return self.res +ab_gmres = decorator(AB_GMRES, name="ab_gmres") + + + +class BA_GMRES(IterativeReconAlg): + __doc__ = ( + " BA_GMRES solves the CBCT problem using preconditioned GMRES\n" + " AB_GMRES(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem\n" + " using the projection data PROJ taken over ALPHA angles, corresponding\n" + " to the geometry descrived in GEO, using NITER iterations." + ) + IterativeReconAlg.__doc__ + + def __init__(self, proj, geo, angles, niter, **kwargs): + # Don't precompute V and W. + kwargs.update(dict(W=None, V=None)) + kwargs.update(dict(blocksize=angles.shape[0])) + self.re_init_at_iteration = 0 + IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) + backproject=kwargs.get("backprojector","matched") + if backproject is "matched": + self.backproject=Atb + elif backproject is "FDK": + self.backproject=algs.fdk + + def __compute_res__(self,x,w,y): + y=y.astype(np.float32) + for i in range(w.shape[0]): + x=x+np.reshape(w[i],self.res.shape)*y[i] + return x + + def run_main_iter(self): + + self.l2l = np.zeros((1, self.niter), dtype=np.float32) + w = np.zeros((self.niter+1,(np.prod(self.geo.nVoxel))),dtype=np.float32) + r=self.backproject(self.proj - Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids), self.geo, self.angles, gpuids=self.gpuids) + w[0] = r.ravel()/np.linalg.norm(r.ravel(), 2) + h=np.zeros((self.niter,self.niter+1),dtype=np.float32) + + for k in range(self.niter): + if self.verbose: + self._estimate_time_until_completion(k) + + qk=self.backproject(Ax(np.reshape(w[k],self.res.shape),self.geo,self.angles, "Siddon",gpuids=self.gpuids),self.geo, self.angles, gpuids=self.gpuids) + e1=np.zeros(k+2) + e1[0]=1 + for i in range(k+1): + h[k,i]=sum(qk.ravel()*w[i]) + qk=qk.ravel()-h[k,i]*w[i] + + h[k,k+1]=np.linalg.norm(qk.ravel(),2) + w[k+1]=qk.ravel()/h[k,k+1] + y=np.linalg.lstsq(np.transpose(h[0:k+1,0:k+2]),e1*np.linalg.norm(r.ravel(),2),rcond=None) + y=y[0] + + self.l2l[0, i] = np.linalg.norm((self.proj - tigre.Ax(self.__compute_res__(self.res,w[0:k+1],y), self.geo, self.angles, "Siddon", gpuids=self.gpuids)).ravel(),2) + if i > 0 and self.l2l[0, i] > self.l2l[0, i - 1]: + if self.re_init_at_iteration + 1 == i or not self.restart: + print("BA-GMRES exited due to divergence at iteration "+str(i)) + return self.__compute_res__(self.res,w[0:k+1],y) + + self.res=self.__compute_res__(self.res,w[0:-1],y) + return self.res +ba_gmres = decorator(BA_GMRES, name="ba_gmres") \ No newline at end of file From 9ae577ffcc6da1f6768c396082c22e8c2d2b7cf9 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Thu, 3 Nov 2022 16:55:41 +0000 Subject: [PATCH 40/42] Auto stash before merge of "master" and "origin/master" --- Python/demos/d08_Algorithms03.py | 6 ++--- .../algorithms/krylov_subspace_algorithms.py | 25 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Python/demos/d08_Algorithms03.py b/Python/demos/d08_Algorithms03.py index ceb0026e..6222585f 100644 --- a/Python/demos/d08_Algorithms03.py +++ b/Python/demos/d08_Algorithms03.py @@ -81,9 +81,9 @@ # AB/BA-GMRES imgabgmres, normhabgmres = algs.ab_gmres(noise_projections, geo, angles, 30, computel2=True) imgbagmres, normhbagmres = algs.ba_gmres(noise_projections, geo, angles, 30, computel2=True) -# AB/BA-GMRES with FDK backprojector -imgabgmresfdk, normhabgmresfdk = algs.ab_gmres(noise_projections, geo, angles, 30, computel2=True,backprojection="FDK") -imgbagmresfdk, normhbagmresfdk = algs.ba_gmres(noise_projections, geo, angles, 30, computel2=True,backprojection="FDK") +# # AB/BA-GMRES with FDK backprojector +imgabgmresfdk, normhabgmresfdk = algs.ab_gmres(noise_projections, geo, angles, 30, computel2=True,backprojector="FDK") +imgbagmresfdk, normhbagmresfdk = algs.ba_gmres(noise_projections, geo, angles, 30, computel2=True,backprojector="FDK") # SIRT for comparison. diff --git a/Python/tigre/algorithms/krylov_subspace_algorithms.py b/Python/tigre/algorithms/krylov_subspace_algorithms.py index ba637e6a..9e419b10 100644 --- a/Python/tigre/algorithms/krylov_subspace_algorithms.py +++ b/Python/tigre/algorithms/krylov_subspace_algorithms.py @@ -288,7 +288,7 @@ def run_main_iter(self): #% Test for convergence. #% msl: I still need to implement this. #% msl: There are suggestions on the original paper. Let's talk about it! - print(y) + self.res = self.res + np.reshape(np.matmul(np.transpose(self.__V__),y),self.res.shape) return self.res hybrid_lsqr = decorator(hybrid_LSQR, name="hybrid_lsqr") @@ -577,12 +577,16 @@ def __init__(self, proj, geo, angles, niter, **kwargs): kwargs.update(dict(W=None, V=None)) kwargs.update(dict(blocksize=angles.shape[0])) self.re_init_at_iteration = 0 - IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) - backproject=kwargs.get("backprojector","matched") - if backproject is "matched": + if "backprojector" in kwargs: + backproject=kwargs.pop("backprojector") + else: + backproject="matched" + if backproject == "matched": self.backproject=Atb - elif backproject is "FDK": + elif backproject == "FDK": self.backproject=algs.fdk + IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) + def __compute_res__(self,x,w,y): y=y.astype(np.float32) @@ -637,12 +641,15 @@ def __init__(self, proj, geo, angles, niter, **kwargs): kwargs.update(dict(W=None, V=None)) kwargs.update(dict(blocksize=angles.shape[0])) self.re_init_at_iteration = 0 - IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) - backproject=kwargs.get("backprojector","matched") - if backproject is "matched": + if "backprojector" in kwargs: + backproject=kwargs.pop("backprojector") + else: + backproject="matched" + if backproject == "matched": self.backproject=Atb - elif backproject is "FDK": + elif backproject == "FDK": self.backproject=algs.fdk + IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) def __compute_res__(self,x,w,y): y=y.astype(np.float32) From 4bbcf34668434c661e718a66323798c68c058939 Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Mon, 7 Nov 2022 18:38:07 +0000 Subject: [PATCH 41/42] Broken version of hybrid_fLSQSR_TV --- MATLAB/Algorithms/hybrid_fLSQR_TV.m | 22 +-- Python/tigre/algorithms/__init__.py | 1 + .../algorithms/krylov_subspace_algorithms.py | 170 +++++++++++++++++- 3 files changed, 181 insertions(+), 12 deletions(-) diff --git a/MATLAB/Algorithms/hybrid_fLSQR_TV.m b/MATLAB/Algorithms/hybrid_fLSQR_TV.m index d0d8b5f8..e0f1b3ed 100644 --- a/MATLAB/Algorithms/hybrid_fLSQR_TV.m +++ b/MATLAB/Algorithms/hybrid_fLSQR_TV.m @@ -73,7 +73,7 @@ % Initialise matrices U = single(zeros(numel(proj), niter+1)); -V = single(zeros(prod(geo.nVoxel), niter)); % Malena: Check if prod(geo.nVoxel) is correct, I want size of object +V = single(zeros(prod(geo.nVoxel), niter)); Z = single(zeros(prod(geo.nVoxel), niter)); % Flexible basis M = zeros(niter+1,niter); % Projected matrix 1 T = zeros(niter+1); % Projected matrix 2 @@ -108,8 +108,8 @@ u = u/normr; U(:,1) = u(:); -if max(max(max(x0))) == 0 - W = ones(size(x0)); +if max(x0(:)) == 0 + W = ones(size(x0),'single'); else W = build_weights (x0); end @@ -200,7 +200,7 @@ rhsZk = [rhsk; zeros(ii,1)]; y = MZk\rhsZk; - errorL2(ii)=norm(rhsk - Mk*y); +% errorL2(ii)=norm(rhsk - Mk*y); d = Z(:,1:ii)*y; x = x0 + reshape(d,size(x0)) + xA0; @@ -215,11 +215,11 @@ qualMeasOut(:,ii)=Measure_Quality(x_prev,x,QualMeasOpts); end aux=proj-Ax(x,geo,angles,'Siddon','gpuids',gpuids); - resL2(ii)=im3Dnorm(aux,'L2'); -% if ii>1 && resL2(ii)>resL2(ii-1) -% disp(['Algorithm stoped in iteration ', num2str(ii),' due to loss of ortogonality.']) -% return; -% end + errorL2(ii)=im3Dnorm(aux,'L2'); + if ii>1 && resL2(ii)>resL2(ii-1) + disp(['Algorithm stoped in iteration ', num2str(ii),' due to loss of ortogonality.']) + return; + end if (ii==1 && verbose) expected_time=toc*niter; disp('hybrid fLSQR TV'); @@ -372,9 +372,9 @@ function out = mvpE(k_aux, x , transp_flag) if strcmp(transp_flag,'transp') - out = x(:) - k_aux(:)*(ones(size(x(:)))'*x(:)); + out = x(:) - k_aux(:)*sum(x(:)); elseif strcmp(transp_flag,'notransp') - out = x(:) - ones(size(x(:)))*(k_aux(:)'*x(:)); + out = x(:) - (k_aux(:)'*x(:)); end end diff --git a/Python/tigre/algorithms/__init__.py b/Python/tigre/algorithms/__init__.py index 40f77c86..0fe068ca 100644 --- a/Python/tigre/algorithms/__init__.py +++ b/Python/tigre/algorithms/__init__.py @@ -15,6 +15,7 @@ from .krylov_subspace_algorithms import hybrid_lsqr from .krylov_subspace_algorithms import lsmr from .krylov_subspace_algorithms import irn_tv_cgls +from .krylov_subspace_algorithms import hybrid_flsqr_tv from .krylov_subspace_algorithms import ab_gmres from .krylov_subspace_algorithms import ba_gmres from .pocs_algorithms import asd_pocs diff --git a/Python/tigre/algorithms/krylov_subspace_algorithms.py b/Python/tigre/algorithms/krylov_subspace_algorithms.py index 9e419b10..f493e74d 100644 --- a/Python/tigre/algorithms/krylov_subspace_algorithms.py +++ b/Python/tigre/algorithms/krylov_subspace_algorithms.py @@ -3,12 +3,15 @@ import time import copy import numpy as np +import scipy as sp import tigre from tigre.algorithms.iterative_recon_alg import IterativeReconAlg from tigre.algorithms.iterative_recon_alg import decorator from tigre.utilities.Atb import Atb from tigre.utilities.Ax import Ax import tigre.algorithms as algs +import scipy.sparse.linalg + if hasattr(time, "perf_counter"): default_timer = time.perf_counter @@ -689,4 +692,169 @@ def run_main_iter(self): self.res=self.__compute_res__(self.res,w[0:-1],y) return self.res -ba_gmres = decorator(BA_GMRES, name="ba_gmres") \ No newline at end of file +ba_gmres = decorator(BA_GMRES, name="ba_gmres") + + +class hybrid_fLSQR_TV(IterativeReconAlg): + __doc__ = ( + " hybrid_fLSQR_TV solves the CBCT problem using preconditioned hybrid flexuble LSQR with TV regularization\n" + " AB_GMRES(PROJ,GEO,ANGLES,NITER) solves the reconstruction problem\n" + " using the projection data PROJ taken over ALPHA angles, corresponding\n" + " to the geometry descrived in GEO, using NITER iterations." + ) + IterativeReconAlg.__doc__ + + def __init__(self, proj, geo, angles, niter, **kwargs): + # Don't precompute V and W. + kwargs.update(dict(W=None, V=None)) + kwargs.update(dict(blocksize=angles.shape[0])) + self.re_init_at_iteration = 0 + IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs) + self.__U__ = np.zeros((self.niter+1,np.prod(self.geo.nDetector)*len(self.angles)),dtype=np.float32) + self.__V__ = np.zeros((self.niter,(np.prod(self.geo.nVoxel))),dtype=np.float32) + self.__Z__ = np.zeros((self.niter,(np.prod(self.geo.nVoxel))),dtype=np.float32) + + self.__M__ = np.zeros((self.niter,self.niter+1),dtype=np.float32) #% Projected matrix + self.__T__ = np.zeros((self.niter,self.niter),dtype=np.float32) #% Projected matrix + self.__proj_rhs__ = np.zeros((self.niter+1,1),dtype=np.float32) #% Projected right hand side + + + + def __build_weights__(self): + Dxx=np.copy(self.res) + Dyx=np.copy(self.res) + Dzx=np.copy(self.res) + + Dxx[0:-2,:,:]=self.res[0:-2,:,:]-self.res[1:-1,:,:] + Dyx[:,0:-2,:]=self.res[:,0:-2,:]-self.res[:,1:-1,:] + Dzx[:,:,0:-2]=self.res[:,:,0:-2]-self.res[:,:,1:-1] + + return (Dxx**2+Dyx**2+Dzx**2+1e-6)**(-1/4) + + def Lx(self,W,img): + img=np.reshape(img,self.res.shape) + Dxx=np.copy(img) + Dyx=np.copy(img) + Dzx=np.copy(img) + + Dxx[0:-2,:,:]=img[0:-2,:,:]-img[1:-1,:,:] + Dyx[:,0:-2,:]=img[:,0:-2,:]-img[:,1:-1,:] + Dzx[:,:,0:-2]=img[:,:,0:-2]-img[:,:,1:-1] + + return np.stack((W*Dxx,W*Dyx,W*Dzx),axis=0) + + def Ltx(self,W,img3): + img3 =np.reshape(img3,(3,*self.res.shape)) + Wx_1 = W * img3[0,:,:,:] + Wx_2 = W * img3[1,:,:,:] + Wx_3 = W * img3[2,:,:,:] + + DxtWx_1=Wx_1 + DytWx_2=Wx_2 + DztWx_3=Wx_3 + + DxtWx_1[1:-2,:,:]=Wx_1[1:-2,:,:]-Wx_1[0:-3,:,:] + DxtWx_1[-1,:,:]=-Wx_1[-2,:,:] + + DytWx_2[:,1:-2,:]=Wx_2[:,1:-2,:]-Wx_2[:,0:-3,:] + DytWx_2[:,-1,:]=-Wx_2[:,-2,:] + + DztWx_3[:,:,1:-2]=Wx_3[:,:,1:-2]-Wx_3[:,:,0:-3] + DztWx_3[:,:,-1]=-Wx_3[:,:,-2] + + return DxtWx_1 + DytWx_2 + DztWx_3 + + def mvT(self,k_aux,x ): + return x.ravel() - k_aux.ravel()*np.sum(x.ravel()) + def mv(self,k_aux,x ): + return x.ravel() - np.dot(k_aux.ravel(),x.ravel()) + + def run_main_iter(self): + # % Initialise matrices + + # (1) Initialize + u=self.proj - Ax(self.res, self.geo, self.angles, "Siddon", gpuids=self.gpuids) + + k_aux = Ax(np.ones(self.res.shape,dtype=np.float32)/np.sqrt(np.prod(self.geo.nVoxel)), self.geo, self.angles, "Siddon", gpuids=self.gpuids) + xA0 = 1/(np.sqrt(np.prod(self.geo.nVoxel))*np.linalg.norm(k_aux.ravel(),2)**2)*np.dot(k_aux.ravel(),u.ravel()) + xA0 =np.ones(self.res.shape,dtype=np.float32)*xA0 + + k_aux = 1/(np.sqrt(np.prod(self.geo.nVoxel))*np.linalg.norm(k_aux.ravel(),2)**2)*Atb(k_aux, self.geo, self.angles, backprojection_type="matched", gpuids=self.gpuids) + + u = u - Ax(xA0, self.geo, self.angles, "Siddon", gpuids=self.gpuids) + + normr=np.linalg.norm(u.ravel(),2) + u=u/normr + self.__U__[0]=u.ravel() + + self.__proj_rhs__[0]=normr + + if np.max(self.res)==0: + W=np.ones(self.res.shape,dtype=np.float32) + else: + W=self.__build_weights__() + + self.l2l = np.zeros((1, self.niter), dtype=np.float32) + + for i in range(self.niter): + if self.verbose: + self._estimate_time_until_completion(i) + v=Atb(u,self.geo,self.angles,backprojection_type="matched", gpuids=self.gpuids) + for j in range(i+1): + self.__T__[i,j] = np.dot(self.__V__[j],v.ravel()) + v = v.ravel() - self.__T__[i,j]*self.__V__[j] + v=np.reshape(v,self.res.shape) + self.__T__[i,i] = np.linalg.norm(v.ravel(),2) + v=v/self.__T__[i,i] + + z=self.mvT(k_aux,v) + + L = sp.sparse.linalg.LinearOperator((np.prod(self.res.shape),np.prod(self.res.shape)*3), matvec=lambda x: self.Ltx(W,x).ravel(),rmatvec=lambda x: self.Lx(W,x).ravel()) + aux_z=sp.sparse.linalg.lsqr(L,z.ravel(),iter_lim=50) + L = sp.sparse.linalg.LinearOperator((np.prod(self.res.shape)*3,np.prod(self.res.shape)), matvec=lambda x: self.Lx(W,x).ravel(),rmatvec=lambda x: self.Ltx(W,x).ravel()) + z=sp.sparse.linalg.lsqr(L,aux_z[0].ravel(),iter_lim=50) + z=z[0].astype(np.float32) + + z=self.mv(k_aux,z) + + self.__V__[i]=v.ravel() + self.__Z__[i]=z.ravel() + + # Update U and projected matrix M + u = Ax(np.reshape(z,self.res.shape), self.geo, self.angles, "Siddon", gpuids=self.gpuids) + for j in range(i+1): + self.__M__[i,j] = np.dot(self.__U__[j],u.ravel()) + u = u.ravel() - np.dot(self.__M__[i,j],self.__U__[j]) + self.__M__[i,i+1]=np.linalg.norm(u.ravel(),2) + u=u/self.__M__[i,i+1] + u=np.reshape(u,self.proj.shape) + self.__U__[i+1]=u.ravel() + + ### Solve the regularized projected problem + # (using the DVF of the small projected matrix) + + Mk=self.__M__[0:i+1,0:i+2] + # Prepare the projected regularization term + WZ=np.zeros((i+1,(3*np.prod(self.geo.nVoxel))),dtype=np.float32) + for j in range(i+1): + # This can be more efficiently... + # DZ can be saved and updated at each iteration + WZ[j]=self.Lx(W,self.__Z__[j]).ravel() + __,ZRk =sp.linalg.qr(np.transpose(WZ),mode='economic') + ZRksq=ZRk[0:i+1,0:i+1] + rhsk=self.__proj_rhs__[0:i+2] + MZk=np.concatenate((np.transpose(Mk),self.lmbda*ZRksq)) + rhsZk=np.concatenate((rhsk,np.zeros((i+1,1),dtype=np.float32))) + y = np.linalg.lstsq(MZk, rhsZk,rcond=None) + print(y[0]) + d=np.matmul(np.transpose(self.__Z__[0:i+1]),y[0]) + + x = self.res + np.reshape(d,self.res.shape) + xA0 + self.l2l[0, i] = np.linalg.norm((self.proj - tigre.Ax(x, self.geo, self.angles, "Siddon", gpuids=self.gpuids)).ravel(),2) + if i > 0 and self.l2l[0, i] > self.l2l[0, i - 1]: + if self.re_init_at_iteration + 1 == i or not self.restart: + print("BA-GMRES exited due to divergence at iteration "+str(i)) + return x + + return x + +hybrid_flsqr_tv = decorator(hybrid_fLSQR_TV, name="hybrid_flsqr_tv") From b89fa59382c3c4fabddd40a39d42754aea0907ac Mon Sep 17 00:00:00 2001 From: Ander Biguri Date: Tue, 6 Dec 2022 10:58:59 +0000 Subject: [PATCH 42/42] Tiny bug on restart for IRN_TV_CGLS --- MATLAB/Algorithms/IRN_TV_CGLS.m | 2 +- Python/demos/d09_Algorithms04.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/MATLAB/Algorithms/IRN_TV_CGLS.m b/MATLAB/Algorithms/IRN_TV_CGLS.m index 47bf1e63..fbe75978 100644 --- a/MATLAB/Algorithms/IRN_TV_CGLS.m +++ b/MATLAB/Algorithms/IRN_TV_CGLS.m @@ -65,7 +65,7 @@ end qualMeasOut=zeros(length(QualMeasOpts),niter); resL2 = zeros(1,niter); - +remember=0; iter=0; % for iii = 1:niter_outer while iter