From 95c721457bd2df9bfd9da5cbf5374870ce23bf7b Mon Sep 17 00:00:00 2001 From: ferreol Date: Wed, 3 Apr 2019 12:23:41 +0200 Subject: [PATCH] ## v1.1.2 release notes The v1.1.2 release does not contain any major changes but mainly incremental improvements and bug fixes. #### Improvements - New for 2D radon transform (Note that the adjoint is not really the adjoint, but only an approximation (up to about 20 dB), - LinOp compatibility with sparse matrices, - for estimation of the norm of an operator, - New to print SNR during optimization, - Better summation and multiplication of Map objects with scalars, - Rename to, - Add some kind of unitary tests, - : Epsilon can be a vector, - non scalar step size in #### Bug fixes Many bug fixes, among them: - Generalize Compiler option for C/C++ files and reactivate openmp for the Schatten norm - Better handling of singletons dimensions. --- .gitignore | 1 + Abstract/Cost.m | 3 +- Abstract/Map.m | 175 ++++++++++++++---- Abstract/OperationsOnMaps/CostComposition.m | 5 +- .../OperationsOnMaps/CostPartialSummation.m | 4 +- Abstract/OperationsOnMaps/MapComposition.m | 2 +- Abstract/OperationsOnMaps/MapSummation.m | 11 +- Abstract/Opti.m | 4 +- Cost/CostGoodRoughness.m | 2 +- Cost/CostHyperBolic.m | 26 ++- Cost/CostL2.m | 5 +- Cost/CostL2Composition.m | 3 +- Cost/CostMixNormSchatt1.m | 38 ++-- .../HessianSchatten/buildHessianSchatten.m | 29 ++- .../HessianSchatten/svd2D_decomp.cpp | 2 + Cost/IndicatorFunctions/CostComplexCircle.m | 2 +- Cost/IndicatorFunctions/CostComplexDisk.m | 2 +- Doc/source/conditionsuse.rst | 9 +- Doc/source/conf.py | 4 +- Doc/source/contact.rst | 2 +- Doc/source/cost.rst | 4 +- Doc/source/examples.rst | 5 +- Doc/source/index.rst | 24 ++- Doc/source/linop.rst | 9 + Doc/source/methodssummary.rst | 4 +- Doc/source/nonlinop.rst | 10 +- Doc/source/relatedPapers.rst | 86 +++++++++ .../DeconvEx/2D/Deconv_KL_HessSchatt_NonNeg.m | 4 +- Example/DeconvEx/2D/Deconv_KL_NonNeg_NoReg.m | 4 +- Example/DeconvEx/2D/Deconv_KL_TV_NonNeg.m | 8 +- Example/DeconvEx/2D/Deconv_LS_HessSchatt.m | 4 +- .../DeconvEx/2D/Deconv_LS_HessSchatt_NonNeg.m | 4 +- Example/DeconvEx/2D/Deconv_LS_NoReg.m | 11 +- Example/DeconvEx/2D/Deconv_LS_NonNeg_NoReg.m | 36 ++-- Example/DeconvEx/2D/Deconv_LS_TV.m | 4 +- Example/DeconvEx/2D/Deconv_LS_TV_NonNeg.m | 6 +- Example/DeconvEx/3D/DeconvScript.m | 2 +- LinOp/Deprecated/LinOpCpx.m | 1 + LinOp/LinOpBroadcast.m | 3 +- LinOp/LinOpConv.m | 2 +- LinOp/LinOpGrad.m | 6 +- LinOp/LinOpHess.m | 8 +- LinOp/LinOpMatrix.m | 6 +- LinOp/LinOpSum.m | 2 + LinOp/LinOpXRay.m | 117 ++++++++++++ LinOp/SelectorLinOps/LinOpSelector.m | 1 + LinOp/SelectorLinOps/LinOpSelectorPatch.m | 16 +- LinOp/Tests/testLinOpXray.m | 35 ++++ NEWS.md | 24 ++- .../{OpEWSquareRoot.m => OpEWSqrt.m} | 6 +- NonLinOp/Tests/TestCompositionEWop.m | 4 +- Opti/OptiADMM.m | 24 ++- Opti/OptiDouglasRachford.m | 38 +--- Opti/OptiFBS.m | 27 ++- Opti/OptiFGP.m | 2 +- Opti/OptiPrimalDualCondat.m | 13 +- .../MatlabOptimPack/installOptimPack.m | 13 +- Opti/OptiVMLMB.m | 20 +- Opti/OutputOpti/OutputOpti.m | 119 ++++++++---- Opti/OutputOpti/OutputOptiConjGrad.m | 21 +-- Opti/OutputOpti/OutputOptiSNR.m | 145 +++++++++++++++ Opti/TestCvg/TestCvgADMM.m | 21 ++- Opti/TestCvg/TestCvgCostRelative.m | 2 +- Opti/TestCvg/TestCvgMaxSnr.m | 2 +- Opti/TestCvg/TestCvgStepRelative.m | 2 +- README.md | 11 +- TODO_LIST.txt | 4 + Util/UnitTest/{Script.m => UnitScript.m} | 15 +- Util/UnitTest/testEstimateNorm.m | 24 +++ Util/estimateNorm.m | 86 +++++++++ Util/get_architecture.m | 9 + setGlobalBioImPath.m | 6 + setMatlabPath.m | 5 - 73 files changed, 1086 insertions(+), 308 deletions(-) create mode 100644 Doc/source/relatedPapers.rst create mode 100644 LinOp/LinOpXRay.m create mode 100644 LinOp/Tests/testLinOpXray.m rename NonLinOp/ElementWiseOp/{OpEWSquareRoot.m => OpEWSqrt.m} (93%) create mode 100644 Opti/OutputOpti/OutputOptiSNR.m rename Util/UnitTest/{Script.m => UnitScript.m} (85%) create mode 100644 Util/UnitTest/testEstimateNorm.m create mode 100644 Util/estimateNorm.m create mode 100644 Util/get_architecture.m create mode 100644 setGlobalBioImPath.m delete mode 100644 setMatlabPath.m diff --git a/.gitignore b/.gitignore index 2ebc232..543ebcc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *doctrees/ *Opti/OptiUtils/MatlabOptimPack/ .DS_Store +*Util/UnitTest/Data/ \ No newline at end of file diff --git a/Abstract/Cost.m b/Abstract/Cost.m index 8fa6e23..fbc5de6 100644 --- a/Abstract/Cost.m +++ b/Abstract/Cost.m @@ -98,7 +98,7 @@ % $$ \\mathrm{prox}_{\\alpha C}(\\mathrm{z}) = \\mathrm{arg} \\, \\mathrm{min}_{\\mathrm{u} \\in \\mathrm{X}} \\; \\frac{1}{2\\alpha} \\| \\mathrm{u} - \\mathrm{z} \\|_2^2 + C(\\mathrm{u}). $$ % % Calls the method :meth:`applyProx_` - assert(isscalar(alpha),'alpha must be a scalar'); + assert(isnumeric(alpha),'alpha must be a numeric'); if ~checkSize(z, this.sizein) % check input size error('Input to applyProx was size [%s], didn''t match stated sizein [%s].',... num2str(size(z)), num2str(this.sizein)); @@ -209,7 +209,6 @@ % Uses the method applyGrad (hence do not need to be % reimplemented in derived classes) x=y.*this.applyGrad(v); - x=sum(x(:)); end end diff --git a/Abstract/Map.m b/Abstract/Map.m index ea4db25..8e47ef1 100644 --- a/Abstract/Map.m +++ b/Abstract/Map.m @@ -159,59 +159,128 @@ % $$ \\mathrm{M}(\\mathrm{x}) := \\mathrm{H}(\\mathrm{x}) + \\mathrm{G}(\\mathrm{x})$$ % % Calls the method :meth:`plus_` - if ~cmpSize(this.sizein, G.sizein) % check input size - error('Input to plus is a %s of sizein [%s], which didn''t match the added %s sizein [%s].',... - class(G),num2str(G.sizein),class(this), num2str(this.sizein)); - end - if ~cmpSize(this.sizeout, G.sizeout) % check input size - error('Input to plus is a %s of sizeout [%s], which didn''t match the added %s sizeout [%s].',... - class(G),num2str(G.sizeout),class(this), num2str(this.sizeout)); + + if isnumeric(this) && isscalar(this) + if isequal(this,0) + M = G; + elseif cmpSize(G.sizein, G.sizeout) + M = LinOpDiag(G.sizein,this) +G; + else + error('Cannot sum a scalar and a non-square matrix'); + end + elseif isnumeric(G) && isscalar(G) + if isequal(G,0) + M = this; + elseif cmpSize(this.sizein, this.sizeout) + M = this + LinOpDiag(this.sizein,G); + else + error('Cannot sum a scalar and a non-square matrix'); + end + else + if ~cmpSize(this.sizein, G.sizein) % check input size + error('Input to plus is a %s of sizein [%s], which didn''t match the added %s sizein [%s].',... + class(G),num2str(G.sizein),class(this), num2str(this.sizein)); + end + if ~cmpSize(this.sizeout, G.sizeout) % check input size + error('Input to plus is a %s of sizeout [%s], which didn''t match the added %s sizeout [%s].',... + class(G),num2str(G.sizeout),class(this), num2str(this.sizeout)); + end + M = this.plus_(G); end - M = this.plus_(G); end function M = minus(this,G) % Overload operator (-) for :class:`Map` objects % $$ \\mathrm{M}(\\mathrm{x}) := \\mathrm{H}(\\mathrm{x}) - \\mathrm{G}(\\mathrm{x})$$ % % Calls the method :meth:`minus_` - if ~cmpSize(this.sizein, G.sizein) % check input size - error('Input to plus is a %s of sizein [%s], which didn''t match the substracted %s sizein [%s].',... - class(G),num2str(G.sizein),class(this), num2str(this.sizein)); - end - if ~cmpSize(this.sizeout, G.sizeout) % check input size - error('Input to plus is a %s of sizeout [%s], which didn''t match the substracted %s sizeout [%s].',... - class(G),num2str(G.sizeout),class(this), num2str(this.sizeout)); + if isnumeric(this) && isscalar(this) && isequal(this,0) % if multiply by 0 return 0 + M = -1*G; + elseif isnumeric(G) && isscalar(G) + if isequal(G,0) + M = this; + else + M = this - LinOpDiag(this.sizeout,G); + end + else + + + if ~cmpSize(this.sizein, G.sizein) % check input size + error('Input to plus is a %s of sizein [%s], which didn''t match the substracted %s sizein [%s].',... + class(G),num2str(G.sizein),class(this), num2str(this.sizein)); + end + if ~cmpSize(this.sizeout, G.sizeout) % check input size + error('Input to plus is a %s of sizeout [%s], which didn''t match the substracted %s sizeout [%s].',... + class(G),num2str(G.sizeout),class(this), num2str(this.sizeout)); + end + M = this.minus_(G); end - M = this.minus_(G); end function M = mpower(this,p) % Returns a new :class:`Map` which is the power p \\(\\mathrm{H}^{p}\\) of the % current \\(\\mathrm{H}\\). % % Calls the method :meth:`mpower_` - M=this.mpower_(p); + if ~cmpSize(this.sizeout, this.sizein) + error('Input to mpower must be a square map. Here %s x %s',... + num2str(this.sizeout),num2str(this.sizein)); + end + if isnumeric(p) && isscalar(p) + if isequal(p,0) + M = LinOpIdentity(G.sizein); + elseif isequal(p,1) + M = this; + else + M=this.mpower_(p); + end + else + error('p must be a scalar'); + end + end + + function M = power(this,p) + % Returns a new :class:`Map` which is the power p \\(\\mathrm{H}.^{p}\\) of the + % current \\(\\mathrm{H}\\). + % + % Calls the method :meth:`mpower_` + if isnumeric(p) && isscalar(p) + if isequal(p,1) + M = this; + else + M=this.power_(p); + end + else + error('p must be a scalar'); + end end function G = mtimes(this,G) % Overload operator (*) for :class:`Map` objects % $$ \\mathrm{M}(\\mathrm{x}) := \\mathrm{H}(\\mathrm{G}(\\mathrm{x}))$$ - % - If \\(\\mathrm{G}\\) is numeric of size sizein, then :meth:`apply` is called - % - If \\(\\mathrm{G}\\) is a :class:`Map`, then a - % :class:`MapComposition`is intanciated - if isa(G,'Map') - if (isnumeric(this) && isscalar(this)) % Left multiplication by scalar - if ~isequal(this,1) % if multiply by 1 do noting - if isa(G,'Cost') - G=CostMultiplication(this,G); - else - H=LinOpDiag(G.sizeout,this); - G=H.makeComposition(G); + % + % - If \\(\\mathrm{G}\\) is numeric of size sizein, then :meth:`apply` is called + % + % - If \\(\\mathrm{G}\\) is a :class:`Map`, then a :class:`MapComposition` is intanciated + + if isa(this,'LinOp') && isnumeric(G) && isscalar(G)&& isequal(G,0) + G = 0; + else + if isa(G,'Map') + if (isnumeric(this) && isscalar(this)) % Left multiplication by scalar + if isequal(this,0) % if multiply by 0 return 0 + G = 0; + elseif ~isequal(this,1) % if multiply by 1 do noting + if isa(G,'Cost') + G=CostMultiplication(this,G); + else + H=LinOpDiag(G.sizeout,this); + G=H.makeComposition(G); + end end + else + G =this.makeComposition(G); end else - G =this.makeComposition(G); + G = this.apply(G); end - else - G = this.apply(G); end end function M = times(this,G) @@ -220,17 +289,23 @@ % $$ \\mathrm{M}(\\mathrm{x}) := \\mathrm{H}(\\mathrm{x}) \\times \\mathrm{G}(\\mathrm{x})$$ % % Calls the method :meth:`times_` - if ~cmpSize(this.sizein, G.sizein) % check input size - error('Input to times is a %s of sizein [%s], which didn''t match the multiplied %s sizein [%s].',... - class(G),num2str(G.sizein),class(this), num2str(this.sizein)); - end - if ~cmpSize(this.sizeout, G.sizeout) % check input size - error('Input to times is a %s of sizeout [%s], which didn''t match the multiplied %s sizeout [%s].',... - class(G),num2str(G.sizeout),class(this), num2str(this.sizeout)); + + if isnumeric(this) && isscalar(this) && isequal(this,0) % if multiply by 0 return 0 + M = 0; + elseif isa(this,'LinOp')&& isnumeric(G)&& isscalar(G) && isequal(G,0) + M = 0; + else + if ~cmpSize(this.sizein, G.sizein) % check input size + error('Input to times is a %s of sizein [%s], which didn''t match the multiplied %s sizein [%s].',... + class(G),num2str(G.sizein),class(this), num2str(this.sizein)); + end + if ~cmpSize(this.sizeout, G.sizeout) % check input size + error('Input to times is a %s of sizeout [%s], which didn''t match the multiplied %s sizeout [%s].',... + class(G),num2str(G.sizeout),class(this), num2str(this.sizeout)); + end + M=this.times_(G); end - M=this.times_(G); end - % TODO Overload operator .^ to do (H(x)).^p ? function sz = size(this, varargin) sz = {this.sizeout, this.sizein}; if length(varargin) == 1 @@ -280,10 +355,30 @@ % When \\(p\\neq-1\\), this method is not implemented in this Abstract class if p==-1 M=this.makeInversion_(); + elseif mod(p,1)==0 + if p>0 + M = this; + while p>1 + M = M *this; + p = p-1; + end + else + M = this; + p = abs(p); + while p>1 + M = M *this; + p = p-1; + end + M= M.makeInversion_(); + end else error('mpower_ method not implemented for the given power==%d',p); end end + function M = power_(~,p) + % this method is not implemented in this Abstract class + error('power_ method not implemented for the given power==%d',p); + end function M = times_(this,G) % Constructs a :class:`MapMultiplication` object to element-wise multiply the % current :class:`Map` \\(\\mathrm{H}\\) with the given \\(\\mathrm{G}\\). diff --git a/Abstract/OperationsOnMaps/CostComposition.m b/Abstract/OperationsOnMaps/CostComposition.m index 795b28a..c55838f 100644 --- a/Abstract/OperationsOnMaps/CostComposition.m +++ b/Abstract/OperationsOnMaps/CostComposition.m @@ -77,8 +77,9 @@ % If this.H2 is a :class:`LinOp` and \\(\\mathrm{H} % \\mathrm{H}^{\\star}\\) is a :class:`LinOpScaledIdentity` % - if this.isConvex && this.isH2LinOp && this.isH2SemiOrtho - x = z + 1/this.nu*this.H2.applyAdjoint(this.H1.applyProx(this.H2*z,alpha*this.nu)-this.H2*z); + if this.isConvex && this.isH2LinOp && this.isH2SemiOrtho + H2z=this.H2*z; + x = z + 1/this.nu*this.H2.applyAdjoint(this.H1.applyProx(H2z,alpha*this.nu)-H2z); else x = applyProx_@Cost(this,z,alpha); end diff --git a/Abstract/OperationsOnMaps/CostPartialSummation.m b/Abstract/OperationsOnMaps/CostPartialSummation.m index dba0e4d..d814908 100644 --- a/Abstract/OperationsOnMaps/CostPartialSummation.m +++ b/Abstract/OperationsOnMaps/CostPartialSummation.m @@ -56,6 +56,8 @@ function setLsub(this,Lsub) methods (Access = protected) function updateSubset(this) switch this.partialGrad + case 0 + this.subset = 1:this.numMaps; case 1 this.subset = sort(randi(this.numMaps,this.Lsub,1)); case 2 @@ -86,7 +88,7 @@ function updateSubset(this) function g=applyGrad_(this,x) % Reimplemented from :class:`Cost` if this.partialGrad > 0 - g = zeros(size(x)); + g = zeros_(size(x)); for kk = 1:this.Lsub ind = this.subset(kk); g = g + this.alpha(ind)*this.mapsCell{ind}.applyGrad(x); diff --git a/Abstract/OperationsOnMaps/MapComposition.m b/Abstract/OperationsOnMaps/MapComposition.m index 83aacbe..30f89fe 100644 --- a/Abstract/OperationsOnMaps/MapComposition.m +++ b/Abstract/OperationsOnMaps/MapComposition.m @@ -82,7 +82,7 @@ function M = makeComposition_(this,G) % Reimplemented from :class:`Map` H2G = this.H2*G; % Since H1*H2 is not simplified, first try to compose H2 with G - if ~isa(H2G, 'MapComposition'); + if ~isa(H2G, 'MapComposition') M=this.H1*H2G; else M = MapComposition(this, G); diff --git a/Abstract/OperationsOnMaps/MapSummation.m b/Abstract/OperationsOnMaps/MapSummation.m index 5f75a94..306c602 100644 --- a/Abstract/OperationsOnMaps/MapSummation.m +++ b/Abstract/OperationsOnMaps/MapSummation.m @@ -69,6 +69,15 @@ this.isInvertible=false; this.sizein = this.mapsCell{1}.sizein; this.sizeout = this.mapsCell{1}.sizeout; + this.norm=0; + for n=1:this.numMaps + if this.mapsCell{n}.norm~=-1 + this.norm=this.norm+abs(this.alpha(n))*this.mapsCell{n}.norm; + else + this.norm=-1; + break + end + end for n =2:this.numMaps assert(cmpSize(this.sizein,this.mapsCell{n}.sizein),'%d-th input does not have consistent sizein', n) ; assert(cmpSize(this.sizeout,this.mapsCell{n}.sizeout),'%d-th input does not have the consistent sizeout ', n); @@ -91,7 +100,7 @@ end function x = applyJacobianT_(this, y, v) % Reimplemented from :class:`Map` - x = zeros_(this.sizeout); + x = zeros_(this.sizein); for n = 1:this.numMaps x = x + this.alpha(n) .* this.mapsCell{n}.applyJacobianT(y,v); end diff --git a/Abstract/Opti.m b/Abstract/Opti.m index 6016733..ccd458c 100644 --- a/Abstract/Opti.m +++ b/Abstract/Opti.m @@ -80,11 +80,11 @@ function run(this,x0) % - Algorithm iteration flag=this.doIteration(); - if(flag == this.OPTI_NEXT_IT) + if(flag == this.OPTI_NEXT_IT ) this.niter=this.niter+1; % - Convergence test - if this.CvOp.testConvergence(this), break; end + if this.niter>1 && this.CvOp.testConvergence(this), break; end % - Call OutputOpti object if this.ItUpOut>0 && (((mod(this.niter,this.ItUpOut)==0)|| (this.niter==1))) diff --git a/Cost/CostGoodRoughness.m b/Cost/CostGoodRoughness.m index 222cdb4..a1a1d59 100644 --- a/Cost/CostGoodRoughness.m +++ b/Cost/CostGoodRoughness.m @@ -4,7 +4,7 @@ % with \\( \\vert (\\nabla \\mathrm{x})_{.,k} \\vert^2 \\) the gradient % magnitude at pixel k. % - % :param G: :class:`LinOpGrad`object + % :param G: :class:`LinOpGrad` object % :param bet: smoothing parameter (default 1e-1) % % All attributes of parent class :class:`Cost` are inherited. diff --git a/Cost/CostHyperBolic.m b/Cost/CostHyperBolic.m index c3080a5..3c25f40 100644 --- a/Cost/CostHyperBolic.m +++ b/Cost/CostHyperBolic.m @@ -1,9 +1,9 @@ classdef CostHyperBolic < Cost % CostHyperBolic: Hyperbolic cost function - % $$C(\\mathrm{x}) := \\sum_{k=1}^K \\sqrt{\\sum_{l=1}^L (\\mathrm{x}-y)_{k,l}^2 + \\varepsilon^2}$$ + % $$C(\\mathrm{x}) := \\sum_{k=1}^K \\sqrt{\\sum_{l=1}^L (\\mathrm{x}-y)_{k,l}^2 + \\varepsilon_k^2} - \\sum_{k=1}^K\\varepsilon_k $$ % % :param index: dimensions along which the l2-norm will be applied (inner sum over l) - % :param epsilon: \\(\\in \\mathbb{R}_+\\) smoothing parameter (default + % :param epsilon: \\(\\in \\mathbb{R}^K_+\\) smoothing parameter (default % \\(10^{-3}\\)) % % All attributes of parent class :class:`Cost` are inherited. @@ -33,6 +33,7 @@ epsilon; index; sumOp; + sumEpsilon; end %% Constructor @@ -47,18 +48,29 @@ if nargin<3|| isempty(index) %no sum index=0; end - if nargin<2|| isempty(epsilon) - epsilon=1e-3; - end - this.epsilon = epsilon; this.index = index; - if index~=0 this.sumOp = LinOpSum(sz,index); else this.sumOp = LinOpDiag(sz); end + + if nargin<2|| isempty(epsilon) + epsilon=1e-3; + end + if ~(isscalar(epsilon)||checkSize(epsilon, this.sumOp.sizeout)) + error('epsilon is of size [%s] and didn''t match [%s].',... + num2str(size(epsilon)), num2str(this.sumOp.sizeout)); + end + + this.epsilon = epsilon; + if isscalar(this.epsilon) + this.sumEpsilon = prod(this.sumOp.sizeout(:)).*this.epsilon; + else + this.sumEpsilon = sum(this.epsilon(:)); + end + this.memoizeOpts.computeF=true; this.memoCache.computeF= struct('in',[],'out', []); end diff --git a/Cost/CostL2.m b/Cost/CostL2.m index 44a792f..c9aa905 100644 --- a/Cost/CostL2.m +++ b/Cost/CostL2.m @@ -18,6 +18,7 @@ % This program is free software: you can redistribute it and/or modify % it under the terms of the GNU General Public License as published by % the Free Software Foundation, either version 3 of the License, or + % % (at your option) any later version. % % This program is distributed in the hope that it will be useful, @@ -65,10 +66,10 @@ % Reimplemented from parent class :class:`Cost`. if (isscalar(this.y)&&(this.y==0)) wr=this.W*(x); - y=0.5*dot(x(:),wr(:)); + y=0.5*real(dot(x(:),wr(:))); else wr=this.W*(x-this.y); - y=0.5*dot(x(:)-this.y(:),wr(:)); + y=0.5*real(dot(x(:)-this.y(:),wr(:))); end end function g=applyGrad_(this,x) diff --git a/Cost/CostL2Composition.m b/Cost/CostL2Composition.m index c489ba2..f6b0118 100644 --- a/Cost/CostL2Composition.m +++ b/Cost/CostL2Composition.m @@ -95,7 +95,7 @@ if ~isfield(this.precomputeCache,'ytWy') this.precomputeCache.ytWy= this.H1.y(:)' * reshape(this.H1.W * this.H1.y,numel(this.H1.y), 1); end - y=0.5 * x(:)' * reshape(this.H1.W*this.H2.applyHtH(x),numel(this.H1.y),1) - x(:)' * this.precomputeCache.WHty(:) + 0.5 * this.precomputeCache.ytWy; + y=0.5 * x(:)' * reshape(this.H1.W*this.H2.applyHtH(x),prod(this.H2.sizein),1) - x(:)' * this.precomputeCache.WHty(:) + 0.5 * this.precomputeCache.ytWy; y = real(y); % due to numerical error else y=apply_@CostComposition(this,x); @@ -121,6 +121,7 @@ end function y=applyProx_(this,x,alpha) % Reimplemented from parent class :class:`CostComposition`. + % % Implemented % - if the operator \\(\\alpha\\mathrm{H^{\\star}WH + I} \\) is invertible: % $$ \\mathrm{y} = (\\alpha\\mathrm{H^{\\star}WH + I} )^{-1} (\\alpha \\mathrm{H^TWy +x})$$ diff --git a/Cost/CostMixNormSchatt1.m b/Cost/CostMixNormSchatt1.m index 05624ae..c497e32 100644 --- a/Cost/CostMixNormSchatt1.m +++ b/Cost/CostMixNormSchatt1.m @@ -11,18 +11,17 @@ % % All attributes of parent class :class:`Cost` are inherited. % - % **Note** The actual implementation works for size (sz) having one of the two following forms: + % **Note** The actual implementation works for size (sz) having one of the two following forms: % - % - (NxMx3) such that the Sp norm will be applied on each symetric 2x2 - % $$ \\begin{bmatrix} \\mathrm{x}_{n m 1} & \\mathrm{x}_{n m 2} \\newline - % \\mathrm{x}_{n m 2} & \\mathrm{x}_{n m 3} \\end{bmatrix}$$ - % and then the \\(\\ell_1\\) norm on the two other dimensions. - % - % - (NxMxKx6) such that the Sp norm will be applied on each symetric 3x3 - % $$ \\begin{bmatrix} \\mathrm{x}_{n m k 1} & \\mathrm{x}_{n m k 2} & \\mathrm{x}_{n m k 3} \\newline - % \\mathrm{x}_{n m k 2} & \\mathrm{x}_{n m k 4} & \\mathrm{x}_{n m k 5} \\newline - % \\mathrm{x}_{n m k 3} & \\mathrm{x}_{n m k 5} & \\mathrm{x}_{n m k 6} \\newline \\end{bmatrix}$$ - % and then the \\(\\ell_1\\) norm on the three other dimensions. + % * (NxMx3) such that the Sp norm will be applied on each symetric 2x2 + % $$ \\begin{bmatrix} \\mathrm{x}_{n m 1} & \\mathrm{x}_{n m 2} \\newline + % \\mathrm{x}_{n m 2} & \\mathrm{x}_{n m 3} \\end{bmatrix}$$ + % and then the \\(\\ell_1\\) norm on the two other dimensions. + % * (NxMxKx6) such that the Sp norm will be applied on each symetric 3x3 + % $$ \\begin{bmatrix} \\mathrm{x}_{n m k 1} & \\mathrm{x}_{n m k 2} & \\mathrm{x}_{n m k 3} \\newline + % \\mathrm{x}_{n m k 2} & \\mathrm{x}_{n m k 4} & \\mathrm{x}_{n m k 5} \\newline + % \\mathrm{x}_{n m k 3} & \\mathrm{x}_{n m k 5} & \\mathrm{x}_{n m k 6} \\newline \\end{bmatrix}$$ + % and then the \\(\\ell_1\\) norm on the three other dimensions. % % **References** % [1] Lefkimmiatis, S., Ward, J. P., & Unser, M. (2013). Hessian Schatten-norm regularization @@ -97,25 +96,14 @@ function y=applyProx_(this,x,alpha) % Reimplemented from parent class :class:`Cost`. - dim=size(x); if this.p==1 [E,V]=this.svdDecomp(x); E=max(abs(E)-alpha,0).*sign(E); y=reshape(this.svdRecomp(E,V),this.sizein); - % y=this.svdRecomp(E,V); elseif this.p==2 - % Emmanuel (05/07/2018): this part should also be updated - % in order to use the last dimension (not :,:,k or :,:,:,k, - % but more general - if dim(end)==3 % 2D - N=sqrt(x(:,:,1).^2+2*x(:,:,2).^2+x(:,:,3).^2); - y=repmat((N-1)./N,[ones(1,length(this.sizein)-1),3]).*x; - elseif dim(end)==6 % 3D - N=sqrt(x(:,:,:,1).^2+2*x(:,:,:,2).^2+2*x(:,:,:,3).^2+x(:,:,:,4)+2*x(:,:,:,5)+x(:,:,:,6)); - y=repmat((N-1)./N,[ones(1,length(this.sizein)-1),6]).*x; - else - error('last dimension of x should be 3 or 6'); - end + [E,V]=this.svdDecomp(x); + E=E./(1+alpha); + y=reshape(this.svdRecomp(E,V),this.sizein); else y=applyProx_@Cost(this,x,alpha); end diff --git a/Cost/CostUtils/HessianSchatten/buildHessianSchatten.m b/Cost/CostUtils/HessianSchatten/buildHessianSchatten.m index f501b46..a7e026d 100644 --- a/Cost/CostUtils/HessianSchatten/buildHessianSchatten.m +++ b/Cost/CostUtils/HessianSchatten/buildHessianSchatten.m @@ -1,6 +1,9 @@ -function buildHessianSchatten() +function buildHessianSchatten(options) %% buildHessianSchatten function % build the HessianSchatten norm mexgl files for CostMixNormSchatt1 +% +% You can give as a parameter of this function the path to your GCC +% compiler. Ex: buildHessianSchatten('GCC=/usr/bin/gcc-6') % Copyright (C) 2018 F. Soulez ferreol.soulez@epfl.ch % @@ -16,16 +19,26 @@ function buildHessianSchatten() % % You should have received a copy of the GNU General Public License % along with this program. If not, see . +if nargin==0 + options=[]; +end -[mpath,~,~] = fileparts(which('buildHessianSchatten')); disp('Installing HessianSchatten'); -disp('WARNING: depending on your system and compiler, OPENMP may not be activated leading to slow computation.'); +get_architecture; +if linux + options = [ options, ' CXXFLAGS='' -fopenmp ''',' LDFLAGS=''$LDFLAGS -fopenmp ''']; +else + disp('On your system and compiler, OPENMP is desactivated leading to slow computation. This can be tuned using the options parameter:'); + disp('Example: options = CXXFLAGS= -fopenmp '); +end + +[mpath,~,~] = fileparts(which('buildHessianSchatten')); pth = cd; cd(mpath); -MexOpt= ['-DUSE_BLAS_LIB ' '-DNEW_MATLAB_BLAS ' '-DINT_64BITS ' '-largeArrayDims ' 'COMPFLAGS=''$COMPFLAGS -Wall -mtune=native -fomit-frame-pointer -O2 -fopenmp ''']; -eval(['mex ','svd2D_recomp.cpp ',MexOpt]); -eval(['mex ','svd2D_decomp.cpp ',MexOpt]); -eval(['mex ','svd3D_recomp.cpp ',MexOpt]); -eval(['mex ','svd3D_decomp.cpp ',MexOpt]); +MexOpt= ['-DUSE_BLAS_LIB ' '-DNEW_MATLAB_BLAS ' '-DINT_64BITS ' '-largeArrayDims ' ,options, ' CXXFLAGS=''$CXXFLAGS -fPIC -Wall -mtune=native -fomit-frame-pointer -O2 ''' ' LDFLAGS=''$LDFLAGS ''']; +eval(['mex ',' svd2D_recomp.cpp ',MexOpt]); +eval(['mex ',' svd2D_decomp.cpp ',MexOpt]); +eval(['mex ',' svd3D_recomp.cpp ',MexOpt]); +eval(['mex ',' svd3D_decomp.cpp ',MexOpt]); cd(pth); end \ No newline at end of file diff --git a/Cost/CostUtils/HessianSchatten/svd2D_decomp.cpp b/Cost/CostUtils/HessianSchatten/svd2D_decomp.cpp index bcafd76..45c138b 100644 --- a/Cost/CostUtils/HessianSchatten/svd2D_decomp.cpp +++ b/Cost/CostUtils/HessianSchatten/svd2D_decomp.cpp @@ -20,6 +20,8 @@ -linux: mex -v svd2D_decomp.cpp CFLAGS="\$CFLAGS -openmp" LDFLAGS="\$LDFLAGS -openmp" -largeArrayDims -mac : mex svd2D_decomp.cpp -DUSE_BLAS_LIB -DNEW_MATLAB_BLAS -DINT_64BITS -largeArrayDims CXX=/usr/local/Cellar/gcc/6.3.0_1/bin/g++-6 CXXOPTIMFLAGS="-O3 -mtune=native -fomit-frame-pointer -fopenmp" LDOPTIMFLAGS=" -O " LINKLIBS="$LINKLIBS -lmwblas -lmwlapack -L"/usr/local/Cellar/gcc/6.3.0_1/lib/gcc/6" -L/ -fopenmp" + -mac (another option): "brew install llvm" on terminal, then use the command + mex svd2D_decomp.cpp -DUSE_BLAS_LIB -DNEW_MATLAB_BLAS -DINT_64BITS -largeArrayDims CXX="/usr/local/opt/llvm/bin/clang++" Copyright (C) 2017 E. Soubies emmanuel.soubies@epfl.ch diff --git a/Cost/IndicatorFunctions/CostComplexCircle.m b/Cost/IndicatorFunctions/CostComplexCircle.m index ee5e964..a7177ca 100644 --- a/Cost/IndicatorFunctions/CostComplexCircle.m +++ b/Cost/IndicatorFunctions/CostComplexCircle.m @@ -7,7 +7,7 @@ % All attributes of parent class :class:`CostComplexRing` are inherited % % - % :param radius radius of the circle (default 1) + % :param radius: radius of the circle (default 1) % % **Example** C=CostComplexCircle(radius,sz,y) % diff --git a/Cost/IndicatorFunctions/CostComplexDisk.m b/Cost/IndicatorFunctions/CostComplexDisk.m index ede6f7b..0f39ba5 100644 --- a/Cost/IndicatorFunctions/CostComplexDisk.m +++ b/Cost/IndicatorFunctions/CostComplexDisk.m @@ -6,7 +6,7 @@ % % All attributes of parent class :class:`CostComplexRing` are inherited % - % :param radius radius of the disk (default 1) + % :param radius: radius of the disk (default 1) % % **Example** C=CostComplexDisk(sz,radius, y) % diff --git a/Doc/source/conditionsuse.rst b/Doc/source/conditionsuse.rst index b9b89ee..8c701e3 100644 --- a/Doc/source/conditionsuse.rst +++ b/Doc/source/conditionsuse.rst @@ -10,7 +10,10 @@ If not, see . Whenever you present or publish results that are based on this library, please cite: -M. Unser, E. Soubies, F. Soulez, M. McCann, L. Donati, -`GlobalBioIm: A Unifying Computational Framework for Solving Inverse Problems `_ -Proceedings of the OSA Imaging and Applied Optics Congress on Computational Optical Sensing and Imaging (COSI'17), San Francisco CA, USA, June 26-29, 2017, paper no. CTu1B. +| `Pocket Guide to Solve Inverse Problems with GlobalBioIm `_, +| Submitted, 2019. +| E. Soubies, F. Soulez, M. T. McCann, T-A. Pham, L. Donati, T. Debarre, D. Sage, and M. Unser. +| `GlobalBioIm: A Unifying Computational Framework for Solving Inverse Problems `_, +| Proceedings of the OSA Computational Optical Sensing and Imaging congres, 2017. +| M. Unser, E. Soubies, F. Soulez, M. McCann, and L. Donati. \ No newline at end of file diff --git a/Doc/source/conf.py b/Doc/source/conf.py index 6987e04..66cdd09 100644 --- a/Doc/source/conf.py +++ b/Doc/source/conf.py @@ -62,9 +62,9 @@ # built documents. # # The short X.Y version. -version = u'1.1.1' +version = u'1.1.2' # The full version, including alpha/beta/rc tags. -release = u'1.1.1' +release = u'1.1.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/Doc/source/contact.rst b/Doc/source/contact.rst index fb35e60..e4294ae 100644 --- a/Doc/source/contact.rst +++ b/Doc/source/contact.rst @@ -2,7 +2,7 @@ Contact ******* -For any question concerning the GlobalBioIm library, :email:`contact us `. +For any question concerning the GlobalBioIm library, :email:`contact us `. diff --git a/Doc/source/cost.rst b/Doc/source/cost.rst index 8b19248..6c7f9e6 100644 --- a/Doc/source/cost.rst +++ b/Doc/source/cost.rst @@ -16,7 +16,7 @@ CostGoodRoughness applyGrad_, applyProx_, applyProxFench_, set_y CostHyperBolic ---------------------- +-------------- .. autoclass:: CostHyperBolic :show-inheritance: @@ -68,7 +68,7 @@ CostMixNorm21 applyGrad_, applyProx_, applyProxFench_, set_y CostMixNorm21NonNeg -------------- +------------------- .. autoclass:: CostMixNorm21NonNeg :show-inheritance: diff --git a/Doc/source/examples.rst b/Doc/source/examples.rst index 53fc456..0d3ec12 100644 --- a/Doc/source/examples.rst +++ b/Doc/source/examples.rst @@ -76,11 +76,11 @@ constraint, respectively. We are now ready to instanciate and run our algorithm Hn={H,Opreg,Id}; % Associated operators H_n rho_n=[1e-3,1e-3,1e-3]; % Multipliers rho_n ADMM=OptiADMM([],Fn,Hn,rho_n); % Declare optimizer - ADMM.OutOp=OutputOpti(1,im,round(maxIt/10),[1 2]); % build the output object + ADMM.OutOp=OutputOpti(1,[],round(maxIt/10),[1 2]); % build the output object ADMM.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4), 'StepRelative',1e-4); % Set the convergence tests ADMM.ItUpOut=dispIt; ADMM.maxiter=maxIt; - ADMM.run(xopt); + ADMM.run(zeros(sznew)); Here, three splittings have been done: \\(\\mathrm{u_1=Hx}, \\; \\mathrm{u_2=\\nabla x}\\), and \\(\\mathrm{u_3=x}\\). We do not need to provide a solver to the ADMM algorithm (5th argument) since the operator algebra ensures that the operator @@ -96,3 +96,4 @@ The deconvolved image is shown in Figure 2. :align: center Fig 2. Deconvolved C. Elegans embryo. + diff --git a/Doc/source/index.rst b/Doc/source/index.rst index 1cff590..7a7cc26 100644 --- a/Doc/source/index.rst +++ b/Doc/source/index.rst @@ -25,19 +25,24 @@ automatically from comments within M-files. Releases -------- - - `v 1.1.1 `_ (September 2018). + - `v 1.1.2 `_ (April 2018). + - `v 1.1.1 `_ (September 2018). - `v 1.1 `_ (July 2018). *Speed up your codes using the library with GPU* (:ref:`read more `). - `v 1.0.1 `_ (May 2018). - `v 1.0 `_ milestone (March 2018). - `v 0.2 `_ (November 2017). *New tools, more flexibility, improved composition*. - - v 0.1 (June 2017). First public release of the library. + - v 0.1 (June 2017). *First public release of the library.* -Reference ---------- +References +---------- - - M. Unser, E. Soubies, F. Soulez, M. McCann, L. Donati, - `GlobalBioIm: A Unifying Computational Framework for Solving Inverse Problems `_, - Proceedings of the OSA Imaging and Applied Optics Congress on Computational Optical Sensing and Imaging (COSI'17), San Francisco CA, USA, June 26-29, 2017, paper no. CTu1B. +| `Pocket Guide to Solve Inverse Problems with GlobalBioIm `_, +| Submitted, 2019. +| E. Soubies, F. Soulez, M. T. McCann, T-A. Pham, L. Donati, T. Debarre, D. Sage, and M. Unser. + +| `GlobalBioIm: A Unifying Computational Framework for Solving Inverse Problems `_, +| Proceedings of the OSA Computational Optical Sensing and Imaging congres, 2017. +| M. Unser, E. Soubies, F. Soulez, M. McCann, and L. Donati. Contents ******** @@ -46,9 +51,10 @@ Contents :maxdepth: 1 :caption: General - Download or Clone (v 1.1.1) + Download or Clone (v 1.1.2) infos - examples + examples + relatedPapers conditionsuse .. toctree:: diff --git a/Doc/source/linop.rst b/Doc/source/linop.rst index 401b1e1..57c396a 100644 --- a/Doc/source/linop.rst +++ b/Doc/source/linop.rst @@ -88,6 +88,15 @@ LinOpSum :members: apply_, applyJacobianT_, applyInverse_, plus_, minus_, mpower_, makeComposition_, applyAdjoint_, applyHtH_, applyHHt_, applyAdjointInverse_, makeAdjoint_, makeHtH_, makeHHt_, makeInversion_ + +LinOpXRay +--------- + +.. autoclass:: LinOpXRay + :show-inheritance: + :members: apply_, applyJacobianT_, applyInverse_, plus_, minus_, mpower_, makeComposition_, + applyAdjoint_, applyHtH_, applyHHt_, applyAdjointInverse_, makeAdjoint_, makeHtH_, makeHHt_, makeInversion_ + .. automodule:: LinOp.SelectorLinOps diff --git a/Doc/source/methodssummary.rst b/Doc/source/methodssummary.rst index eeacc46..764d37f 100644 --- a/Doc/source/methodssummary.rst +++ b/Doc/source/methodssummary.rst @@ -129,9 +129,7 @@ Opti +-------------------------+------------------------------------------------------+ | updateParams() | | Update algorithm parameters (e.g. descent step). | +-------------------------+------------------------------------------------------+ -| test_convergence() | | Test if the algorithm has converged. | -+-------------------------+------------------------------------------------------+ | starting_verb() | | Display starting message. | +-------------------------+------------------------------------------------------+ | ending_verb() | | Display ending message. | -+-------------------------+------------------------------------------------------+ \ No newline at end of file ++-------------------------+------------------------------------------------------+ diff --git a/Doc/source/nonlinop.rst b/Doc/source/nonlinop.rst index 1a21a4b..481c853 100644 --- a/Doc/source/nonlinop.rst +++ b/Doc/source/nonlinop.rst @@ -3,17 +3,17 @@ Non-Linear Operators This section contains non-linear operator classes which all derive directly from the abstract class :class:`Map`. -.. automodule:: NonLinop +.. automodule:: NonLinOp -.. automodule:: NonLinop.ElementWiseOp +.. automodule:: NonLinOp.ElementWiseOp Element-wise Operators ---------------------- -OpEWSquareRoot -.............. +OpEWSqrt +........ -.. autoclass:: OpEWSquareRoot +.. autoclass:: OpEWSqrt :show-inheritance: :members: apply_, applyJacobianT_, applyInverse_, plus_, minus_, mpower_, makeComposition_, diff --git a/Doc/source/relatedPapers.rst b/Doc/source/relatedPapers.rst new file mode 100644 index 0000000..447ca00 --- /dev/null +++ b/Doc/source/relatedPapers.rst @@ -0,0 +1,86 @@ +.. _ref-relatedPapers: + +Related Papers +************** + +This list gathers works that use the GlobalBioIm library. +If you want to add your work in this list, do not hesitate to +:email:`contact us`. + +Papers with open-source code +---------------------------- + +| **Improving 3D MA-TIRF Reconstruction with Deconvolution and Background Estimation** +| Proceedings of ISBI, 2019. + `[Paper] `_ + `[Code] `_ +| E. Soubies, L. Blanc-Feraud, S. Schaub, and E. Van Obberghen-Schilling. + +| **Nanometric Axial Resolution of Fibronectin Assembly Units Achieved with an Efficient Reconstruction Approach for Multi-Angle-TIRF Microscopy** +| Scientific Reports, 2019. + `[Paper] `_ + `[Code] `_ +| E. Soubies, A. Radwanska, D. Grall, L. Blanc-Feraud, E. Van Obberghen-Schilling, and S. Schaub. + +| **Region of interest X-ray computed tomography via corrected back projection** +| Proceedings of ISBI, 2018. + `[Paper] `_ + `[Code] `_ +| M. McCann, L. Vilaclara, and M. Unser. + +| **Efficient Inversion of Multiple-Scattering Model for Optical Diffraction Tomography** +| Optics Express 25-18, pp. 21786-21800, 2017. + `[Paper] `_ + `[Code] `_ +| E. Soubies, T.-A. Pham, and M. Unser. + + +Other papers +------------ + +| **Inner-Loop-Free ADMM for Cryo-EM** +| Proceedings of ISBI, 2019. + `[Paper] `_ +| L. Donati, E. Soubies, and M. Unser. + +| **Computational Super-Sectioning for Single-Slice Structured Illumination Microscopy** +| IEEE Transactions on Computational Imaging, 2018. + `[Paper] `_ +| E. Soubies and M. Unser. + +| **Phaseless Diffraction Tomography with Regularized Beam Propagation** +| Proceedings of ISBI, 2018. + `[Paper] `_ +| T.-A. Pham, E. Soubies, J. Lim, A. Goy, F. Soulez, D. Psaltis and M. Unser. + +| **Imaging neural activity in the ventral nerve cord of behaving adult Drosophila** +| Nature communications, 9-1, pp. 4390, 2018. + `[Paper] `_ +| C.-L. Chen, L. Hermans, M. Viswanathan, D. Fortun, F. Aymanns, M. Unser, A. Cammarato, M. Dickinson, and P. Ramdya. + +| **Versatile Reconstruction Framework for Diffraction Tomography with Intensity Measurements and Multiple Scattering** +| Optics Express, 26-3, pp. 2749-2763, 2018. + `[Paper] `_ +| T.-A. Pham, E. Soubies, J. Lim, A. Goy, F. Soulez, D. Psaltis and M. Unser. + +| **Deep Convolutional Neural Network for Inverse Problems in Imaging** +| IEEE Transactions on Image Processing, 2017. + `[Paper] `_ +| K.H. Jin, M.T. McCann, E. Froustey, and M. Unser. + +| **High-Quality Parallel-Ray X-Ray CT Back Projection Using Optimized Interpolation** +| IEEE Transactions on Image Processing, 2017. + `[Paper] `_ +| M.T. McCann and M. Unser. + +| **Compact lensless phase imager** +| Optics Express, Optical Society of America, 2017, 25 (4), pp.889 - 895. + `[Paper] `_ +| M. Rostykus, F. Soulez, M. Unser, C. Moser. + +| **Compact in-line lensfree digital holographic microscope** +| Methods, Elsevier, 2017, + `[Paper] `_ +| M. Rostykus, F. Soulez, M. Unser, C. Moser. + + diff --git a/Example/DeconvEx/2D/Deconv_KL_HessSchatt_NonNeg.m b/Example/DeconvEx/2D/Deconv_KL_HessSchatt_NonNeg.m index 24219d2..2b7d30e 100644 --- a/Example/DeconvEx/2D/Deconv_KL_HessSchatt_NonNeg.m +++ b/Example/DeconvEx/2D/Deconv_KL_HessSchatt_NonNeg.m @@ -53,7 +53,7 @@ Hn={H,Hess,LinOpDiag(sz)}; rho_n=[1e-2,1e-2,1e-2]; ADMM=OptiADMM([],Fn,Hn,rho_n,[]); -ADMM.OutOp=OutputOpti(1,im,40,[1 2]); +ADMM.OutOp=OutputOptiSNR(1,im,40,[1 2]); ADMM.ItUpOut=5; % call OutputOpti update every ItUpOut iterations ADMM.maxiter=200; % max number of iterations ADMM.run(y); % run the algorithm @@ -63,7 +63,7 @@ Fn={lamb*R_1sch,F}; Hn={Hess,H}; PDC=OptiPrimalDualCondat([],R_POS,Fn,Hn); -PDC.OutOp=OutputOpti(1,im,40,[2 3]); +PDC.OutOp=OutputOptiSNR(1,im,40,[2 3]); PDC.tau=100; % set algorithm parameters PDC.sig=1e-2; % PDC.rho=1.2; % diff --git a/Example/DeconvEx/2D/Deconv_KL_NonNeg_NoReg.m b/Example/DeconvEx/2D/Deconv_KL_NonNeg_NoReg.m index ef3a689..dea8ad2 100644 --- a/Example/DeconvEx/2D/Deconv_KL_NonNeg_NoReg.m +++ b/Example/DeconvEx/2D/Deconv_KL_NonNeg_NoReg.m @@ -47,7 +47,7 @@ % -- FISTA KL + NonNeg FBS=OptiFBS(KL*H,R_POS); -FBS.OutOp=OutputOpti(1,im,40); +FBS.OutOp=OutputOptiSNR(1,im,40); FBS.ItUpOut=5; % call OutputOpti update every ItUpOut iterations FBS.fista=true; % activate fista FBS.maxiter=200; % max number of iterations @@ -56,7 +56,7 @@ % -- Richardson-Lucy KL + NonNeg (implicit) RL=OptiRichLucy(KL*H); -RL.OutOp=OutputOpti(1,im,40); +RL.OutOp=OutputOptiSNR(1,im,40); RL.ItUpOut=5; % call OutputOpti update every ItUpOut iterations RL.maxiter=200; % max number of iterations RL.run(y); % run the algorithm diff --git a/Example/DeconvEx/2D/Deconv_KL_TV_NonNeg.m b/Example/DeconvEx/2D/Deconv_KL_TV_NonNeg.m index 2be91e1..fbd32da 100644 --- a/Example/DeconvEx/2D/Deconv_KL_TV_NonNeg.m +++ b/Example/DeconvEx/2D/Deconv_KL_TV_NonNeg.m @@ -57,7 +57,7 @@ Hn={H,G,LinOpDiag(sz)}; rho_n=[1e-3,1e-3,1e-3]; ADMM=OptiADMM([],Fn,Hn,rho_n); -ADMM.OutOp=OutputOpti(1,im,40,[1 2]); +ADMM.OutOp=OutputOptiSNR(1,im,40,[1 2]); ADMM.ItUpOut=2; % call OutputOpti update every ItUpOut iterations ADMM.maxiter=200; % max number of iterations ADMM.run(y); % run the algorithm @@ -66,7 +66,7 @@ Fn={lamb*R_N12,F}; Hn={G,H}; PDC=OptiPrimalDualCondat([],R_POS,Fn,Hn); -PDC.OutOp=OutputOpti(1,im,40,[2 3]); +PDC.OutOp=OutputOptiSNR(1,im,40,[2 3]); PDC.tau=100; % set algorithm parameters PDC.sig=1e-2; % PDC.rho=1.2; % @@ -76,7 +76,7 @@ % -- Richardson-Lucy-TV KL + TV + NonNeg (implicit) RLTV=OptiRichLucy(F*H,1,lamb); -RLTV.OutOp=OutputOpti(1,im,40); +RLTV.OutOp=OutputOptiSNR(1,im,40); RLTV.ItUpOut=2; % call OutputOpti update every ItUpOut iterations RLTV.maxiter=200; % max number of iterations RLTV.run(y); % run the algorithm @@ -86,7 +86,7 @@ C = F*H+ lamb*hyperB; C.memoizeOpts.apply=true; VMLMB=OptiVMLMB(C,0.,[]); -VMLMB.OutOp=OutputOpti(1,im,40); +VMLMB.OutOp=OutputOptiSNR(1,im,40); VMLMB.ItUpOut=2; VMLMB.maxiter=200; % max number of iterations VMLMB.m=3; % number of memorized step in hessian approximation diff --git a/Example/DeconvEx/2D/Deconv_LS_HessSchatt.m b/Example/DeconvEx/2D/Deconv_LS_HessSchatt.m index 3fdd397..d7cb6d8 100644 --- a/Example/DeconvEx/2D/Deconv_LS_HessSchatt.m +++ b/Example/DeconvEx/2D/Deconv_LS_HessSchatt.m @@ -51,7 +51,7 @@ % -- Chambolle-Pock LS + ShattenHess CP=OptiChambPock(lamb*R_1sch,Hess,F); -CP.OutOp=OutputOpti(1,im,20); +CP.OutOp=OutputOptiSNR(1,im,20); CP.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4), 'StepRelative',1e-4); CP.tau=1; % algorithm parameters CP.sig=0.02; % @@ -65,7 +65,7 @@ rho_n=[1e-1]; ADMM=OptiADMM(F,Fn,Hn,rho_n); ADMM.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4), 'StepRelative',1e-4); -ADMM.OutOp=OutputOpti(1,im,10); +ADMM.OutOp=OutputOptiSNR(1,im,10); ADMM.ItUpOut=1; % call OutputOpti update every ItUpOut iterations ADMM.maxiter=200; % max number of iterations ADMM.run(zeros(size(y))); % run the algorithm diff --git a/Example/DeconvEx/2D/Deconv_LS_HessSchatt_NonNeg.m b/Example/DeconvEx/2D/Deconv_LS_HessSchatt_NonNeg.m index 0bdbb08..7221a61 100644 --- a/Example/DeconvEx/2D/Deconv_LS_HessSchatt_NonNeg.m +++ b/Example/DeconvEx/2D/Deconv_LS_HessSchatt_NonNeg.m @@ -57,7 +57,7 @@ rho_n=[1e-1,1e-1]; ADMM=OptiADMM(F,Fn,Hn,rho_n); ADMM.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4,[1 2]), 'StepRelative',1e-4); -ADMM.OutOp=OutputOpti(1,im,10,[1 2]); +ADMM.OutOp=OutputOptiSNR(1,im,10,[1 2]); ADMM.ItUpOut=1; % call OutputOpti update every ItUpOut iterations ADMM.maxiter=200; % max number of iterations ADMM.run(zeros(size(y))); % run the algorithm @@ -67,7 +67,7 @@ Hn={Hess}; PDC=OptiPrimalDualCondat(F,R_POS,Fn,Hn); PDC.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4,[1 3]), 'StepRelative',1e-4); -PDC.OutOp=OutputOpti(1,im,40,[1 3]); +PDC.OutOp=OutputOptiSNR(1,im,40,[1 3]); PDC.tau=1; % set algorithm parameters PDC.sig=1e-2; % PDC.rho=1.7; % diff --git a/Example/DeconvEx/2D/Deconv_LS_NoReg.m b/Example/DeconvEx/2D/Deconv_LS_NoReg.m index aa9ea4b..2b362e8 100644 --- a/Example/DeconvEx/2D/Deconv_LS_NoReg.m +++ b/Example/DeconvEx/2D/Deconv_LS_NoReg.m @@ -57,14 +57,14 @@ % -- Gradient Descent LS GD=OptiGradDsct(F); -GD.OutOp=OutputOpti(1,im,40); +GD.OutOp=OutputOptiSNR(1,im,40); GD.ItUpOut=2; % call OutputOpti update every ItUpOut iterations GD.maxiter=200; % max number of iterations GD.run(y); % run the algorithm (Note that gam is fixed automatically to 1/F.lip here since F.lip is defined and since we do not have setted gam) %% -- VMLMB LS VMLMB=OptiVMLMB(F,[],[]); -VMLMB.OutOp=OutputOpti(1,im,40); +VMLMB.OutOp=OutputOptiSNR(1,im,40); VMLMB.ItUpOut=2; VMLMB.maxiter=200; % max number of iterations VMLMB.m=2; % number of memorized step in hessian approximation (one step is enough for quadratic function) @@ -112,11 +112,18 @@ valVMLMB=VMLMB.OutOp.evolcost;save('Util/UnitTest/Data/Deconv_LS_NoReg_VMLMB','valVMLMB'); valCG=CG.OutOp.evolcost;save('Util/UnitTest/Data/Deconv_LS_NoReg_CG','valCG'); else + if (exist('Util/UnitTest/Data/Deconv_LS_NoReg_GD.mat','file')==2) && ... + (exist('Util/UnitTest/Data/Deconv_LS_NoReg_VMLMB.mat','file')==2) && ... + (exist('Util/UnitTest/Data/Deconv_LS_NoReg_CG.mat','file')==2) + load('Util/UnitTest/Data/Deconv_LS_NoReg_GD'); load('Util/UnitTest/Data/Deconv_LS_NoReg_VMLMB'); load('Util/UnitTest/Data/Deconv_LS_NoReg_CG'); stateTest=isequal(valGD,GD.OutOp.evolcost) && isequal(valVMLMB,VMLMB.OutOp.evolcost) && isequal(valCG,CG.OutOp.evolcost); message=['Max error between costs evolution : ',num2str(max([norm(valGD-GD.OutOp.evolcost),... norm(valVMLMB-VMLMB.OutOp.evolcost),norm(valCG-CG.OutOp.evolcost)]))]; + else + error('you must generate data before setting : set generateDataUnitTests=1 in UnitScript.m'); + end end end \ No newline at end of file diff --git a/Example/DeconvEx/2D/Deconv_LS_NonNeg_NoReg.m b/Example/DeconvEx/2D/Deconv_LS_NonNeg_NoReg.m index f33324e..e6a11a4 100644 --- a/Example/DeconvEx/2D/Deconv_LS_NonNeg_NoReg.m +++ b/Example/DeconvEx/2D/Deconv_LS_NonNeg_NoReg.m @@ -3,10 +3,10 @@ % the Least-Squares function plus the NonNegativity constraint % without any regularizer: % 0.5 ||Hx - y||^2 + i_{>0}(x) -% using FISTA and VMLMB +% using FISTA, Douglas-Rachford and VMLMB % % See LinOp, LinOpConv, Cost, CostL2, CostNonNeg, Opti, -% OptiFBS, OptiVMLMB, OutpuOpti +% OptiFBS, OptiVMLMB, OptiDouglasRachford, OutpuOpti %------------------------------------------------------------ close all; help Deconv_Ls_NonNeg_NoReg @@ -48,19 +48,25 @@ % -- FISTA LS + NonNeg FBS=OptiFBS(F,R_POS); -FBS.OutOp=OutputOpti(1,im,10); +FBS.OutOp=OutputOptiSNR(1,im,10); FBS.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4), 'StepRelative',1e-4); FBS.ItUpOut=2; % call OutputOpti update every ItUpOut iterations FBS.fista=true; % activate fista FBS.maxiter=200; % max number of iterations FBS.run(zeros(size(y)));% run the algorithm (Note that gam is fixed automatically to 1/F.lip here since F.lip is defined and since we do not have setted gam) +% -- Douglas-Rachford LS + NonNeg +DR=OptiDouglasRachford(F,R_POS,[],10,1.5); +DR.OutOp=OutputOptiSNR(1,im,10); +DR.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4), 'StepRelative',1e-4); +DR.ItUpOut=2; % call OutputOpti update every ItUpOut iterations +DR.maxiter=200; % max number of iterations +DR.run(y); -%% -- VMLMB LS + NonNeg - +% - VMLMB LS + NonNeg H.memoizeOpts.applyHtH=true; VMLMB=OptiVMLMB(F,0.,[]); -VMLMB.OutOp=OutputOpti(1,im,10); +VMLMB.OutOp=OutputOptiSNR(1,im,10); VMLMB.CvOp=TestCvgCombine('CostRelative',1e-4, 'StepRelative',1e-4); % identical to VMLMB.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4),TestCvgStepRelative(1e-5)); VMLMB.ItUpOut=2; VMLMB.maxiter=200; % max number of iterations @@ -70,18 +76,22 @@ %% -- Display imdisp(FBS.xopt,'LS + NonNeg (FISTA)',1); imdisp(VMLMB.xopt,'LS+POS (VMLMB)',1); +imdisp(DR.xopt,'LS+POS (DR)',1); figure;plot(FBS.OutOp.iternum,FBS.OutOp.evolcost,'LineWidth',1.5);grid; set(gca,'FontSize',12); -hold all;loglog(VMLMB.OutOp.iternum,VMLMB.OutOp.evolcost,'LineWidth',1.5);grid; set(gca,'FontSize',12); -xlabel('Iterations');ylabel('Cost');legend('LS+POS (FISTA)','LS+POS (VMLMB)');title('Cost evolution'); +hold all;loglog(VMLMB.OutOp.iternum,VMLMB.OutOp.evolcost,'LineWidth',1.5); +loglog(DR.OutOp.iternum,DR.OutOp.evolcost,'LineWidth',1.5); +xlabel('Iterations');ylabel('Cost');legend('LS+POS (FISTA)','LS+POS (VMLMB)','LS+POS (DR)');title('Cost evolution'); figure;subplot(1,2,1); grid; hold all; title('Evolution SNR');set(gca,'FontSize',12); semilogy(FBS.OutOp.iternum,FBS.OutOp.evolsnr,'LineWidth',1.5); semilogy(VMLMB.OutOp.iternum,VMLMB.OutOp.evolsnr,'LineWidth',1.5); +semilogy(DR.OutOp.iternum,DR.OutOp.evolsnr,'LineWidth',1.5); xlabel('Iterations');ylabel('SNR (dB)');legend('LS+POS (FISTA)','LS+POS (VMLMB)','Location','southeast'); subplot(1,2,2);hold on; grid; title('Runing Time (200 iterations)');set(gca,'FontSize',12); orderCol=get(gca,'ColorOrder'); bar(1,[FBS.time],'FaceColor',orderCol(1,:),'EdgeColor','k'); -bar(2,[VMLMB.time],'FaceColor',orderCol(1,:),'EdgeColor','k'); +bar(2,[VMLMB.time],'FaceColor',orderCol(2,:),'EdgeColor','k'); +bar(3,[DR.time],'FaceColor',orderCol(3,:),'EdgeColor','k'); set(gca,'xtick',[1 2]);ylabel('Time (s)'); set(gca,'xticklabels',{'LS+POS (FISTA)','LS+POS (VMLMB)'});set(gca,'XTickLabelRotation',45) @@ -91,11 +101,13 @@ if generateDataUnitTests valFBS=FBS.OutOp.evolcost;save('Util/UnitTest/Data/Deconv_LS_NonNeg_NoReg_FBS','valFBS'); valVMLMB=VMLMB.OutOp.evolcost;save('Util/UnitTest/Data/Deconv_LS_NonNeg_NoReg_VMLMB','valVMLMB'); + valDR=DR.OutOp.evolcost;save('Util/UnitTest/Data/Deconv_LS_NonNeg_NoReg_DR','valDR'); else load('Util/UnitTest/Data/Deconv_LS_NonNeg_NoReg_FBS'); load('Util/UnitTest/Data/Deconv_LS_NonNeg_NoReg_VMLMB'); - stateTest=isequal(valFBS,FBS.OutOp.evolcost) && isequal(valVMLMB,VMLMB.OutOp.evolcost); - message=['Max error between costs evolution : ',num2str(norm(valFBS-FBS.OutOp.evolcost),... - norm(valVMLMB-VMLMB.OutOp.evolcost))]; + load('Util/UnitTest/Data/Deconv_LS_NonNeg_NoReg_DR'); + stateTest=isequal(valFBS,FBS.OutOp.evolcost) && isequal(valVMLMB,VMLMB.OutOp.evolcost) && isequal(valDR,DR.OutOp.evolcost); + message=['Max error between costs evolution : ',num2str(max([norm(valFBS-FBS.OutOp.evolcost),... + norm(valVMLMB-VMLMB.OutOp.evolcost),norm(valDR-DR.OutOp.evolcost)]))]; end end \ No newline at end of file diff --git a/Example/DeconvEx/2D/Deconv_LS_TV.m b/Example/DeconvEx/2D/Deconv_LS_TV.m index a3cc53a..176278b 100644 --- a/Example/DeconvEx/2D/Deconv_LS_TV.m +++ b/Example/DeconvEx/2D/Deconv_LS_TV.m @@ -51,7 +51,7 @@ % -- Chambolle-Pock LS + TV CP=OptiChambPock(lamb*R_N12,G,F); -CP.OutOp=OutputOpti(1,im,10); +CP.OutOp=OutputOptiSNR(1,im,10); CP.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4), 'StepRelative',1e-4); CP.tau=15; % algorithm parameters CP.sig=1/(CP.tau*G.norm^2)*0.99; % @@ -65,7 +65,7 @@ % Here no solver needed in ADMM since the operator H'*H + alpha*G'*G is invertible ADMM=OptiADMM(F,Fn,Hn,rho_n); ADMM.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4), 'StepRelative',1e-4); -ADMM.OutOp=OutputOpti(1,im,10); +ADMM.OutOp=OutputOptiSNR(1,im,10); ADMM.ItUpOut=2; % call OutputOpti update every ItUpOut iterations ADMM.maxiter=200; % max number of iterations ADMM.run(zeros(size(y))); % run the algorithm diff --git a/Example/DeconvEx/2D/Deconv_LS_TV_NonNeg.m b/Example/DeconvEx/2D/Deconv_LS_TV_NonNeg.m index 2787733..3d026ab 100644 --- a/Example/DeconvEx/2D/Deconv_LS_TV_NonNeg.m +++ b/Example/DeconvEx/2D/Deconv_LS_TV_NonNeg.m @@ -62,7 +62,7 @@ Hn={G,LinOpIdentity(sz)}; rho_n=[1e-1,1e-1]; ADMM=OptiADMM(F,Fn,Hn,rho_n); -ADMM.OutOp=OutputOpti(1,im,10,[1 2]); +ADMM.OutOp=OutputOptiSNR(1,im,10,[1 2]); % STOP when the sum successives C = F*x + Fn{1}*Hn{1}*x is lower than 1e-4 or when the distance between two successive step is lower than 1e-5 ADMM.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4,[1 2]), 'StepRelative',1e-4); ADMM.ItUpOut=2; % call OutputOpti update every ItUpOut iterations @@ -73,7 +73,7 @@ Fn={lamb*R_N12}; Hn={G}; PDC=OptiPrimalDualCondat(F,R_POS,Fn,Hn); -PDC.OutOp=OutputOpti(1,im,40,[1 3]); +PDC.OutOp=OutputOptiSNR(1,im,40,[1 3]); % STOP when the sum successives C = F*x + Fn*Hn*x is lower than 1e-4 or when the distance between two successive step is lower than 1e-5 PDC.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4,[1 3]) , 'StepRelative',1e-4); PDC.tau=1; % set algorithm parameters @@ -89,7 +89,7 @@ C = F+ lamb*hyperB; C.memoizeOpts.apply=true; VMLMB=OptiVMLMB(C,0.,[]); -VMLMB.OutOp=OutputOpti(1,im,10); +VMLMB.OutOp=OutputOptiSNR(1,im,10); VMLMB.CvOp=TestCvgCombine('CostRelative',1e-4, 'StepRelative',1e-4); % identical to VMLMB.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4),TestCvgStepRelative(1e-5)); VMLMB.ItUpOut=2; VMLMB.maxiter=200; % max number of iterations diff --git a/Example/DeconvEx/3D/DeconvScript.m b/Example/DeconvEx/3D/DeconvScript.m index c357920..ade59c1 100644 --- a/Example/DeconvEx/3D/DeconvScript.m +++ b/Example/DeconvEx/3D/DeconvScript.m @@ -93,7 +93,7 @@ rho_n=[1e-3,1e-3,1e-3]; % Multipliers rho_n ADMM=OptiADMM([],Fn,Hn,rho_n); end -ADMM.OutOp=OutputOpti(1,im,round(maxIt/10),[1 2]); +ADMM.OutOp=OutputOptiSNR(1,im,round(maxIt/10),[1 2]); ADMM.CvOp=TestCvgCombine(TestCvgCostRelative(1e-4), 'StepRelative',1e-4); ADMM.ItUpOut=round(maxIt/10); ADMM.maxiter=maxIt; diff --git a/LinOp/Deprecated/LinOpCpx.m b/LinOp/Deprecated/LinOpCpx.m index f7c3b7d..e331600 100644 --- a/LinOp/Deprecated/LinOpCpx.m +++ b/LinOp/Deprecated/LinOpCpx.m @@ -35,6 +35,7 @@ %% Constructor methods function this = LinOpCpx(sz) + warning('LinOpCpx Deprecated'); this.name ='LinOpCpx'; assert(issize(sz),'The input size sz should be a conformable to a size '); this.isInvertible = true; diff --git a/LinOp/LinOpBroadcast.m b/LinOp/LinOpBroadcast.m index 28ac004..d36e20c 100644 --- a/LinOp/LinOpBroadcast.m +++ b/LinOp/LinOpBroadcast.m @@ -16,6 +16,7 @@ %% Copyright (C) 2018 % F. Soulez ferreol.soulez@epfl.ch + % Anthony Berdeu (Laboratoire Hubert Curien) % % This program is free software: you can redistribute it and/or modify % it under the terms of the GNU General Public License as published by @@ -96,7 +97,7 @@ for n=this.index x = sum(x,n); end - y = squeeze(x); + y = reshape(x, this.sizein); end function y = applyHtH_(this,x) % Reimplemented from parent class :class:`LinOp`. diff --git a/LinOp/LinOpConv.m b/LinOp/LinOpConv.m index d30bf83..051604f 100644 --- a/LinOp/LinOpConv.m +++ b/LinOp/LinOpConv.m @@ -162,7 +162,7 @@ if centering for n=1:ndims(psf) if any(index==n) - psf = fftshift(psf,n); + psf = ifftshift(psf,n); end end diff --git a/LinOp/LinOpGrad.m b/LinOp/LinOpGrad.m index 1325594..6c6c715 100644 --- a/LinOp/LinOpGrad.m +++ b/LinOp/LinOpGrad.m @@ -10,8 +10,8 @@ % All attributes of parent class :class:`LinOp` are inherited. % % **Note** When circular boundary conditions are selected, the method - % makeHtH (or equivalently the composition H'*H) returns a convolution - % linear operator :class:`LinOp` + % makeHtH (or equivalently the composition ``H'*H``) returns a convolution + % linear operator :class:`LinOp` % % **Example** G = LinOpGrad(sz,index,bc,res) % @@ -210,7 +210,7 @@ % Reimplemented from parent class :class:`LinOp`. if strcmp(this.bc,'circular') fHtH=zeros_(this.sizein) ; - idxAll = repmat({1}, 1, this.ndms); + idxAll = repmat({1}, 1, max(this.ndms,2)); % The max(this.ndms,2) is to deal with the vectorial case (i.e. this.ndms==1) rep=this.sizein; sel=idxAll; for ii = this.index diff --git a/LinOp/LinOpHess.m b/LinOp/LinOpHess.m index aea9c22..96195a0 100644 --- a/LinOp/LinOpHess.m +++ b/LinOp/LinOpHess.m @@ -10,13 +10,15 @@ % the index vector. The last dimension of length lidx(lidx+1)/2 % represents the coefficients of upper-right part of the symmetric hessian % matrix ordered from top to bottom and left to right. For instance: - % - in 3D with index=[1 2 3], [d^2F/dxx;d^2F/dxy;d^2F/dxz;d^2F/dyy;d^2F/dyz;d^2F/dzz] - % - in 3D with index=[2 3], [d^2F/dyy;d^2F/dyz;d^2F/dzz] + % + % - in 3D with index=[1 2 3], [d^2F/dxx;d^2F/dxy;d^2F/dxz;d^2F/dyy;d^2F/dyz;d^2F/dzz] + % + % - in 3D with index=[2 3], [d^2F/dyy;d^2F/dyz;d^2F/dzz] % % All attributes of parent class :class:`LinOp` are inherited. % % **Note** When circular boundary conditions are selected, the method - % makeHtH (or equivalently the composition H'*H) returns a convolution + % makeHtH (or equivalently the composition ``H'*H``) returns a convolution % linear operator :class:`LinOp` % % **Example** H = LinOpHess(sz,bc,index) diff --git a/LinOp/LinOpMatrix.m b/LinOp/LinOpMatrix.m index 2904d1b..85d67c8 100644 --- a/LinOp/LinOpMatrix.m +++ b/LinOp/LinOpMatrix.m @@ -44,7 +44,11 @@ this.M = M; this.sz = size(M); this.ndms = ndims(M); - this.norm = norm(M); + if issparse(M) + this.norm = normest(M); + else + this.norm = norm(M); + end if nargin == 1 index = []; end diff --git a/LinOp/LinOpSum.m b/LinOp/LinOpSum.m index cd1432f..361a9ff 100644 --- a/LinOp/LinOpSum.m +++ b/LinOp/LinOpSum.m @@ -78,6 +78,8 @@ this.imdims = this.sizein; this.imdims(~T)=1; + this.norm=sqrt(this.sizein(this.index)); + end end diff --git a/LinOp/LinOpXRay.m b/LinOp/LinOpXRay.m new file mode 100644 index 0000000..47fd1c3 --- /dev/null +++ b/LinOp/LinOpXRay.m @@ -0,0 +1,117 @@ +classdef LinOpXRay < LinOp + % LinOpXray: 2D, parallel x-ray operator based on MATLAB's ``radon`` function. + % + % + % :param x: structure defining the reconstruction domain (always 2D) + % :param x.size: 1x2 vector giving dimensions of the domain in pixels, must be of the form ``N*ones(1,2)``. + % :param x.step: 1x2 vector giving the spacing of the pixels + % (in an apropirate unit of length, e.g. milimeteres). + % :param thetas: 1x(number of projections) vector giving the projection directions in radians. +% Specifically, ``[cos(theta) sin(theta)]`` points in the direction of the positive axis in the projection coordinate system. + % + % **Note** We take rows as $$x$$ and columns as $$y$$ in the reconstruction domain. + % **Note** The center of the reconstruction (and rotation axis) is assumed to be at ``floor((x.size+1)/2)``. + % + % **Note** The center of each projection is at ``ceil(sizeout/2)``. + % + % **Note** Be warned that the adjoint is only accurate to around 20 dB for + % for images of size 500x500 px. The error is smaller for larger + % images. + + %% Copyright (C) 2019 + % M. McCann michael.thompson.mccann@gmail.com +% This program is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program. If not, see . + + + properties + x + y + thetas + end + + methods + + function this = LinOpXRay(x, thetas) + this.name ='XRay'; + + % input checking for x + if length(unique(x.size))>1 + error('only square x domains are supported'); + end + + if length(x.size) ~= 2 + error('only 2d supported'); + end + + if length(unique(x.step))>1 + error('only isotropic x domains are supported'); + end + + this.x = x; + this.sizein = x.size; + + % setting output size + y.size = 2*ceil(norm(x.size-floor((x.size-1)/2)-1))+3; + this.sizeout= [y.size length(thetas)]; + + this.y = y; + this.thetas = thetas; + + % make copies of private image processing toolbox functions so + % that we can call them directly + if isempty(which('iradonmex')) + filename = which('iradonmex', '-all'); + privateDir = fullfile(fileparts(mfilename('fullpath')), 'private'); + if ~exist(privateDir, 'dir') + mkdir(privateDir) + end + copyfile(filename{1}, privateDir) + end + + % estimate norm, assuming shift invariance + d = zeros(x.size); + d(ceil(end/2), ceil(end/2)) = 1; + + HTHd = fft2(this.applyAdjoint_(this.apply_(d))); + + this.norm = sqrt(max(abs(HTHd(:)))); + + + end + + + end + + methods (Access = protected) + + function y=apply_(this,x) % apply the operator + y=radon(x,this.thetas/pi*180 - 90, this.y.size) * this.x.step(1); + end + + function g = applyAdjoint_(this,c) % apply the adjoint + xVects = (1:this.x.size(1)) - floor((this.x.size(1)+1)/2); + [X0, X1] = ndgrid(xVects); + g = iradonmex(size(X0,1), this.thetas - pi/2, X1./this.x.step(2), -X0./this.x.step(1), c, 1); % 1 - linear interp + g = g * this.x.step(1); + end + + function y = applyHtH_(this,x) % apply the HtH matrix + y = this.applyAdjoint(this.apply(x)); + end + + end + + +end + diff --git a/LinOp/SelectorLinOps/LinOpSelector.m b/LinOp/SelectorLinOps/LinOpSelector.m index 619e597..5089c4c 100644 --- a/LinOp/SelectorLinOps/LinOpSelector.m +++ b/LinOp/SelectorLinOps/LinOpSelector.m @@ -59,6 +59,7 @@ function y = apply_(this,x) % Reimplemented from parent class :class:`LinOpSelector`. y =x(this.sel{:}); + y = reshape(y, this.sizeout); end function y = applyAdjoint_(this,x) % Reimplemented from parent class :class:`LinOpSelector`. diff --git a/LinOp/SelectorLinOps/LinOpSelectorPatch.m b/LinOp/SelectorLinOps/LinOpSelectorPatch.m index 67b1854..f5588da 100644 --- a/LinOp/SelectorLinOps/LinOpSelectorPatch.m +++ b/LinOp/SelectorLinOps/LinOpSelectorPatch.m @@ -8,6 +8,7 @@ % :param sz: size of \\(\\mathrm{x}\\) on which the :class:`LinOpDownsample` applies. % :param idxmin: array containing the first kept index in each direction % :param idxmax: array containing the last kept index in each direction + % :param squeezeflag: true to squeeze singleton dimensions (default: false) % % All attributes of parent class :class:`LinOpSelector` are inherited. % @@ -18,6 +19,8 @@ %% Copyright (C) 2017 % E. Soubies emmanuel.soubies@epfl.ch + % F. Soulez + % A. Berdeu % % This program is free software: you can redistribute it and/or modify % it under the terms of the GNU General Public License as published by @@ -39,7 +42,7 @@ %% Constructor methods - function this = LinOpSelectorPatch(sz,idxmin,idxmax) + function this = LinOpSelectorPatch(sz,idxmin,idxmax,squeezeflag) this.name ='LinOp SelectorPatch'; assert(cmpSize(size(sz),size(idxmin)) && cmpSize(size(sz),size(idxmax)),'Parameters sz idxmin and idxmax must have the same size'); assert(~any(idxmin<=0) && ~any(idxmin>sz),'idxmin out of sz range'); @@ -48,7 +51,16 @@ this.idxmin=idxmin; this.idxmax=idxmax; this.sizeout=idxmax-idxmin+1; - if this.sizeout(end)==1, this.sizeout=this.sizeout(1:end-1);end; + % flag_squeeze + if (nargin>3)&& ~isscalar(squeezeflag ) + error('squeezeflag must be a single boolean'); + end + if (nargin>3) && squeezeflag + this.sizeout = this.sizeout(this.sizeout~=1); + end + if numel(this.sizeout)==1 + this.sizeout = [this.sizeout, 1]; + end this.sizein=sz; this.sel=cell(length(sz),1); for ii=1:length(sz) diff --git a/LinOp/Tests/testLinOpXray.m b/LinOp/Tests/testLinOpXray.m new file mode 100644 index 0000000..31ede72 --- /dev/null +++ b/LinOp/Tests/testLinOpXray.m @@ -0,0 +1,35 @@ +x.size = [125 125]; +x.step = [1 1]; + +numThetas = 100; +thetas = linspace(0, pi, numThetas+1); +thetas(end) = []; % we don't want to have both 0 and pi + +H = LinOpXRay(x, thetas); + +% make some arbitrary phantom +[X, Y] = ndgrid(1:x.size(1), 1:x.size(2)); +c = (X-50).^2 + (Y-50).^2 < 40^2; +c = c & (((X-40)/1.5).^2 + (Y-24).^2 > 10^2); +c = c & (((X-40)/1.5).^2 + (Y-60).^2 > 20^2); +c = c & ((X-60).^2 + (Y-35).^2 > 5^2); +c = c & ((X-77).^2 + ((Y-35)./1.5).^2 > 8^2); +c = c .* (sqrt( (X - x.size(1)).^2 + (Y- x.size(2)).^2 ) ); +c = reshape( double(c), x.size); + +%% + +g = H*c; +HTg = H'*g; + +%% +d = zeros(x.size); +d(ceil(end/2), ceil(end/2)) = 1; + +HTHd = H'*H*d; + + +%% +checkLinOp(H) + + diff --git a/NEWS.md b/NEWS.md index fa41a63..71f649c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,29 @@ +## v1.1.2 release notes +The v1.1.2 release does not contain any major changes but mainly incremental improvements and bug fixes. + +#### Improvements +- New `LinOpXRay` for 2D radon transform (Note that the adjoint is not really the adjoint, but only an approximation (up to about 20 dB), +- LinOp compatibility with sparse matrices, +- `Util/estimateNorm.m` for estimation of the norm of an operator, +- New `OutputOptiSNR` to print SNR during optimization, +- Better summation and multiplication of Map objects with scalars, +- Rename `OpEWSquareRoot` to` OpEWSqrt`, +- Add some kind of unitary tests, +- `CostHyperBolic`: Epsilon can be a vector, +- non scalar step size in `OptiFBS` + + +#### Bug fixes +Many bug fixes, among them: +- Generalize Compiler option for C/C++ files and reactivate openmp for the Schatten norm +- Better handling of singletons dimensions. + + + ## v1.1.1 release notes The v1.1.1 release does not contain any major changes but mainly incremental improvements and bug fixes. -### Improvements +#### Improvements - Sfft functions: faster implementation, work with any dimension, new padding option argument. - Merge LinOpDFT into LinOpSDFT. LinOpDFT remains now as an alias. - Remove code duplication in SelectorLinOps. diff --git a/NonLinOp/ElementWiseOp/OpEWSquareRoot.m b/NonLinOp/ElementWiseOp/OpEWSqrt.m similarity index 93% rename from NonLinOp/ElementWiseOp/OpEWSquareRoot.m rename to NonLinOp/ElementWiseOp/OpEWSqrt.m index a9d1591..67a1efd 100644 --- a/NonLinOp/ElementWiseOp/OpEWSquareRoot.m +++ b/NonLinOp/ElementWiseOp/OpEWSqrt.m @@ -1,4 +1,4 @@ -classdef OpEWSquareRoot < Map +classdef OpEWSqrt < Map % Element-wise square root operator: % $$[\\mathrm{H}(x)]_k = \\sqrt{(x_k)} $$ % @@ -28,8 +28,8 @@ %% Constructor methods - function this = OpEWSquareRoot(sz) - this.name ='OpEWSquareRoot'; + function this = OpEWSqrt(sz) + this.name ='OpEWSqrt'; this.sizein=sz; this.sizeout=sz; this.isDifferentiable=true; diff --git a/NonLinOp/Tests/TestCompositionEWop.m b/NonLinOp/Tests/TestCompositionEWop.m index d814ba2..ea90eb9 100644 --- a/NonLinOp/Tests/TestCompositionEWop.m +++ b/NonLinOp/Tests/TestCompositionEWop.m @@ -9,7 +9,7 @@ sq=OpEWSquaredMagnitude(G.sizeout); % Elem-wise square magnitude for numerator S=LinOpSum(G.sizeout,3); % To sum the two squared gradient components iv=OpEWInverse(sz); % Elem-wise inverse -sqr=OpEWSquareRoot(sz); % Elem-wise square root +sqr=OpEWSqrt(sz); % Elem-wise square root sq2=OpEWSquaredMagnitude(sz); % Elem-wise square magnitude for denominator (different size) SS=LinOpSum(sz); % Final sum of all pixels @@ -28,7 +28,7 @@ G=LinOpGrad(sz); % Gradient operator sq=OpEWSquaredMagnitude(G.sizeout); % Elem-wise square magnitude for numerator S=LinOpSum(G.sizeout,3); % To sum the two squared gradient components -sqr=OpEWSquareRoot(sz); % Elem-wise square root +sqr=OpEWSqrt(sz); % Elem-wise square root SS=LinOpSum(sz); % Final sum of all pixels op=SS*sqr*S*sq*G; % Combinaison of Maps diff --git a/Opti/OptiADMM.m b/Opti/OptiADMM.m index 00322c5..5fc2717 100644 --- a/Opti/OptiADMM.m +++ b/Opti/OptiADMM.m @@ -90,8 +90,10 @@ this.Hn=Hn; this.rho_n=rho_n; if ~isempty(F0) - assert(~isempty(solver) || isa(F0,'CostL2') || isa(F0,'CostL2Composition') || ... - ( isa(F0,'CostSummation') && all(cellfun(@(x) (isa(x,'CostL2Composition') || isa(x,'CostL2')), F0.mapsCell))), ... + assert(~isempty(solver) || isa(F0,'CostL2') || isa(F0,'CostL2Composition') || ... + (isa(F0,'CostMultiplication') && F0.isnum && (isa(F0.cost2,'CostL2') || isa(F0.cost2,'CostL2Composition'))) || ... + ( isa(F0,'CostSummation') && all(cellfun(@(x) (isa(x,'CostL2Composition') || isa(x,'CostL2') || ... + (isa(x,'CostMultiplication') && x.isnum && (isa(x.cost2,'CostL2') || isa(x.cost2,'CostL2Composition'))) ), F0.mapsCell))), ... 'when F0 is nonempty and is not CostL2 or CostL2Composition (or a sum of them), a solver must be given (see help)'); this.cost=F0 +Fn{1}*Hn{1}; else @@ -113,14 +115,30 @@ elseif isa(this.F0,'CostL2') this.A=this.A + LinOpDiag(this.A.sizein); this.b0=this.F0.y; + elseif isa(this.F0,'CostMultiplication') && F0.isnum + if isa(F0.cost2,'CostL2') + this.A=this.A + this.F0.cost1 * LinOpDiag(this.A.sizein); + this.b0=this.F0.cost1 * this.F0.cost2.y; + elseif isa(F0.cost2,'CostL2Composition') + this.A=this.A + this.F0.cost1 * this.F0.cost2.H2'*(this.F0.cost2.H1.W*this.F0.cost2.H2); + this.b0=this.F0.cost1 * this.F0.cost2.H2'*this.F0.cost2.H1.y; + else + error('If F0 is not a CostL2 / CostL2Composition / CostSummation of them, a solver is required in ADMM'); + end elseif isa(this.F0,'CostSummation') for n=1:length(this.F0.mapsCell) if isa(this.F0.mapsCell{n},'CostL2Composition') this.A=this.A + this.F0.alpha(n)*this.F0.mapsCell{n}.H2'*this.F0.mapsCell{n}.H2; this.b0=this.b0 + this.F0.alpha(n)*this.F0.mapsCell{n}.H2'*this.F0.mapsCell{n}.H1.y; - else + elseif isa(this.F0.mapsCell{n},'CostL2') this.A=this.A + this.F0.alpha(n)*LinOpDiag(this.A.sizein); this.b0=this.b0 + this.F0.alpha(n)*this.F0.mapsCell{n}.y; + elseif isa(this.F0.mapsCell{n}.cost2,'CostL2Composition') % If CostMultiplication with a scalar + this.A=this.A + this.F0.alpha(n)*this.F0.mapsCell{n}.cost1*this.F0.mapsCell{n}.cost2.H2'*this.F0.mapsCell{n}.cost2.H2; + this.b0=this.b0 + this.F0.alpha(n)*this.F0.mapsCell{n}.cost1*this.F0.mapsCell{n}.cost2.H2'*this.F0.mapsCell{n}.cost2.H1.y; + elseif isa(this.F0.mapsCell{n}.cost2,'CostL2') + this.A=this.A + this.F0.alpha(n)*this.F0.mapsCell{n}.cost1*LinOpDiag(this.A.sizein); + this.b0=this.b0 + this.F0.alpha(n)*this.F0.mapsCell{n}.cost1*this.F0.mapsCell{n}.cost2.y; end end else diff --git a/Opti/OptiDouglasRachford.m b/Opti/OptiDouglasRachford.m index 46665b7..9110034 100644 --- a/Opti/OptiDouglasRachford.m +++ b/Opti/OptiDouglasRachford.m @@ -31,37 +31,22 @@ % along with this program. If not, see . properties - nu = 1; - useL = 0; F1; F2; lambda gamma - L; end properties (SetAccess = protected,GetAccess = protected) - y; - Ly; + y; end methods function this = OptiDouglasRachford(F1, F2, L, gamma, lambda) + if isempty(L), L=LinOpIdentity(F2.sizein); end; % F1, F2 this.F1 = F1; - this.F2 = F2; - - % L - if exist('L', 'var') && ~isempty(L) - this.useL = 1; - r = randn(L.sizeout); - nu = r ./ L.HHt(r); - assert(std(nu(:)) <1e-6, 'LLt != nu I'); - this.nu = real(mean(nu(:))); - if this.nu==1 - this.useL = 2; - end - this.L=L; - end + this.F2 = F2*L; + this.cost=this.F1+this.F2; % gamma if numel(gamma)==1 @@ -84,18 +69,9 @@ function initialize(this,x0) function flag=doIteration(this) % Reimplementation from :class:`Opti`. - if this.useL - this.Ly = this.L*this.y; - if this.useL==2 - this.xopt = this.L.adjoint( this.F2.prox(this.Ly, this.gamma(2))); - else - this.xopt = this.y + (1./this.nu).* this.L.adjoint( this.F2.prox(this.Ly, this.nu.*this.gamma(2)) - this.Ly); - end - else - this.xopt = this.F2.prox(this.y, this.gamma(2)); - end - this.y = this.y + this.lambda .* ( this.F1.prox(2.*this.xopt- this.y,this.gamma(1)) - this.xopt); - flag=OPTI_NEXT_IT; + this.xopt = this.F2.applyProx(this.y, this.gamma(2)); + this.y = this.y + this.lambda .* ( this.F1.applyProx(2.*this.xopt- this.y,this.gamma(1)) - this.xopt); + flag=this.OPTI_NEXT_IT; end end end diff --git a/Opti/OptiFBS.m b/Opti/OptiFBS.m index d563d68..dbf968d 100644 --- a/Opti/OptiFBS.m +++ b/Opti/OptiFBS.m @@ -6,6 +6,7 @@ % :param G: a :class:`Cost` with an implementation of the :meth:`applyProx`. % :param gam: descent step % :param fista: boolean true if the accelerated version FISTA [3] is used (default false) + % :param momRestart: boolean true if the moment restart strategy is used [4] is used (default false) % % All attributes of parent class :class:`Opti` are inherited. % @@ -26,6 +27,8 @@ % [3] Amir Beck and Marc Teboulle, "A Fast Iterative Shrinkage-Thresholding Algorithm for Linear inverse Problems", % SIAM Journal on Imaging Science, vol 2, no. 1, pp 182-202 (2009) % + % [4] Brendan O'donoghue and Emmanuel Candès. 2015. Adaptive Restart for Accelerated Gradient Schemes. Found. Comput. Math. 15, 3 (June 2015), 715-732. + % % **Example** FBS=OptiFBS(F,G) % % See also :class:`Opti` :class:`OutputOpti` :class:`Cost` @@ -57,7 +60,7 @@ properties F; % Cost F G; % Cost G - + momRestart = false; % Donoho put grad gam=[]; % descent step fista=false; % FISTA option [3] @@ -93,15 +96,23 @@ function initialize(this,x0) % Reimplementation from :class:`Opti`. For details see [1-3]. if this.fista % if fista - this.xopt=this.G.applyProx(this.y - this.gam*this.F.applyGrad(this.y),this.gam); - told=this.tk; - this.tk=0.5*(1+sqrt(1+4*this.tk^2)); - this.y=this.xopt + this.alpha*(told-1)/this.tk*(this.xopt - this.xold); + this.xopt=this.G.applyProx(this.y - this.gam.*this.F.applyGrad(this.y),this.gam); + if this.momRestart && dot(this.y(:) - this.xopt(:),this.xopt(:) - this.xold(:)) > 0 + fprint('Restarting\n'); + this.y = this.xold; + this.tk = 1; + this.xopt=this.G.applyProx(this.y - this.gam.*this.F.applyGrad(this.y),this.gam); + else + told=this.tk; + this.tk=0.5*(1+sqrt(1+4*this.tk^2)); + this.y=this.xopt + this.alpha*(told-1)/this.tk*(this.xopt - this.xold); + end else - this.xopt=this.G.applyProx(this.xopt - this.gam*this.F.applyGrad(this.xopt),this.gam); + this.xopt=this.G.applyProx(this.xopt - this.gam.*this.F.applyGrad(this.xopt),this.gam); end - + flag=this.OPTI_NEXT_IT; - end + + end end end diff --git a/Opti/OptiFGP.m b/Opti/OptiFGP.m index a4f6c13..0033071 100644 --- a/Opti/OptiFGP.m +++ b/Opti/OptiFGP.m @@ -72,7 +72,7 @@ this.TV = TV; this.cost = this.F0 + this.TV; - this.gam = 1/8; + this.gam = 1/this.D.norm^2; end function setLambda(this,new_l) diff --git a/Opti/OptiPrimalDualCondat.m b/Opti/OptiPrimalDualCondat.m index 6c4d399..cd71008 100644 --- a/Opti/OptiPrimalDualCondat.m +++ b/Opti/OptiPrimalDualCondat.m @@ -63,9 +63,9 @@ end % Full public properties properties - tau; % parameter of the algorithm - sig; % parameter of the algorithm - rho; % parameter of the algorithm + tau; % parameter of the algorithm + sig; % parameter of the algorithm + rho=1.95; % parameter of the algorithm end methods @@ -125,11 +125,10 @@ function initialize(this,x0) % Update xopt this.xopt=this.rho*xtilde+(1-this.rho)*this.xopt; % Update ytilde and y - ytilde = cell(length(this.Hn),1); for n=1:length(this.Fn) - ytilde{n}=this.Fn{n}.applyProxFench(this.y{n}+this.sig*this.Hn{n}.apply(2*xtilde-this.xold),this.sig); - this.y{n}=this.rho*ytilde{n}+(1-this.rho)*this.y{n}; - end + ytilde=this.Fn{n}.applyProxFench(this.y{n}+this.sig*this.Hn{n}.apply(2*xtilde-this.xold),this.sig); + this.y{n}=this.rho*ytilde +(1-this.rho)*this.y{n}; + end flag=this.OPTI_NEXT_IT; end end diff --git a/Opti/OptiUtils/MatlabOptimPack/installOptimPack.m b/Opti/OptiUtils/MatlabOptimPack/installOptimPack.m index 764d43a..e6d12b5 100644 --- a/Opti/OptiUtils/MatlabOptimPack/installOptimPack.m +++ b/Opti/OptiUtils/MatlabOptimPack/installOptimPack.m @@ -1,6 +1,9 @@ -function installOptimPack() +function installOptimPack(options) %% installOptimPack function % Install OptimPackLegacy and its matlab API from https://github.com/emmt/OptimPackLegacy +% +% You can give as a parameter of this function the path to your GCC +% compiler. Ex: installOptimPack('/usr/bin/gcc-6') % Copyright (C) 2018 F. Soulez ferreol.soulez@epfl.ch % @@ -16,6 +19,10 @@ function installOptimPack() % % You should have received a copy of the GNU General Public License % along with this program. If not, see . +if nargin==0 + options=[]; +end + [mpath,~,~] = fileparts(which('installOptimPack')); disp('Installing OptimPackLegacy'); @@ -25,12 +32,12 @@ function installOptimPack() unzip('OptimPackLegacy'); matDir = 'OptimPackLegacy-master/matlab/'; srcDir = 'OptimPackLegacy-master/src/'; -MexOpt= ['-largeArrayDims ' '-DUSE_BLAS_LIB ' '-DNEW_MATLAB_BLAS ' '-DINT_64BITS ' '-largeArrayDims ' 'COMPFLAGS=''$COMPFLAGS -Wall -mtune=native -fomit-frame-pointer -O2 ''']; +MexOpt= [ '-largeArrayDims ' '-DUSE_BLAS_LIB ' '-DNEW_MATLAB_BLAS ' '-DINT_64BITS ' '-largeArrayDims ' ,options, ' CFLAGS=''-fPIC -Wall -mtune=native -fomit-frame-pointer -O2 ''' ]; CFiles = [srcDir,'opl_vmlmb.c ',srcDir,'opl_algebra.c ',srcDir,'opl_lnsrch.c ',srcDir,'opl_utils.c ','-IOptimPackLegacy-master/src/ ']; eval(['mex ',matDir,'m_opl_vmlmb_get_reason.c ', CFiles,MexOpt]); eval(['mex ',matDir,'m_opl_vmlmb_create.c ', CFiles,MexOpt]); eval(['mex ',matDir,'m_opl_vmlmb_iterate.c ', CFiles,MexOpt]); -eval(['mex ',matDir,'m_opl_vmlmb_restore.c ', CFiles,MexOpt]); +eval(['mex ',matDir,'m_opl_vmlmb_restore.c ', CFiles,MexOpt]); movefile('OptimPackLegacy-master/matlab/*.m'); delete('makeOptimPack.m'); delete('OptimPackLegacy.zip'); diff --git a/Opti/OptiVMLMB.m b/Opti/OptiVMLMB.m index c70c313..56d525b 100644 --- a/Opti/OptiVMLMB.m +++ b/Opti/OptiVMLMB.m @@ -62,19 +62,20 @@ sxtol=0.1; epsilon=0.01; % a small value, in the range [0,1), equals to the cosine of the maximum angle between the search direction and the anti-gradient. The BFGS recursion is restarted, whenever the search direction is not sufficiently "descending". delta=0.1; % DELTA is a small nonegative value used to compute a small initial step. + xmin=[]; + xmax=[]; + active; end properties (SetAccess = protected,GetAccess = public) nparam; task; - xmin=[]; - xmax=[]; neval; bounds =0; ws; end properties (SetAccess = protected,GetAccess = protected) nbeval; - active; + %active; grad; cc; end @@ -127,8 +128,8 @@ function initialize(this,x0) end - this.cc = this.cost.apply(this.xopt); - this.grad = this.cost.applyGrad(this.xopt); + this.cc = gather(this.cost.apply(this.xopt)); + this.grad = gather(real(this.cost.applyGrad(this.xopt))); this.nbeval=this.nbeval+1; @@ -139,7 +140,7 @@ function initialize(this,x0) % Computes next step: this.xopt(1) = this.xopt(1); %Warning : side effect on x0 if x=x0 (due to the fact that x is passed-by-reference in the mexfiles) - this.task = m_opl_vmlmb_iterate(this.ws,this.xopt,this.cc,this.grad,this.active); + this.task = m_opl_vmlmb_iterate(this.ws,gather(this.xopt),this.cc,this.grad,this.active); flag=this.OPTI_REDO_IT; if (this.task == this.OPL_TASK_FG) @@ -155,8 +156,8 @@ function initialize(this,x0) end - this.cc = this.cost.apply(this.xopt); - this.grad = this.cost.applyGrad(this.xopt); + this.cc = gather(this.cost.apply(this.xopt)); + this.grad = gather(real(this.cost.applyGrad(this.xopt))); this.nbeval=this.nbeval+1; @@ -185,12 +186,13 @@ function initialize(this,x0) case 3 this.active = int32( ( (this.xopt>this.xmin) + (this.grad<0) ).*( (this.xopt0) ) ); end + this.active = gather(this.active); else % Convergence, or error, or warning this.endingMessage = ['Convergence, or error, or warning : ',this.task,m_opl_vmlmb_get_reason(this.ws)]; flag=this.OPTI_STOP; - this.task = m_opl_vmlmb_restore(this.ws,this.xopt,this.cc,this.grad); + this.task = m_opl_vmlmb_restore(this.ws,gather(this.xopt),this.cc,this.grad); return; end diff --git a/Opti/OutputOpti/OutputOpti.m b/Opti/OutputOpti/OutputOpti.m index 09ba718..318ae97 100644 --- a/Opti/OutputOpti/OutputOpti.m +++ b/Opti/OutputOpti/OutputOpti.m @@ -17,15 +17,13 @@ % % :param name: name of the :class:`OutputOpti` % :param computecost: boolean, if true the cost function will be computed - % :param xtrue: ground truth to compute the error with the solution (if provided) % :param evolcost: array to save the evolution of the cost function - % :param evolsnr: array to save the evolution of the SNR % :param saveXopt: boolean (defaul true) to save the evolution of the optimized variable xopt. % :param evolxopt: cell saving the optimization variable xopt % :param iterVerb: message will be displayed every iterVerb iterations (must be a multiple of the :attr:`ItUpOut` parameter of classes :class:`Opti`) % :param costIndex: select a specific cost function among a sum in the case where the optimized cost function is a sum of cost functions % - % **Example** OutOpti=OutputOpti(computecost,xtrue,iterVerb) + % **Example** OutOpti=OutputOpti(computecost,iterVerb,costIndex) % % **Important** The update method should have an unique imput that is the :class:`Opti` object in order to % be generic for all Optimization routines. Hence the update method has access (in reading mode) @@ -52,79 +50,93 @@ properties (SetAccess = protected,GetAccess = public) name = 'OutputOpti'% name of the optimization algorithm evolcost; % array saving the evolution of the cost function - evolsnr; % array saving the evolution of the error with the groud truth evolxopt; % cell saving the optimization variable xopt iternum; % array saving the iteration number corresponding to evolcost, evolxopt and evolerr entries + count; % internal counter + + %% To be removed (deprecated) normXtrue; % Norm of the true signal (to compute snr) isgt=false; % Boolean true if Ground Truth is provided - count; % internal counter - xtrue; % Ground Thruth + evolsnr; % array saving the evolution of the error with the groud truth- + xtrue; % Ground Truth + snrOp=[]; % Map to which the coefficients must be applied to compute reconstructed signal + OptiSNR_ = false; + end properties computecost=false; % Boolean, if true the cost function will be computed iterVerb=0; % message will be displayed every iterVerb iterations costIndex=0; % index of the cost function - saveXopt=false; % save evolution of the optimized variable + saveXopt=false; % save evolution of the optimized variable end methods %% Constructor - function this=OutputOpti(computecost,xtrue,iterVerb,costIndex) + function this=OutputOpti(varargin)%(computecost,xtrue,iterVerb,costIndex,snrOp) if nargin>=1 + computecost = varargin{1}; if isscalar(computecost) computecost = (computecost ~= 0); - end - + end assert(islogical(computecost),'Parameter computecost must be logical'); this.computecost=computecost; end - if nargin>=2, this.xtrue=xtrue;end - if nargin>=3 - assert(isscalar(iterVerb) && iterVerb>=0,'Parameter iterVerb must be a positive integer'); + + + if nargin>=2 + if ~isscalar( varargin{2}) + warning('xtrue parameter OutputOpti is deprecated use OutputOptiSNR'); + this=this.OutputOptiSNR_(varargin{:}); + return + end + iterVerb = varargin{2}; + assert(isscalar(iterVerb) && iterVerb>=0 && mod(iterVerb,1)==0,'Parameter iterVerb must be a positive integer'); this.iterVerb=iterVerb; end - if nargin==4, this.costIndex=costIndex;end - if ~isempty(this.xtrue) - this.isgt=true; - this.xtrue=xtrue; - this.normXtrue=norm(this.xtrue(:)); + + if nargin>=3 + costIndex = varargin{3}; + assert(isnumeric(iterVerb) ,'CostIndex must be numeric'); + this.costIndex=costIndex; end + end %% Initialization function init(this) % Initialize the arrays and counters. this.count=1; this.evolcost=zeros_(1); - this.evolsnr=zeros_(1); this.iternum = []; - this.evolxopt = {}; + this.evolxopt = {}; end %% Update method function update(this,opti) % Computes SNR, cost and display evolution. - str=sprintf('Iter: %5i',opti.niter); - if this.computecost + str=sprintf('Iter: %5i',opti.niter); + if this.computecost cc=this.computeCost(opti); - str=sprintf('%s | Cost: %4.4e',str,cc); - this.evolcost(this.count)=cc; - end - if this.isgt + str=sprintf('%s | Cost: %4.4e',str,cc); + this.evolcost(this.count)=cc; + end + + if this.OptiSNR_ && this.isgt snr=this.computeSNR(opti); - str=sprintf('%s | SNR: %4.4e dB',str,snr); - this.evolsnr(this.count)=snr; + str=sprintf('%s | SNR: %4.4e dB',str,snr); + this.evolsnr(this.count)=snr; end + if this.saveXopt this.evolxopt{this.count}=opti.xopt; end - this.iternum(this.count)=opti.niter; - this.count=this.count+1; - if opti.verbose && (opti.niter~=0 && (mod(opti.niter,this.iterVerb)==0) || (opti.niter==1 && this.iterVerb~=0)), - disp(str); - end + this.iternum(this.count)=opti.niter; + this.count=this.count+1; + if opti.verbose && (opti.niter~=0 && (mod(opti.niter,this.iterVerb)==0) || (opti.niter==1 && this.iterVerb~=0)) + disp(str); + end end function cc=computeCost(this,opti) % Evaluate the cost function at the current iterate xopt of - % the given :class:`Opti` opti object + % the given :class:`Opti` opti object if (any(this.costIndex>0) && isa(opti.cost,'CostSummation')) cc = 0; for n=1:numel(this.costIndex) @@ -134,10 +146,45 @@ function update(this,opti) cc = opti.cost*opti.xopt; end end + %% Deprecated functions + function this= OutputOptiSNR_(this,computecost,xtrue,iterVerb,costIndex,snrOp) + this.OptiSNR_=true; + this.name = 'OutputOptiSNR';% name + + if isscalar(computecost) + computecost = (computecost ~= 0); + end + + assert(islogical(computecost),'Parameter computecost must be logical'); + this.computecost=computecost; + + this.xtrue=xtrue; + + if nargin>=4 + assert(isscalar(iterVerb) && iterVerb>=0,'Parameter iterVerb must be a positive integer'); + this.iterVerb=iterVerb; + end + if nargin==5, this.costIndex=costIndex;end + if ~isempty(this.xtrue) + this.isgt=true; + this.xtrue=xtrue; + this.normXtrue=norm(this.xtrue(:)); + end + if nargin==6, this.snrOp = snrOp;end + if ~isempty(this.snrOp) + assert(~isempty(this.xtrue), 'Ground truth must be provided in order to compute SNR'); + assert(isa(this.snrOp, 'Map') && isequal(this.snrOp.sizeout, size(this.xtrue)), ... + 'The SNR operator must have the same output size as the ground truth'); + elseif ~isempty(this.xtrue) + this.snrOp = LinOpDiag(size(this.xtrue)); % Identity operator by default + end + end + function snr=computeSNR(this,opti) % Evaluate the snr for the current iterate xopt of - % the given :class:`Opti` opti object - snr=20*log10(this.normXtrue/norm(this.xtrue(:)-opti.xopt(:))); + % the given :class:`Opti` opti object + reconstruction = this.snrOp.apply(opti.xopt); + snr=20*log10(this.normXtrue/norm(this.xtrue(:)-reconstruction(:))); end end end diff --git a/Opti/OutputOpti/OutputOptiConjGrad.m b/Opti/OutputOpti/OutputOptiConjGrad.m index be061bf..1056bc2 100644 --- a/Opti/OutputOpti/OutputOptiConjGrad.m +++ b/Opti/OutputOpti/OutputOptiConjGrad.m @@ -13,7 +13,9 @@ % :param xtrue: ground truth to compute the error with the solution (if provided) % :param iterVerb: message will be displayed every iterVerb iterations (must be a multiple of the :attr:`ItUpOut` parameter of classes :class:`Opti`) % :param ytWy: weighted norm of $y$ : $ \\mathrm{ytWy} = \\mathrm{ y^T\\,W\\,y}$ + % % See also :class:`OptiConjGrad` :class:`OutputOpti` + % %% Copyright (C) 2018 % F. Soulez ferreol.soulez@epfl.ch @@ -37,25 +39,10 @@ methods function this=OutputOptiConjGrad(computecost,yty,xtrue,iterVerb) + this@OutputOpti(computecost,xtrue,iterVerb); if nargin>=1 - if isscalar(computecost) - computecost = (computecost ~= 0); - end - - assert(islogical(computecost),'Parameter computecost must be logical'); - this.computecost=computecost; this.ytWy = 0.5.*yty; - end - if nargin>=3, this.xtrue=xtrue;end - if nargin>=4 - assert(isscalar(iterVerb) && iterVerb>=0,'Parameter iterVerb must be a positive integer'); - this.iterVerb=iterVerb; - end - if ~isempty(this.xtrue) - this.isgt=true; - this.xtrue=xtrue; - this.normXtrue=norm(this.xtrue(:)); - end + end end function cc=computeCost(this,opti) % Evaluate the cost function at the current iterate xopt of diff --git a/Opti/OutputOpti/OutputOptiSNR.m b/Opti/OutputOpti/OutputOptiSNR.m new file mode 100644 index 0000000..9bb428a --- /dev/null +++ b/Opti/OutputOpti/OutputOptiSNR.m @@ -0,0 +1,145 @@ +classdef OutputOptiSNR < OutputOpti + % OutputOptiSNR class for algorithms displayings and savings + % + % At each :attr:`ItUpOut` iterations of an optimization algorithm (see :class:`Opti` generic class), + % the update method of an :class:`OutputOptiSNR` object will be executed in order to acheive user + % defined computations, e.g., + % + % - compute cost / SNR + % - store current iterate / cost value + % - plot/display stuffs + % + % The present generic class implements a basic update method that: + % + % - display the iteration number + % - computes & display thhandlee cost (if activated) + % - computes & display the SNR if ground truth is provided + % + % :param name: name of the :class:`OutputOptiSNR` + % :param computecost: boolean, if true the cost function will be computed + % :param xtrue: ground truth to compute the error with the solution (if provided) + % :param evolcost: array to save the evolution of the cost function + % :param evolsnr: array to save the evolution of the SNR + % :param saveXopt: boolean (defaul true) to save the evolution of the optimized variable xopt. + % :param evolxopt: cell saving the optimization variable xopt + % :param iterVerb: message will be displayed every iterVerb iterations (must be a multiple of the :attr:`ItUpOut` parameter of classes :class:`Opti`) + % :param costIndex: select a specific cost function among a sum in the case where the optimized cost function is a sum of cost functions + % + % **Example** OutOpti=OutputOptiSNR(computecost,xtrue,iterVerb) + % + % **Important** The update method should have an unique imput that is the :class:`Opti` object in order to + % be generic for all Optimization routines. Hence the update method has access (in reading mode) + % to all the properties of :class:`Opti` objects. + % + % See also :class:`Opti` + + %% Copyright (C) 2017 + % E. Soubies emmanuel.soubies@epfl.ch + % + % This program is free software: you can redistribute it and/or modify + % it under the terms of the GNU General Public License as published by + % the Free Software Foundation, either version 3 of the License, or + % (at your option) any later version. + % + % This program is distributed in the hope that it will be useful, + % but WITHOUT ANY WARRANTY; without even the implied warranty of + % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + % GNU General Public License for more details. + % + % You should have received a copy of the GNU General Public License + % along with this program. If not, see . + + properties (SetAccess = protected,GetAccess = public) + % evolsnr; % array saving the evolution of the error with the groud truth + % xtrue; % Ground Truth + % snrOp=[]; % Map to which the coefficients must be applied to compute reconstructed signal + % normXtrue; % Norm of the true signal (to compute snr) + end + + methods + %% Constructor + function this=OutputOptiSNR (computecost,xtrue,iterVerb,costIndex,snrOp) + + this.name = 'OutputOptiSNR';% name + + if isscalar(computecost) + computecost = (computecost ~= 0); + end + + assert(islogical(computecost),'Parameter computecost must be logical'); + this.computecost=computecost; + + this.xtrue=xtrue; + + if nargin>=3 + assert(isscalar(iterVerb) && iterVerb>=0,'Parameter iterVerb must be a positive integer'); + this.iterVerb=iterVerb; + end + if nargin==4, this.costIndex=costIndex;end + if ~isempty(this.xtrue) + this.xtrue=xtrue; + this.normXtrue=norm(this.xtrue(:)); + end + if nargin==5, this.snrOp = snrOp;end + if ~isempty(this.snrOp) + assert(~isempty(this.xtrue), 'Ground truth must be provided in order to compute SNR'); + assert(isa(this.snrOp, 'Map') && isequal(this.snrOp.sizeout, size(this.xtrue)), ... + 'The SNR operator must have the same output size as the ground truth'); + elseif ~isempty(this.xtrue) + this.snrOp = LinOpDiag(size(this.xtrue)); % Identity operator by default + end + + + end + %% Initialization + function init(this) + % Initialize the arrays and counters. + this.count=1; + this.evolcost=zeros_(1); + this.evolsnr=zeros_(1); + this.iternum = []; + this.evolxopt = {}; + end + %% Update method + function update(this,opti) + % Computes SNR, cost and display evolution. + str=sprintf('Iter: %5i',opti.niter); + if this.computecost + cc=this.computeCost(opti); + str=sprintf('%s | Cost: %4.4e',str,cc); + this.evolcost(this.count)=cc; + end + + snr=this.computeSNR(opti); + str=sprintf('%s | SNR: %4.4e dB',str,snr); + this.evolsnr(this.count)=snr; + + if this.saveXopt + this.evolxopt{this.count}=opti.xopt; + end + this.iternum(this.count)=opti.niter; + this.count=this.count+1; + if opti.verbose && (opti.niter~=0 && (mod(opti.niter,this.iterVerb)==0) || (opti.niter==1 && this.iterVerb~=0)) + disp(str); + end + end + function cc=computeCost(this,opti) + % Evaluate the cost function at the current iterate xopt of + % the given :class:`Opti` opti object + if (any(this.costIndex>0) && isa(opti.cost,'CostSummation')) + cc = 0; + for n=1:numel(this.costIndex) + cc = cc+opti.cost.mapsCell{this.costIndex(n)}*opti.xopt; + end + else + cc = opti.cost*opti.xopt; + end + end + function snr=computeSNR(this,opti) + % Evaluate the snr for the current iterate xopt of + % the given :class:`Opti` opti object + reconstruction = this.snrOp.apply(opti.xopt); + snr=20*log10(this.normXtrue/norm(this.xtrue(:)-reconstruction(:))); + end + end +end diff --git a/Opti/TestCvg/TestCvgADMM.m b/Opti/TestCvg/TestCvgADMM.m index 91929c8..f222274 100644 --- a/Opti/TestCvg/TestCvgADMM.m +++ b/Opti/TestCvg/TestCvgADMM.m @@ -37,6 +37,11 @@ eps_abs; % Termination criterion tolerances. eps_rel; end + properties + evolResPrim + evolResDual + count=0; + end methods %% Constructor @@ -52,7 +57,7 @@ %% Update method function stop = testConvergence(this,opti) % Reimplemented from parent class :class:`TestCvg`. - + this.count=this.count+1; stop = false; p = 0; % Number of constraints @@ -63,15 +68,19 @@ adjHnwn_norm = 0; % ||H'w|| for n = 1:length(opti.wn) p = p + length(opti.yn{n}); - r_norm = r_norm + norm(opti.Hnx{n}-opti.yn{n})^2; - s_norm = s_norm + norm(opti.rho_n(n)*opti.Hn{n}.applyAdjoint(opti.yn{n} - opti.yold{n}))^2; - Hnx_norm = Hnx_norm + norm(opti.Hnx{n})^2; - y_norm = y_norm + norm(opti.yn{n})^2; - adjHnwn_norm = adjHnwn_norm + norm(opti.Hn{n}.applyAdjoint(opti.wn{n}))^2; + r_norm = r_norm + norm(opti.Hnx{n}(:)-opti.yn{n}(:))^2; + tmp=opti.rho_n(n)*opti.Hn{n}.applyAdjoint(opti.yn{n} - opti.yold{n}); + s_norm = s_norm + norm(tmp(:))^2; + Hnx_norm = Hnx_norm + norm(opti.Hnx{n}(:))^2; + y_norm = y_norm + norm(opti.yn{n}(:))^2; + tmp=opti.Hn{n}.applyAdjoint(opti.wn{n}); + adjHnwn_norm = adjHnwn_norm + norm(tmp(:))^2; end eps_primal = sqrt(p)*this.eps_abs + this.eps_rel*sqrt(max(Hnx_norm, y_norm)); eps_dual = sqrt(length(opti.xopt))*this.eps_abs + this.eps_rel*sqrt(adjHnwn_norm); + this.evolResPrim(this.count)=r_norm; + this.evolResDual(this.count)=s_norm; if (sqrt(r_norm) <= eps_primal) && (sqrt(s_norm) <= eps_dual) stop = true; endingMessage = [this.name,' convergence reached']; diff --git a/Opti/TestCvg/TestCvgCostRelative.m b/Opti/TestCvg/TestCvgCostRelative.m index ef986a9..fbc6254 100644 --- a/Opti/TestCvg/TestCvgCostRelative.m +++ b/Opti/TestCvg/TestCvgCostRelative.m @@ -47,7 +47,7 @@ % Tests algorithm convergence from the relative difference between two successive value of the cost function % % :return: boolean true if - % $$ \\frac{\\left| C(\\mathrm{x}^{k}) - C(\\mathrm{x}^{k-1})\\right|}{\\left|C(\\mathrm{x}^{k-1})\\right|} < \\mathrm{costRelativeTol}$$ + % $$ \\frac{\\left| C(\\mathrm{x}^{k}) - C(\\mathrm{x}^{k-1})\\right|}{\\left|C(\\mathrm{x}^{k-1})\\right|} < \\mathrm{costRelativeTol}$$ stop = false; diff --git a/Opti/TestCvg/TestCvgMaxSnr.m b/Opti/TestCvg/TestCvgMaxSnr.m index 63d6e12..1e910e7 100644 --- a/Opti/TestCvg/TestCvgMaxSnr.m +++ b/Opti/TestCvg/TestCvgMaxSnr.m @@ -43,7 +43,7 @@ % Tests algorithm convergence using the SNR with % % :return: boolean true if - % $$ 20\\log\\left(\\frac{\\| \\mathrm{ref}\\|}{\\| \\mathrm{x}^{k} - \\mathrm{ref}\\|}\\right)< 20\\log\\left(\\frac{\\| \\mathrm{ref}\\|}{\\| \\mathrm{x}^{k-1} - \\mathrm{ref}\\|}\\right)$$ + % $$ 20\\log\\left(\\frac{\\| \\mathrm{ref}\\|}{\\| \\mathrm{x}^{k} - \\mathrm{ref}\\|}\\right)< 20\\log\\left(\\frac{\\| \\mathrm{ref}\\|}{\\| \\mathrm{x}^{k-1} - \\mathrm{ref}\\|}\\right)$$ stop = false; assert(checkSize(this.sizeRef, size(opti.xopt)), ' Reference signal and x should be conformable'); diff --git a/Opti/TestCvg/TestCvgStepRelative.m b/Opti/TestCvg/TestCvgStepRelative.m index 4c81450..e45c363 100644 --- a/Opti/TestCvg/TestCvgStepRelative.m +++ b/Opti/TestCvg/TestCvgStepRelative.m @@ -39,7 +39,7 @@ % Tests algorithm convergence from the relative difference between two successive value of the step function % % :return: boolean true if - % $$ \\frac{\\| \\mathrm{x}^{k} - \\mathrm{x}^{k-1}\\|}{\\|\\mathrm{x}^{k-1}\\|} < \\text{stepRelativeTol}.$$ + % $$ \\frac{\\| \\mathrm{x}^{k} - \\mathrm{x}^{k-1}\\|}{\\|\\mathrm{x}^{k-1}\\|} < \\text{stepRelativeTol}.$$ stop = false; if ~isempty(opti.xold) diff --git a/README.md b/README.md index 41672cf..f89ea1a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This Matlab toolbox provides a set of tools (operators, cost functions, optimiza ## Getting started -Download or clone this repository and run the script *setMatlabPath.m* which will update your Matlab path with the library. +Download or clone this repository and run the script *setGlobalBioImPath.m* which will update your Matlab path with the library. ## Documentation @@ -15,12 +15,11 @@ A detailled documentation can be found