From e23a45a61a8f185efe564088daea45de714d94ac Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 16 Apr 2019 19:04:46 +0100 Subject: fix not memopt case --- .../Python/ccpi/optimisation/algorithms/PDHG.py | 48 ++++++++--------- .../ccpi/optimisation/functions/KullbackLeibler.py | 60 +++++++++++++++++----- 2 files changed, 71 insertions(+), 37 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py index d1b5351..a165e55 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py @@ -152,28 +152,28 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): if not memopt: - y_old += sigma * operator.direct(xbar) - y = f.proximal_conjugate(y_old, sigma) - - x_old -= tau*operator.adjoint(y) - x = g.proximal(x_old, tau) - + y_tmp = y_old + sigma * operator.direct(xbar) + y = f.proximal_conjugate(y_tmp, sigma) + + x_tmp = x_old - tau*operator.adjoint(y) + x = g.proximal(x_tmp, tau) + x.subtract(x_old, out=xbar) xbar *= theta xbar += x - + x_old.fill(x) - y_old.fill(y) + y_old.fill(y) -# if i%100==0: -# -# p1 = f(operator.direct(x)) + g(x) -# d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) -# primal.append(p1) -# dual.append(d1) -# pdgap.append(p1-d1) -# print(p1, d1, p1-d1) + if i%100==0: + + p1 = f(operator.direct(x)) + g(x) + d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) + primal.append(p1) + dual.append(d1) + pdgap.append(p1-d1) + print(p1, d1, p1-d1) else: @@ -196,14 +196,14 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): x_old.fill(x) y_old.fill(y) -# if i%100==0: -# -# p1 = f(operator.direct(x)) + g(x) -# d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) -# primal.append(p1) -# dual.append(d1) -# pdgap.append(p1-d1) -# print(p1, d1, p1-d1) + if i%100==0: + + p1 = f(operator.direct(x)) + g(x) + d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) + primal.append(p1) + dual.append(d1) + pdgap.append(p1-d1) + print(p1, d1, p1-d1) t_end = time.time() diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index 18af154..ae25bdb 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -19,23 +19,29 @@ import numpy from ccpi.optimisation.functions import Function -from ccpi.optimisation.functions.ScaledFunction import ScaledFunction -from ccpi.framework import DataContainer, ImageData, ImageGeometry +from ccpi.optimisation.functions.ScaledFunction import ScaledFunction class KullbackLeibler(Function): - def __init__(self,data,**kwargs): + ''' Assume that data > 0 + + ''' + + def __init__(self,data, **kwargs): super(KullbackLeibler, self).__init__() self.b = data self.bnoise = kwargs.get('bnoise', 0) + + def __call__(self, x): + + # TODO check + self.sum_value = self.b + self.bnoise if (self.sum_value.as_array()<0).any(): self.sum_value = numpy.inf - - def __call__(self, x): if self.sum_value==numpy.inf: return numpy.inf @@ -43,22 +49,50 @@ class KullbackLeibler(Function): return numpy.sum( x.as_array() - self.b.as_array() * numpy.log(self.sum_value.as_array())) - def gradient(self, x): + def gradient(self, x, out=None): #TODO Division check - return 1 - self.b/(x + self.bnoise) + if out is None: + return 1 - self.b/(x + self.bnoise) + else: + self.b.divide(x+self.bnoise, out=out) + out.subtract(1, out=out) - def convex_conjugate(self, x, out=None): - pass + def convex_conjugate(self, x): + + return self.b * ( numpy.log(self.b/(1-x)) - 1 ) - self.bnoise * (x - 1) def proximal(self, x, tau, out=None): - z = x + tau * self.bnoise - return (z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt() - + if out is None: + return 0.5 *( (x - self.bnoise - tau) + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() ) + else: + tmp = 0.5 *( (x - self.bnoise - tau) + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() ) + out.fill(tmp) + def proximal_conjugate(self, x, tau, out=None): - pass + + + if out is None: + z = x + tau * self.bnoise + return 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) + else: + z = x + tau * self.bnoise + res = 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) + out.fill(res) + + + + def __rmul__(self, scalar): + + ''' Multiplication of L2NormSquared with a scalar + + Returns: ScaledFunction + + ''' + + return ScaledFunction(self, scalar) -- cgit v1.2.3 From 48cffabc0879f9e61d932b654e497382ebfaa995 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 16 Apr 2019 19:05:10 +0100 Subject: fix KL methods --- .../Python/ccpi/optimisation/functions/KullbackLeibler.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index ae25bdb..40dddd7 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -20,6 +20,7 @@ import numpy from ccpi.optimisation.functions import Function from ccpi.optimisation.functions.ScaledFunction import ScaledFunction +from ccpi.framework import ImageData class KullbackLeibler(Function): @@ -39,14 +40,17 @@ class KullbackLeibler(Function): # TODO check - self.sum_value = self.b + self.bnoise + self.sum_value = x + self.bnoise if (self.sum_value.as_array()<0).any(): self.sum_value = numpy.inf if self.sum_value==numpy.inf: return numpy.inf else: - return numpy.sum( x.as_array() - self.b.as_array() * numpy.log(self.sum_value.as_array())) + tmp = self.sum_value.as_array() + return (x - self.b * ImageData( numpy.log(tmp))).sum() + +# return numpy.sum( x.as_array() - self.b.as_array() * numpy.log(self.sum_value.as_array())) def gradient(self, x, out=None): @@ -60,7 +64,10 @@ class KullbackLeibler(Function): def convex_conjugate(self, x): - return self.b * ( numpy.log(self.b/(1-x)) - 1 ) - self.bnoise * (x - 1) + tmp = self.b.as_array()/( 1 - x.as_array() ) + + return (self.b * ( ImageData( numpy.log(tmp) ) - 1 ) - self.bnoise * (x - 1)).sum() +# return self.b * ( ImageData(numpy.log(self.b/(1-x)) - 1 )) - self.bnoise * (x - 1) def proximal(self, x, tau, out=None): -- cgit v1.2.3 From 92a2dfaa652211d5472055c86cc90dfd02e1e400 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 16 Apr 2019 19:05:37 +0100 Subject: add demos --- .../ccpi/optimisation/functions/L2NormSquared.py | 12 ++-- .../ccpi/optimisation/functions/MixedL21Norm.py | 3 +- Wrappers/Python/wip/pdhg_TV_denoising.py | 6 +- Wrappers/Python/wip/pdhg_tv_denoising_poisson.py | 71 ++++++++++++++-------- 4 files changed, 58 insertions(+), 34 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py index 1946d67..6d3bf86 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py +++ b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py @@ -93,12 +93,12 @@ class L2NormSquared(Function): if self.b is None: return x/(1+2*tau) else: - tmp = x - tmp -= self.b - tmp /= (1+2*tau) - tmp += self.b - return tmp -# return (x-self.b)/(1+2*tau) + self.b +# tmp = x +# tmp -= self.b +# tmp /= (1+2*tau) +# tmp += self.b +# return tmp + return (x-self.b)/(1+2*tau) + self.b # if self.b is not None: # out=x diff --git a/Wrappers/Python/ccpi/optimisation/functions/MixedL21Norm.py b/Wrappers/Python/ccpi/optimisation/functions/MixedL21Norm.py index 7e6b6e7..2004e5f 100755 --- a/Wrappers/Python/ccpi/optimisation/functions/MixedL21Norm.py +++ b/Wrappers/Python/ccpi/optimisation/functions/MixedL21Norm.py @@ -96,7 +96,8 @@ class MixedL21Norm(Function): tmp = [ el*el for el in x.containers] res = sum(tmp).sqrt().maximum(1.0) frac = [el/res for el in x.containers] - return BlockDataContainer(*frac) + return BlockDataContainer(*frac) + #TODO this is slow, why??? # return x.divide(x.pnorm().maximum(1.0)) diff --git a/Wrappers/Python/wip/pdhg_TV_denoising.py b/Wrappers/Python/wip/pdhg_TV_denoising.py index a5cd1bf..e6d38e9 100755 --- a/Wrappers/Python/wip/pdhg_TV_denoising.py +++ b/Wrappers/Python/wip/pdhg_TV_denoising.py @@ -20,10 +20,10 @@ from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ from skimage.util import random_noise from timeit import default_timer as timer -def dt(steps): - return steps[-1] - steps[-2] +#def dt(steps): +# return steps[-1] - steps[-2] -# Create phantom for TV denoising +# Create phantom for TV Gaussian denoising N = 100 diff --git a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py index 9fad6f8..fbe0d9b 100644 --- a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py +++ b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py @@ -6,24 +6,25 @@ Created on Fri Feb 22 14:53:03 2019 @author: evangelos """ -from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer - import numpy as np import matplotlib.pyplot as plt +from ccpi.framework import ImageData, ImageGeometry + from ccpi.optimisation.algorithms import PDHG, PDHG_old from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFun, KullbackLeibler, \ - MixedL21Norm, FunctionOperatorComposition, BlockFunction +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction from skimage.util import random_noise +from timeit import default_timer as timer # ############################################################################ -# Create phantom for TV denoising +# Create phantom for TV Poisson denoising N = 100 data = np.zeros((N,N)) @@ -41,13 +42,11 @@ plt.imshow(noisy_data.as_array()) plt.colorbar() plt.show() -#%% - # Regularisation Parameter -alpha = 10 +alpha = 2 #method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") -method = '1' +method = '0' if method == '0': # Create operators @@ -57,15 +56,11 @@ if method == '0': # Form Composite Operator operator = BlockOperator(op1, op2, shape=(2,1) ) - #### Create functions -# f = FunctionComposition_new(operator, mixed_L12Norm(alpha), \ -# L2NormSq(0.5, b = noisy_data) ) - f1 = alpha * MixedL21Norm() - f2 = KullbackLeibler(b = noisy_data) + f2 = KullbackLeibler(noisy_data) f = BlockFunction(f1, f2 ) - g = ZeroFun() + g = ZeroFunction() else: @@ -73,29 +68,57 @@ else: # No Composite # ########################################################################### operator = Gradient(ig) - f = alpha * FunctionOperatorComposition(operator, MixedL21Norm()) + f = alpha * MixedL21Norm() g = KullbackLeibler(noisy_data) ########################################################################### #%% # Compute operator Norm normK = operator.norm() -print ("normK", normK) -# Primal & dual stepsizes -#sigma = 1 -#tau = 1/(sigma*normK**2) -sigma = 1/normK -tau = 1/normK +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) opt = {'niter':2000} +opt1 = {'niter':2000, 'memopt': True} +t1 = timer() res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) - -plt.figure(figsize=(5,5)) +t2 = timer() + +print(" Run memopt") + +t3 = timer() +res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) +t4 = timer() + +#%% +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) plt.imshow(res.as_array()) +plt.title('no memopt') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(res1.as_array()) +plt.title('memopt') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow((res1 - res).abs().as_array()) +plt.title('diff') plt.colorbar() plt.show() +# +plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'memopt') +plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'no memopt') +plt.legend() +plt.show() + +print ("Time: No memopt in {}s, \n Time: Memopt in {}s ".format(t2-t1, t4 -t3)) +diff = (res1 - res).abs().as_array().max() + +print(" Max of abs difference is {}".format(diff)) + #pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) #pdhg.max_iteration = 2000 -- cgit v1.2.3 From 3e0b5a62f3264c3c043c0b219a86b56aadd0d61e Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 17 Apr 2019 11:53:35 +0100 Subject: wip tests and demos --- .../Python/ccpi/optimisation/algorithms/PDHG.py | 35 +++++------ .../ccpi/optimisation/functions/KullbackLeibler.py | 59 +++++++++++------- Wrappers/Python/wip/pdhg_TV_denoising.py | 69 ++++++++++++++++++++-- Wrappers/Python/wip/pdhg_tv_denoising_poisson.py | 66 ++++++++++++++++++--- 4 files changed, 178 insertions(+), 51 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py index a165e55..bc080f8 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py @@ -166,15 +166,15 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): y_old.fill(y) - if i%100==0: - - p1 = f(operator.direct(x)) + g(x) - d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) - primal.append(p1) - dual.append(d1) - pdgap.append(p1-d1) - print(p1, d1, p1-d1) - +# if i%10==0: +## +# p1 = f(operator.direct(x)) + g(x) +## print(p1) +# d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) +# primal.append(p1) +# dual.append(d1) +# pdgap.append(p1-d1) +# print(p1, d1, p1-d1) else: @@ -196,14 +196,15 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): x_old.fill(x) y_old.fill(y) - if i%100==0: - - p1 = f(operator.direct(x)) + g(x) - d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) - primal.append(p1) - dual.append(d1) - pdgap.append(p1-d1) - print(p1, d1, p1-d1) +# if i%10==0: +## +# p1 = f(operator.direct(x)) + g(x) +## print(p1) +# d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) +# primal.append(p1) +# dual.append(d1) +# pdgap.append(p1-d1) +# print(p1, d1, p1-d1) t_end = time.time() diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index 40dddd7..7889cad 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -39,19 +39,14 @@ class KullbackLeibler(Function): def __call__(self, x): # TODO check - - self.sum_value = x + self.bnoise - if (self.sum_value.as_array()<0).any(): - self.sum_value = numpy.inf - - if self.sum_value==numpy.inf: - return numpy.inf - else: - tmp = self.sum_value.as_array() - return (x - self.b * ImageData( numpy.log(tmp))).sum() - -# return numpy.sum( x.as_array() - self.b.as_array() * numpy.log(self.sum_value.as_array())) + + tmp = x + self.bnoise + ind = tmp.as_array()>0 + res = x.as_array()[ind] - self.b.as_array()[ind] * numpy.log(tmp.as_array()[ind]) + + return sum(res) + def gradient(self, x, out=None): @@ -64,10 +59,12 @@ class KullbackLeibler(Function): def convex_conjugate(self, x): - tmp = self.b.as_array()/( 1 - x.as_array() ) + tmp = self.b/( 1 - x ) + ind = tmp.as_array()>0 + + sel return (self.b * ( ImageData( numpy.log(tmp) ) - 1 ) - self.bnoise * (x - 1)).sum() -# return self.b * ( ImageData(numpy.log(self.b/(1-x)) - 1 )) - self.bnoise * (x - 1) def proximal(self, x, tau, out=None): @@ -105,19 +102,39 @@ class KullbackLeibler(Function): if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + import numpy N, M = 2,3 ig = ImageGeometry(N, M) - data = ImageData(numpy.random.randint(-10, 100, size=(M, N))) - x = ImageData(numpy.random.randint(-10, 100, size=(M, N))) - - bnoise = ImageData(numpy.random.randint(-100, 100, size=(M, N))) + data = ImageData(numpy.random.randint(-10, 10, size=(M, N))) + x = ImageData(numpy.random.randint(-10, 10, size=(M, N))) - f = KullbackLeibler(data, bnoise=bnoise) - print(f.sum_value) + bnoise = ImageData(numpy.random.randint(-10, 10, size=(M, N))) - print(f(x)) + f = KullbackLeibler(data) + print(f(x)) + +# numpy.random.seed(10) +# +# +# x = numpy.random.randint(-10, 10, size = (2,3)) +# b = numpy.random.randint(1, 10, size = (2,3)) +# +# ind1 = x>0 +# +# res = x[ind1] - b * numpy.log(x[ind1]) +# +## ind = x>0 +# +## y = x[ind] +# +# +# +# +# \ No newline at end of file diff --git a/Wrappers/Python/wip/pdhg_TV_denoising.py b/Wrappers/Python/wip/pdhg_TV_denoising.py index e6d38e9..0e7f628 100755 --- a/Wrappers/Python/wip/pdhg_TV_denoising.py +++ b/Wrappers/Python/wip/pdhg_TV_denoising.py @@ -8,7 +8,8 @@ Created on Fri Feb 22 14:53:03 2019 from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer -import numpy as np +import numpy as np +import numpy import matplotlib.pyplot as plt from ccpi.optimisation.algorithms import PDHG, PDHG_old @@ -89,13 +90,12 @@ t1 = timer() res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) t2 = timer() -print(" Run memopt") t3 = timer() res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) t4 = timer() - -#%% +# +##%% plt.figure(figsize=(15,15)) plt.subplot(3,1,1) plt.imshow(res.as_array()) @@ -115,10 +115,67 @@ plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'memopt') plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'no memopt') plt.legend() plt.show() - +# print ("Time: No memopt in {}s, \n Time: Memopt in {}s ".format(t2-t1, t4 -t3)) diff = (res1 - res).abs().as_array().max() - +# print(" Max of abs difference is {}".format(diff)) +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( res.as_array() - u.value ) + + # Show result + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(res.as_array()) + plt.title('PDHG solution') + plt.colorbar() + + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(primal[-1])) + + + + diff --git a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py index fbe0d9b..16e6b43 100644 --- a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py +++ b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py @@ -6,7 +6,8 @@ Created on Fri Feb 22 14:53:03 2019 @author: evangelos """ -import numpy as np +import numpy as np +import numpy import matplotlib.pyplot as plt from ccpi.framework import ImageData, ImageGeometry @@ -46,6 +47,7 @@ plt.show() alpha = 2 #method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") + method = '0' if method == '0': @@ -57,7 +59,7 @@ if method == '0': operator = BlockOperator(op1, op2, shape=(2,1) ) f1 = alpha * MixedL21Norm() - f2 = KullbackLeibler(noisy_data) + f2 = 0.5 * KullbackLeibler(noisy_data) f = BlockFunction(f1, f2 ) g = ZeroFunction() @@ -69,9 +71,8 @@ else: ########################################################################### operator = Gradient(ig) f = alpha * MixedL21Norm() - g = KullbackLeibler(noisy_data) + g = 0.5 * KullbackLeibler(noisy_data) ########################################################################### -#%% # Compute operator Norm normK = operator.norm() @@ -87,13 +88,11 @@ t1 = timer() res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) t2 = timer() -print(" Run memopt") - t3 = timer() res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) t4 = timer() -#%% + plt.figure(figsize=(15,15)) plt.subplot(3,1,1) plt.imshow(res.as_array()) @@ -120,6 +119,59 @@ diff = (res1 - res).abs().as_array().max() print(" Max of abs difference is {}".format(diff)) +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + + + solver = SCS + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( res.as_array() - u.value ) + + # Show result + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(res.as_array()) + plt.title('PDHG solution') + plt.colorbar() + + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(primal[-1])) + + + #pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) #pdhg.max_iteration = 2000 #pdhg.update_objective_interval = 10 -- cgit v1.2.3 From fc67848d07425e9c39c80bab7206d45a80087a27 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 17 Apr 2019 11:59:38 +0100 Subject: gpu to cpu --- Wrappers/Python/wip/pdhg_TV_tomography2D.py | 40 +++++++++++++++++------------ 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/Wrappers/Python/wip/pdhg_TV_tomography2D.py b/Wrappers/Python/wip/pdhg_TV_tomography2D.py index 3fec34e..419b098 100644 --- a/Wrappers/Python/wip/pdhg_TV_tomography2D.py +++ b/Wrappers/Python/wip/pdhg_TV_tomography2D.py @@ -56,7 +56,7 @@ detectors = 150 angles = np.linspace(0,np.pi,100) ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'gpu') +Aop = AstraProjectorSimple(ig, ag, 'cpu') sin = Aop.direct(data) plt.imshow(sin.as_array()) @@ -134,23 +134,29 @@ t4 = timer() # print ("No memopt in {}s, memopt in {}s ".format(t2-t1, t4 -t3)) - #%% -#sol = pdhg.get_output().as_array() -#fig = plt.figure() -#plt.subplot(1,2,1) -#plt.imshow(noisy_data.as_array()) -##plt.colorbar() -#plt.subplot(1,2,2) -#plt.imshow(sol) -##plt.colorbar() -#plt.show() +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(res.as_array()) +plt.title('no memopt') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(res1.as_array()) +plt.title('memopt') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow((res1 - res).abs().as_array()) +plt.title('diff') +plt.colorbar() +plt.show() +# +plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'memopt') +plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'no memopt') +plt.legend() +plt.show() # +print ("Time: No memopt in {}s, \n Time: Memopt in {}s ".format(t2-t1, t4 -t3)) +diff = (res1 - res).abs().as_array().max() # -##%% -#plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -#plt.plot(np.linspace(0,N,N), sol[int(N/2),:], label = 'Recon') -#plt.legend() -#plt.show() - +print(" Max of abs difference is {}".format(diff)) -- cgit v1.2.3 From ce0afcc6273f0cedf1d9ac0760f8c3ce80fbde28 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 17 Apr 2019 13:12:31 +0100 Subject: wip tests and demos --- Wrappers/Python/.DS_Store | Bin 0 -> 6148 bytes Wrappers/Python/build/lib/ccpi/__init__.py | 18 + .../build/lib/ccpi/framework/BlockDataContainer.py | 479 +++++++ .../build/lib/ccpi/framework/BlockGeometry.py | 37 + .../Python/build/lib/ccpi/framework/__init__.py | 26 + .../Python/build/lib/ccpi/framework/framework.py | 1414 ++++++++++++++++++++ Wrappers/Python/build/lib/ccpi/io/__init__.py | 18 + Wrappers/Python/build/lib/ccpi/io/reader.py | 511 +++++++ .../Python/build/lib/ccpi/optimisation/__init__.py | 18 + .../lib/ccpi/optimisation/algorithms/Algorithm.py | 158 +++ .../build/lib/ccpi/optimisation/algorithms/CGLS.py | 86 ++ .../build/lib/ccpi/optimisation/algorithms/FBPD.py | 86 ++ .../lib/ccpi/optimisation/algorithms/FISTA.py | 121 ++ .../optimisation/algorithms/GradientDescent.py | 76 ++ .../build/lib/ccpi/optimisation/algorithms/PDHG.py | 215 +++ .../lib/ccpi/optimisation/algorithms/__init__.py | 32 + .../Python/build/lib/ccpi/optimisation/algs.py | 319 +++++ .../Python/build/lib/ccpi/optimisation/funcs.py | 272 ++++ .../ccpi/optimisation/functions/BlockFunction.py | 209 +++ .../lib/ccpi/optimisation/functions/Function.py | 69 + .../functions/FunctionOperatorComposition.py | 85 ++ .../ccpi/optimisation/functions/IndicatorBox.py | 65 + .../ccpi/optimisation/functions/KullbackLeibler.py | 140 ++ .../lib/ccpi/optimisation/functions/L1Norm.py | 234 ++++ .../ccpi/optimisation/functions/L2NormSquared.py | 303 +++++ .../ccpi/optimisation/functions/MixedL21Norm.py | 175 +++ .../lib/ccpi/optimisation/functions/Norm2Sq.py | 98 ++ .../ccpi/optimisation/functions/ScaledFunction.py | 149 +++ .../lib/ccpi/optimisation/functions/ZeroFun.py | 61 + .../ccpi/optimisation/functions/ZeroFunction.py | 61 + .../lib/ccpi/optimisation/functions/__init__.py | 13 + .../lib/ccpi/optimisation/functions/untitled0.py | 50 + .../ccpi/optimisation/operators/BlockOperator.py | 366 +++++ .../optimisation/operators/BlockScaledOperator.py | 67 + .../operators/FiniteDifferenceOperator.py | 373 ++++++ .../operators/FiniteDifferenceOperator_old.py | 374 ++++++ .../optimisation/operators/GradientOperator.py | 243 ++++ .../optimisation/operators/IdentityOperator.py | 79 ++ .../ccpi/optimisation/operators/LinearOperator.py | 22 + .../lib/ccpi/optimisation/operators/Operator.py | 30 + .../ccpi/optimisation/operators/ScaledOperator.py | 51 + .../optimisation/operators/ShrinkageOperator.py | 19 + .../optimisation/operators/SparseFiniteDiff.py | 144 ++ .../operators/SymmetrizedGradientOperator.py | 186 +++ .../ccpi/optimisation/operators/ZeroOperator.py | 39 + .../lib/ccpi/optimisation/operators/__init__.py | 23 + Wrappers/Python/build/lib/ccpi/optimisation/ops.py | 294 ++++ .../Python/build/lib/ccpi/optimisation/spdhg.py | 338 +++++ Wrappers/Python/build/lib/ccpi/processors.py | 514 +++++++ .../Python/ccpi/optimisation/algorithms/PDHG.py | 16 +- Wrappers/Python/wip/pdhg_TV_tomography2D.py | 2 +- .../test_pdhg_profile/profile_pdhg_TV_denoising.py | 273 ---- 52 files changed, 8769 insertions(+), 282 deletions(-) create mode 100644 Wrappers/Python/.DS_Store create mode 100644 Wrappers/Python/build/lib/ccpi/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/framework/BlockDataContainer.py create mode 100644 Wrappers/Python/build/lib/ccpi/framework/BlockGeometry.py create mode 100644 Wrappers/Python/build/lib/ccpi/framework/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/framework/framework.py create mode 100644 Wrappers/Python/build/lib/ccpi/io/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/io/reader.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/CGLS.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FBPD.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FISTA.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/GradientDescent.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algs.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/funcs.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/BlockFunction.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/Function.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/IndicatorBox.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/L1Norm.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/MixedL21Norm.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/Norm2Sq.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/ScaledFunction.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFun.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFunction.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/untitled0.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockScaledOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/IdentityOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/Operator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/ScaledOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/ShrinkageOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/SparseFiniteDiff.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/SymmetrizedGradientOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/ZeroOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/ops.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/spdhg.py create mode 100644 Wrappers/Python/build/lib/ccpi/processors.py delete mode 100644 Wrappers/Python/wip/test_pdhg_profile/profile_pdhg_TV_denoising.py diff --git a/Wrappers/Python/.DS_Store b/Wrappers/Python/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/Wrappers/Python/.DS_Store differ diff --git a/Wrappers/Python/build/lib/ccpi/__init__.py b/Wrappers/Python/build/lib/ccpi/__init__.py new file mode 100644 index 0000000..cf2d93d --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/framework/BlockDataContainer.py b/Wrappers/Python/build/lib/ccpi/framework/BlockDataContainer.py new file mode 100644 index 0000000..386cd87 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/framework/BlockDataContainer.py @@ -0,0 +1,479 @@ + # -*- coding: utf-8 -*- +""" +Created on Tue Mar 5 16:04:45 2019 + +@author: ofn77899 +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy +from numbers import Number +import functools +from ccpi.framework import DataContainer +#from ccpi.framework import AcquisitionData, ImageData +#from ccpi.optimisation.operators import Operator, LinearOperator + +class BlockDataContainer(object): + '''Class to hold DataContainers as column vector + + Provides basic algebra between BlockDataContainer's, DataContainer's and + subclasses and Numbers + + 1) algebra between `BlockDataContainer`s will be element-wise, only if + the shape of the 2 `BlockDataContainer`s is the same, otherwise it + will fail + 2) algebra between `BlockDataContainer`s and `list` or `numpy array` will + work as long as the number of `rows` and element of the arrays match, + indipendently on the fact that the `BlockDataContainer` could be nested + 3) algebra between `BlockDataContainer` and one `DataContainer` is possible. + It will require that all the `DataContainers` in the block to be + compatible with the `DataContainer` we want to algebra with. Should we + require that the `DataContainer` is the same type? Like `ImageData` or `AcquisitionData`? + 4) algebra between `BlockDataContainer` and a `Number` is possible and it + will be done with each element of the `BlockDataContainer` even if nested + + A = [ [B,C] , D] + A * 3 = [ 3 * [B,C] , 3* D] = [ [ 3*B, 3*C] , 3*D ] + + ''' + ADD = 'add' + SUBTRACT = 'subtract' + MULTIPLY = 'multiply' + DIVIDE = 'divide' + POWER = 'power' + __array_priority__ = 1 + __container_priority__ = 2 + def __init__(self, *args, **kwargs): + '''''' + self.containers = args + self.index = 0 + shape = kwargs.get('shape', None) + if shape is None: + shape = (len(args),1) +# shape = (len(args),1) + self.shape = shape + + n_elements = functools.reduce(lambda x,y: x*y, shape, 1) + if len(args) != n_elements: + raise ValueError( + 'Dimension and size do not match: expected {} got {}' + .format(n_elements, len(args))) + + + def __iter__(self): + '''BlockDataContainer is Iterable''' + return self + def next(self): + '''python2 backwards compatibility''' + return self.__next__() + def __next__(self): + try: + out = self[self.index] + except IndexError as ie: + raise StopIteration() + self.index+=1 + return out + + def is_compatible(self, other): + '''basic check if the size of the 2 objects fit''' + + if isinstance(other, Number): + return True + elif isinstance(other, (list, numpy.ndarray)) : + for ot in other: + if not isinstance(ot, (Number,\ + numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.int64,\ + numpy.float, numpy.float16, numpy.float32, numpy.float64, \ + numpy.complex)): + raise ValueError('List/ numpy array can only contain numbers {}'\ + .format(type(ot))) + return len(self.containers) == len(other) + elif issubclass(other.__class__, DataContainer): + ret = True + for i, el in enumerate(self.containers): + if isinstance(el, BlockDataContainer): + a = el.is_compatible(other) + else: + a = el.shape == other.shape + ret = ret and a + return ret + #return self.get_item(0).shape == other.shape + return len(self.containers) == len(other.containers) + + def get_item(self, row): + if row > self.shape[0]: + raise ValueError('Requested row {} > max {}'.format(row, self.shape[0])) + return self.containers[row] + + def __getitem__(self, row): + return self.get_item(row) + + def add(self, other, *args, **kwargs): + '''Algebra: add method of BlockDataContainer with number/DataContainer or BlockDataContainer + + :param: other (number, DataContainer or subclasses or BlockDataContainer + :param: out (optional): provides a placehold for the resul. + ''' + out = kwargs.get('out', None) + if out is not None: + self.binary_operations(BlockDataContainer.ADD, other, *args, **kwargs) + else: + return self.binary_operations(BlockDataContainer.ADD, other, *args, **kwargs) + def subtract(self, other, *args, **kwargs): + '''Algebra: subtract method of BlockDataContainer with number/DataContainer or BlockDataContainer + + :param: other (number, DataContainer or subclasses or BlockDataContainer + :param: out (optional): provides a placehold for the resul. + ''' + out = kwargs.get('out', None) + if out is not None: + self.binary_operations(BlockDataContainer.SUBTRACT, other, *args, **kwargs) + else: + return self.binary_operations(BlockDataContainer.SUBTRACT, other, *args, **kwargs) + def multiply(self, other, *args, **kwargs): + '''Algebra: multiply method of BlockDataContainer with number/DataContainer or BlockDataContainer + + :param: other (number, DataContainer or subclasses or BlockDataContainer + :param: out (optional): provides a placehold for the resul. + ''' + out = kwargs.get('out', None) + if out is not None: + self.binary_operations(BlockDataContainer.MULTIPLY, other, *args, **kwargs) + else: + return self.binary_operations(BlockDataContainer.MULTIPLY, other, *args, **kwargs) + def divide(self, other, *args, **kwargs): + '''Algebra: divide method of BlockDataContainer with number/DataContainer or BlockDataContainer + + :param: other (number, DataContainer or subclasses or BlockDataContainer + :param: out (optional): provides a placehold for the resul. + ''' + out = kwargs.get('out', None) + if out is not None: + self.binary_operations(BlockDataContainer.DIVIDE, other, *args, **kwargs) + else: + return self.binary_operations(BlockDataContainer.DIVIDE, other, *args, **kwargs) + + + def binary_operations(self, operation, other, *args, **kwargs): + '''Algebra: generic method of algebric operation with BlockDataContainer with number/DataContainer or BlockDataContainer + + Provides commutativity with DataContainer and subclasses, i.e. this + class's reverse algebric methods take precedence w.r.t. direct algebric + methods of DataContainer and subclasses. + + This method is not to be used directly + ''' + if not self.is_compatible(other): + raise ValueError('Incompatible for divide') + out = kwargs.get('out', None) + if isinstance(other, Number) or issubclass(other.__class__, DataContainer): + # try to do algebra with one DataContainer. Will raise error if not compatible + kw = kwargs.copy() + res = [] + for i,el in enumerate(self.containers): + if operation == BlockDataContainer.ADD: + op = el.add + elif operation == BlockDataContainer.SUBTRACT: + op = el.subtract + elif operation == BlockDataContainer.MULTIPLY: + op = el.multiply + elif operation == BlockDataContainer.DIVIDE: + op = el.divide + elif operation == BlockDataContainer.POWER: + op = el.power + else: + raise ValueError('Unsupported operation', operation) + if out is not None: + kw['out'] = out.get_item(i) + op(other, *args, **kw) + else: + res.append(op(other, *args, **kw)) + if out is not None: + return + else: + return type(self)(*res, shape=self.shape) + elif isinstance(other, (list, numpy.ndarray, BlockDataContainer)): + # try to do algebra with one DataContainer. Will raise error if not compatible + kw = kwargs.copy() + res = [] + if isinstance(other, BlockDataContainer): + the_other = other.containers + else: + the_other = other + for i,zel in enumerate(zip ( self.containers, the_other) ): + el = zel[0] + ot = zel[1] + if operation == BlockDataContainer.ADD: + op = el.add + elif operation == BlockDataContainer.SUBTRACT: + op = el.subtract + elif operation == BlockDataContainer.MULTIPLY: + op = el.multiply + elif operation == BlockDataContainer.DIVIDE: + op = el.divide + elif operation == BlockDataContainer.POWER: + op = el.power + else: + raise ValueError('Unsupported operation', operation) + if out is not None: + kw['out'] = out.get_item(i) + op(ot, *args, **kw) + else: + res.append(op(ot, *args, **kw)) + if out is not None: + return + else: + return type(self)(*res, shape=self.shape) + return type(self)(*[ operation(ot, *args, **kwargs) for el,ot in zip(self.containers,other)], shape=self.shape) + else: + raise ValueError('Incompatible type {}'.format(type(other))) + + + def power(self, other, *args, **kwargs): + if not self.is_compatible(other): + raise ValueError('Incompatible for power') + out = kwargs.get('out', None) + if isinstance(other, Number): + return type(self)(*[ el.power(other, *args, **kwargs) for el in self.containers], shape=self.shape) + elif isinstance(other, list) or isinstance(other, numpy.ndarray): + return type(self)(*[ el.power(ot, *args, **kwargs) for el,ot in zip(self.containers,other)], shape=self.shape) + return type(self)(*[ el.power(ot, *args, **kwargs) for el,ot in zip(self.containers,other.containers)], shape=self.shape) + + def maximum(self,other, *args, **kwargs): + if not self.is_compatible(other): + raise ValueError('Incompatible for maximum') + out = kwargs.get('out', None) + if isinstance(other, Number): + return type(self)(*[ el.maximum(other, *args, **kwargs) for el in self.containers], shape=self.shape) + elif isinstance(other, list) or isinstance(other, numpy.ndarray): + return type(self)(*[ el.maximum(ot, *args, **kwargs) for el,ot in zip(self.containers,other)], shape=self.shape) + return type(self)(*[ el.maximum(ot, *args, **kwargs) for el,ot in zip(self.containers,other.containers)], shape=self.shape) + + ## unary operations + def abs(self, *args, **kwargs): + return type(self)(*[ el.abs(*args, **kwargs) for el in self.containers], shape=self.shape) + def sign(self, *args, **kwargs): + return type(self)(*[ el.sign(*args, **kwargs) for el in self.containers], shape=self.shape) + def sqrt(self, *args, **kwargs): + return type(self)(*[ el.sqrt(*args, **kwargs) for el in self.containers], shape=self.shape) + def conjugate(self, out=None): + return type(self)(*[el.conjugate() for el in self.containers], shape=self.shape) + + ## reductions + + def sum(self, *args, **kwargs): + return numpy.sum([ el.sum(*args, **kwargs) for el in self.containers]) + + def squared_norm(self): + y = numpy.asarray([el.squared_norm() for el in self.containers]) + return y.sum() + + def norm(self): + return numpy.sqrt(self.squared_norm()) + + def pnorm(self, p=2): + + if p==1: + return sum(self.abs()) + elif p==2: + return sum([el*el for el in self.containers]).sqrt() + else: + return ValueError('Not implemented') + + def copy(self): + '''alias of clone''' + return self.clone() + def clone(self): + return type(self)(*[el.copy() for el in self.containers], shape=self.shape) + def fill(self, other): + if isinstance (other, BlockDataContainer): + if not self.is_compatible(other): + raise ValueError('Incompatible containers') + for el,ot in zip(self.containers, other.containers): + el.fill(ot) + else: + return ValueError('Cannot fill with object provided {}'.format(type(other))) + + def __add__(self, other): + return self.add( other ) + # __radd__ + + def __sub__(self, other): + return self.subtract( other ) + # __rsub__ + + def __mul__(self, other): + return self.multiply(other) + # __rmul__ + + def __div__(self, other): + return self.divide(other) + # __rdiv__ + def __truediv__(self, other): + return self.divide(other) + + def __pow__(self, other): + return self.power(other) + # reverse operand + def __radd__(self, other): + '''Reverse addition + + to make sure that this method is called rather than the __mul__ of a numpy array + the class constant __array_priority__ must be set > 0 + https://docs.scipy.org/doc/numpy-1.15.1/reference/arrays.classes.html#numpy.class.__array_priority__ + ''' + return self + other + # __radd__ + + def __rsub__(self, other): + '''Reverse subtraction + + to make sure that this method is called rather than the __mul__ of a numpy array + the class constant __array_priority__ must be set > 0 + https://docs.scipy.org/doc/numpy-1.15.1/reference/arrays.classes.html#numpy.class.__array_priority__ + ''' + return (-1 * self) + other + # __rsub__ + + def __rmul__(self, other): + '''Reverse multiplication + + to make sure that this method is called rather than the __mul__ of a numpy array + the class constant __array_priority__ must be set > 0 + https://docs.scipy.org/doc/numpy-1.15.1/reference/arrays.classes.html#numpy.class.__array_priority__ + ''' + return self * other + # __rmul__ + + def __rdiv__(self, other): + '''Reverse division + + to make sure that this method is called rather than the __mul__ of a numpy array + the class constant __array_priority__ must be set > 0 + https://docs.scipy.org/doc/numpy-1.15.1/reference/arrays.classes.html#numpy.class.__array_priority__ + ''' + return pow(self / other, -1) + # __rdiv__ + def __rtruediv__(self, other): + '''Reverse truedivision + + to make sure that this method is called rather than the __mul__ of a numpy array + the class constant __array_priority__ must be set > 0 + https://docs.scipy.org/doc/numpy-1.15.1/reference/arrays.classes.html#numpy.class.__array_priority__ + ''' + return self.__rdiv__(other) + + def __rpow__(self, other): + '''Reverse power + + to make sure that this method is called rather than the __mul__ of a numpy array + the class constant __array_priority__ must be set > 0 + https://docs.scipy.org/doc/numpy-1.15.1/reference/arrays.classes.html#numpy.class.__array_priority__ + ''' + return other.power(self) + + def __iadd__(self, other): + '''Inline addition''' + if isinstance (other, BlockDataContainer): + for el,ot in zip(self.containers, other.containers): + el += ot + elif isinstance(other, Number): + for el in self.containers: + el += other + elif isinstance(other, list) or isinstance(other, numpy.ndarray): + if not self.is_compatible(other): + raise ValueError('Incompatible for __iadd__') + for el,ot in zip(self.containers, other): + el += ot + return self + # __iadd__ + + def __isub__(self, other): + '''Inline subtraction''' + if isinstance (other, BlockDataContainer): + for el,ot in zip(self.containers, other.containers): + el -= ot + elif isinstance(other, Number): + for el in self.containers: + el -= other + elif isinstance(other, list) or isinstance(other, numpy.ndarray): + if not self.is_compatible(other): + raise ValueError('Incompatible for __isub__') + for el,ot in zip(self.containers, other): + el -= ot + return self + # __isub__ + + def __imul__(self, other): + '''Inline multiplication''' + if isinstance (other, BlockDataContainer): + for el,ot in zip(self.containers, other.containers): + el *= ot + elif isinstance(other, Number): + for el in self.containers: + el *= other + elif isinstance(other, list) or isinstance(other, numpy.ndarray): + if not self.is_compatible(other): + raise ValueError('Incompatible for __imul__') + for el,ot in zip(self.containers, other): + el *= ot + return self + # __imul__ + + def __idiv__(self, other): + '''Inline division''' + if isinstance (other, BlockDataContainer): + for el,ot in zip(self.containers, other.containers): + el /= ot + elif isinstance(other, Number): + for el in self.containers: + el /= other + elif isinstance(other, list) or isinstance(other, numpy.ndarray): + if not self.is_compatible(other): + raise ValueError('Incompatible for __idiv__') + for el,ot in zip(self.containers, other): + el /= ot + return self + # __rdiv__ + def __itruediv__(self, other): + '''Inline truedivision''' + return self.__idiv__(other) + + + + + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry, BlockGeometry + import numpy + + N, M = 2, 3 + ig = ImageGeometry(N, M) + BG = BlockGeometry(ig, ig) + + U = BG.allocate('random_int') + + + print ("test sum BDC " ) + w = U[0].as_array() + U[1].as_array() + w1 = sum(U).as_array() + numpy.testing.assert_array_equal(w, w1) + + print ("test sum BDC " ) + z = numpy.sqrt(U[0].as_array()**2 + U[1].as_array()**2) + z1 = sum(U**2).sqrt().as_array() + numpy.testing.assert_array_equal(z, z1) + + + + z2 = U.pnorm(2) + + + + + + diff --git a/Wrappers/Python/build/lib/ccpi/framework/BlockGeometry.py b/Wrappers/Python/build/lib/ccpi/framework/BlockGeometry.py new file mode 100644 index 0000000..5dd6750 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/framework/BlockGeometry.py @@ -0,0 +1,37 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy +from numbers import Number +import functools +from ccpi.framework import BlockDataContainer +#from ccpi.optimisation.operators import Operator, LinearOperator + +class BlockGeometry(object): + '''Class to hold Geometry as column vector''' + #__array_priority__ = 1 + def __init__(self, *args, **kwargs): + '''''' + self.geometries = args + self.index = 0 + + shape = (len(args),1) + self.shape = shape + + n_elements = functools.reduce(lambda x,y: x*y, shape, 1) + if len(args) != n_elements: + raise ValueError( + 'Dimension and size do not match: expected {} got {}' + .format(n_elements, len(args))) + + + def get_item(self, index): + '''returns the Geometry in the BlockGeometry located at position index''' + return self.geometries[index] + + def allocate(self, value=0, dimension_labels=None): + containers = [geom.allocate(value) for geom in self.geometries] + return BlockDataContainer(*containers) + diff --git a/Wrappers/Python/build/lib/ccpi/framework/__init__.py b/Wrappers/Python/build/lib/ccpi/framework/__init__.py new file mode 100644 index 0000000..229edb5 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/framework/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Mar 5 16:00:18 2019 + +@author: ofn77899 +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy +import sys +from datetime import timedelta, datetime +import warnings +from functools import reduce + + +from .framework import DataContainer +from .framework import ImageData, AcquisitionData +from .framework import ImageGeometry, AcquisitionGeometry +from .framework import find_key, message +from .framework import DataProcessor +from .framework import AX, PixelByPixelDataProcessor, CastDataContainer +from .BlockDataContainer import BlockDataContainer +from .BlockGeometry import BlockGeometry diff --git a/Wrappers/Python/build/lib/ccpi/framework/framework.py b/Wrappers/Python/build/lib/ccpi/framework/framework.py new file mode 100644 index 0000000..7516447 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/framework/framework.py @@ -0,0 +1,1414 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy +import sys +from datetime import timedelta, datetime +import warnings +from functools import reduce +from numbers import Number + + +def find_key(dic, val): + """return the key of dictionary dic given the value""" + return [k for k, v in dic.items() if v == val][0] + +def message(cls, msg, *args): + msg = "{0}: " + msg + for i in range(len(args)): + msg += " {%d}" %(i+1) + args = list(args) + args.insert(0, cls.__name__ ) + + return msg.format(*args ) + + +class ImageGeometry(object): + RANDOM = 'random' + RANDOM_INT = 'random_int' + CHANNEL = 'channel' + ANGLE = 'angle' + VERTICAL = 'vertical' + HORIZONTAL_X = 'horizontal_x' + HORIZONTAL_Y = 'horizontal_y' + + def __init__(self, + voxel_num_x=0, + voxel_num_y=0, + voxel_num_z=0, + voxel_size_x=1, + voxel_size_y=1, + voxel_size_z=1, + center_x=0, + center_y=0, + center_z=0, + channels=1): + + self.voxel_num_x = voxel_num_x + self.voxel_num_y = voxel_num_y + self.voxel_num_z = voxel_num_z + self.voxel_size_x = voxel_size_x + self.voxel_size_y = voxel_size_y + self.voxel_size_z = voxel_size_z + self.center_x = center_x + self.center_y = center_y + self.center_z = center_z + self.channels = channels + + # this is some code repetition + if self.channels > 1: + if self.voxel_num_z>1: + self.length = 4 + self.shape = (self.channels, self.voxel_num_z, self.voxel_num_y, self.voxel_num_x) + dim_labels = [ImageGeometry.CHANNEL, ImageGeometry.VERTICAL, + ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] + else: + self.length = 3 + self.shape = (self.channels, self.voxel_num_y, self.voxel_num_x) + dim_labels = [ImageGeometry.CHANNEL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] + else: + if self.voxel_num_z>1: + self.length = 3 + self.shape = (self.voxel_num_z, self.voxel_num_y, self.voxel_num_x) + dim_labels = [ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + else: + self.length = 2 + self.shape = (self.voxel_num_y, self.voxel_num_x) + dim_labels = [ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] + + self.dimension_labels = dim_labels + + def get_min_x(self): + return self.center_x - 0.5*self.voxel_num_x*self.voxel_size_x + + def get_max_x(self): + return self.center_x + 0.5*self.voxel_num_x*self.voxel_size_x + + def get_min_y(self): + return self.center_y - 0.5*self.voxel_num_y*self.voxel_size_y + + def get_max_y(self): + return self.center_y + 0.5*self.voxel_num_y*self.voxel_size_y + + def get_min_z(self): + if not self.voxel_num_z == 0: + return self.center_z - 0.5*self.voxel_num_z*self.voxel_size_z + else: + return 0 + + def get_max_z(self): + if not self.voxel_num_z == 0: + return self.center_z + 0.5*self.voxel_num_z*self.voxel_size_z + else: + return 0 + + def clone(self): + '''returns a copy of ImageGeometry''' + return ImageGeometry( + self.voxel_num_x, + self.voxel_num_y, + self.voxel_num_z, + self.voxel_size_x, + self.voxel_size_y, + self.voxel_size_z, + self.center_x, + self.center_y, + self.center_z, + self.channels) + def __str__ (self): + repres = "" + repres += "Number of channels: {0}\n".format(self.channels) + repres += "voxel_num : x{0},y{1},z{2}\n".format(self.voxel_num_x, self.voxel_num_y, self.voxel_num_z) + repres += "voxel_size : x{0},y{1},z{2}\n".format(self.voxel_size_x, self.voxel_size_y, self.voxel_size_z) + repres += "center : x{0},y{1},z{2}\n".format(self.center_x, self.center_y, self.center_z) + return repres + def allocate(self, value=0, dimension_labels=None, **kwargs): + '''allocates an ImageData according to the size expressed in the instance''' + out = ImageData(geometry=self) + if isinstance(value, Number): + if value != 0: + out += value + else: + if value == ImageGeometry.RANDOM: + seed = kwargs.get('seed', None) + if seed is not None: + numpy.random.seed(seed) + out.fill(numpy.random.random_sample(self.shape)) + elif value == ImageGeometry.RANDOM_INT: + seed = kwargs.get('seed', None) + if seed is not None: + numpy.random.seed(seed) + max_value = kwargs.get('max_value', 100) + out.fill(numpy.random.randint(max_value,size=self.shape)) + else: + raise ValueError('Value {} unknown'.format(value)) + if dimension_labels is not None: + if dimension_labels != self.dimension_labels: + return out.subset(dimensions=dimension_labels) + return out + # The following methods return 2 members of the class, therefore I + # don't think we need to implement them. + # Additionally using __len__ is confusing as one would think this is + # an iterable. + #def __len__(self): + # '''returns the length of the geometry''' + # return self.length + #def shape(self): + # '''Returns the shape of the array of the ImageData it describes''' + # return self.shape + +class AcquisitionGeometry(object): + RANDOM = 'random' + RANDOM_INT = 'random_int' + ANGLE_UNIT = 'angle_unit' + DEGREE = 'degree' + RADIAN = 'radian' + CHANNEL = 'channel' + ANGLE = 'angle' + VERTICAL = 'vertical' + HORIZONTAL = 'horizontal' + def __init__(self, + geom_type, + dimension, + angles, + pixel_num_h=0, + pixel_size_h=1, + pixel_num_v=0, + pixel_size_v=1, + dist_source_center=None, + dist_center_detector=None, + channels=1, + **kwargs + ): + """ + General inputs for standard type projection geometries + detectorDomain or detectorpixelSize: + If 2D + If scalar: Width of detector or single detector pixel + If 2-vec: Error + If 3D + If scalar: Width in both dimensions + If 2-vec: Vertical then horizontal size + grid + If 2D + If scalar: number of detectors + If 2-vec: error + If 3D + If scalar: Square grid that size + If 2-vec vertical then horizontal size + cone or parallel + 2D or 3D + parallel_parameters: ? + cone_parameters: + source_to_center_dist (if parallel: NaN) + center_to_detector_dist (if parallel: NaN) + standard or nonstandard (vec) geometry + angles + angles_format radians or degrees + """ + self.geom_type = geom_type # 'parallel' or 'cone' + self.dimension = dimension # 2D or 3D + self.angles = angles + num_of_angles = len (angles) + + self.dist_source_center = dist_source_center + self.dist_center_detector = dist_center_detector + + self.pixel_num_h = pixel_num_h + self.pixel_size_h = pixel_size_h + self.pixel_num_v = pixel_num_v + self.pixel_size_v = pixel_size_v + + self.channels = channels + self.angle_unit=kwargs.get(AcquisitionGeometry.ANGLE_UNIT, + AcquisitionGeometry.DEGREE) + if channels > 1: + if pixel_num_v > 1: + shape = (channels, num_of_angles , pixel_num_v, pixel_num_h) + dim_labels = [AcquisitionGeometry.CHANNEL , + AcquisitionGeometry.ANGLE , AcquisitionGeometry.VERTICAL , + AcquisitionGeometry.HORIZONTAL] + else: + shape = (channels , num_of_angles, pixel_num_h) + dim_labels = [AcquisitionGeometry.CHANNEL , + AcquisitionGeometry.ANGLE, AcquisitionGeometry.HORIZONTAL] + else: + if pixel_num_v > 1: + shape = (num_of_angles, pixel_num_v, pixel_num_h) + dim_labels = [AcquisitionGeometry.ANGLE , AcquisitionGeometry.VERTICAL , + AcquisitionGeometry.HORIZONTAL] + else: + shape = (num_of_angles, pixel_num_h) + dim_labels = [AcquisitionGeometry.ANGLE, AcquisitionGeometry.HORIZONTAL] + self.shape = shape + + self.dimension_labels = dim_labels + + def clone(self): + '''returns a copy of the AcquisitionGeometry''' + return AcquisitionGeometry(self.geom_type, + self.dimension, + self.angles, + self.pixel_num_h, + self.pixel_size_h, + self.pixel_num_v, + self.pixel_size_v, + self.dist_source_center, + self.dist_center_detector, + self.channels) + + def __str__ (self): + repres = "" + repres += "Number of dimensions: {0}\n".format(self.dimension) + repres += "angles: {0}\n".format(self.angles) + repres += "voxel_num : h{0},v{1}\n".format(self.pixel_num_h, self.pixel_num_v) + repres += "voxel size: h{0},v{1}\n".format(self.pixel_size_h, self.pixel_size_v) + repres += "geometry type: {0}\n".format(self.geom_type) + repres += "distance source-detector: {0}\n".format(self.dist_source_center) + repres += "distance center-detector: {0}\n".format(self.dist_source_center) + repres += "number of channels: {0}\n".format(self.channels) + return repres + def allocate(self, value=0, dimension_labels=None): + '''allocates an AcquisitionData according to the size expressed in the instance''' + out = AcquisitionData(geometry=self) + if isinstance(value, Number): + if value != 0: + out += value + else: + if value == AcquisitionData.RANDOM: + seed = kwargs.get('seed', None) + if seed is not None: + numpy.random.seed(seed) + out.fill(numpy.random.random_sample(self.shape)) + elif value == AcquisitionData.RANDOM_INT: + seed = kwargs.get('seed', None) + if seed is not None: + numpy.random.seed(seed) + max_value = kwargs.get('max_value', 100) + out.fill(numpy.random.randint(max_value,size=self.shape)) + else: + raise ValueError('Value {} unknown'.format(value)) + if dimension_labels is not None: + if dimension_labels != self.dimension_labels: + return out.subset(dimensions=dimension_labels) + return out + +class DataContainer(object): + '''Generic class to hold data + + Data is currently held in a numpy arrays''' + + __container_priority__ = 1 + def __init__ (self, array, deep_copy=True, dimension_labels=None, + **kwargs): + '''Holds the data''' + + self.shape = numpy.shape(array) + self.number_of_dimensions = len (self.shape) + self.dimension_labels = {} + self.geometry = None # Only relevant for AcquisitionData and ImageData + + if dimension_labels is not None and \ + len (dimension_labels) == self.number_of_dimensions: + for i in range(self.number_of_dimensions): + self.dimension_labels[i] = dimension_labels[i] + else: + for i in range(self.number_of_dimensions): + self.dimension_labels[i] = 'dimension_{0:02}'.format(i) + + if type(array) == numpy.ndarray: + if deep_copy: + self.array = array.copy() + else: + self.array = array + else: + raise TypeError('Array must be NumpyArray, passed {0}'\ + .format(type(array))) + + # finally copy the geometry + if 'geometry' in kwargs.keys(): + self.geometry = kwargs['geometry'] + else: + # assume it is parallel beam + pass + + def get_dimension_size(self, dimension_label): + if dimension_label in self.dimension_labels.values(): + acq_size = -1 + for k,v in self.dimension_labels.items(): + if v == dimension_label: + acq_size = self.shape[k] + return acq_size + else: + raise ValueError('Unknown dimension {0}. Should be one of'.format(dimension_label, + self.dimension_labels)) + def get_dimension_axis(self, dimension_label): + if dimension_label in self.dimension_labels.values(): + for k,v in self.dimension_labels.items(): + if v == dimension_label: + return k + else: + raise ValueError('Unknown dimension {0}. Should be one of'.format(dimension_label, + self.dimension_labels.values())) + + + def as_array(self, dimensions=None): + '''Returns the DataContainer as Numpy Array + + Returns the pointer to the array if dimensions is not set. + If dimensions is set, it first creates a new DataContainer with the subset + and then it returns the pointer to the array''' + if dimensions is not None: + return self.subset(dimensions).as_array() + return self.array + + + def subset(self, dimensions=None, **kw): + '''Creates a DataContainer containing a subset of self according to the + labels in dimensions''' + if dimensions is None: + if kw == {}: + return self.array.copy() + else: + reduced_dims = [v for k,v in self.dimension_labels.items()] + for dim_l, dim_v in kw.items(): + for k,v in self.dimension_labels.items(): + if v == dim_l: + reduced_dims.pop(k) + return self.subset(dimensions=reduced_dims, **kw) + else: + # check that all the requested dimensions are in the array + # this is done by checking the dimension_labels + proceed = True + unknown_key = '' + # axis_order contains the order of the axis that the user wants + # in the output DataContainer + axis_order = [] + if type(dimensions) == list: + for dl in dimensions: + if dl not in self.dimension_labels.values(): + proceed = False + unknown_key = dl + break + else: + axis_order.append(find_key(self.dimension_labels, dl)) + if not proceed: + raise KeyError('Subset error: Unknown key specified {0}'.format(dl)) + + # slice away the unwanted data from the array + unwanted_dimensions = self.dimension_labels.copy() + left_dimensions = [] + for ax in sorted(axis_order): + this_dimension = unwanted_dimensions.pop(ax) + left_dimensions.append(this_dimension) + #print ("unwanted_dimensions {0}".format(unwanted_dimensions)) + #print ("left_dimensions {0}".format(left_dimensions)) + #new_shape = [self.shape[ax] for ax in axis_order] + #print ("new_shape {0}".format(new_shape)) + command = "self.array[" + for i in range(self.number_of_dimensions): + if self.dimension_labels[i] in unwanted_dimensions.values(): + value = 0 + for k,v in kw.items(): + if k == self.dimension_labels[i]: + value = v + + command = command + str(value) + else: + command = command + ":" + if i < self.number_of_dimensions -1: + command = command + ',' + command = command + ']' + + cleaned = eval(command) + # cleaned has collapsed dimensions in the same order of + # self.array, but we want it in the order stated in the + # "dimensions". + # create axes order for numpy.transpose + axes = [] + for key in dimensions: + #print ("key {0}".format( key)) + for i in range(len( left_dimensions )): + ld = left_dimensions[i] + #print ("ld {0}".format( ld)) + if ld == key: + axes.append(i) + #print ("axes {0}".format(axes)) + + cleaned = numpy.transpose(cleaned, axes).copy() + + return type(self)(cleaned , True, dimensions) + + def fill(self, array, **dimension): + '''fills the internal numpy array with the one provided''' + if dimension == {}: + if issubclass(type(array), DataContainer) or\ + issubclass(type(array), numpy.ndarray): + if array.shape != self.shape: + raise ValueError('Cannot fill with the provided array.' + \ + 'Expecting {0} got {1}'.format( + self.shape,array.shape)) + if issubclass(type(array), DataContainer): + numpy.copyto(self.array, array.array) + else: + #self.array[:] = array + numpy.copyto(self.array, array) + else: + + command = 'self.array[' + i = 0 + for k,v in self.dimension_labels.items(): + for dim_label, dim_value in dimension.items(): + if dim_label == v: + command = command + str(dim_value) + else: + command = command + ":" + if i < self.number_of_dimensions -1: + command = command + ',' + i += 1 + command = command + "] = array[:]" + exec(command) + + + def check_dimensions(self, other): + return self.shape == other.shape + + ## algebra + + def __add__(self, other): + return self.add(other) + def __mul__(self, other): + return self.multiply(other) + def __sub__(self, other): + return self.subtract(other) + def __div__(self, other): + return self.divide(other) + def __truediv__(self, other): + return self.divide(other) + def __pow__(self, other): + return self.power(other) + + + + # reverse operand + def __radd__(self, other): + return self + other + # __radd__ + + def __rsub__(self, other): + return (-1 * self) + other + # __rsub__ + + def __rmul__(self, other): + return self * other + # __rmul__ + + def __rdiv__(self, other): + print ("call __rdiv__") + return pow(self / other, -1) + # __rdiv__ + def __rtruediv__(self, other): + return self.__rdiv__(other) + + def __rpow__(self, other): + if isinstance(other, (int, float)) : + fother = numpy.ones(numpy.shape(self.array)) * other + return type(self)(fother ** self.array , + dimension_labels=self.dimension_labels, + geometry=self.geometry) + elif issubclass(type(other), DataContainer): + if self.check_dimensions(other): + return type(self)(other.as_array() ** self.array , + dimension_labels=self.dimension_labels, + geometry=self.geometry) + else: + raise ValueError('Dimensions do not match') + # __rpow__ + + # in-place arithmetic operators: + # (+=, -=, *=, /= , //=, + # must return self + + def __iadd__(self, other): + kw = {'out':self} + return self.add(other, **kw) + + def __imul__(self, other): + kw = {'out':self} + return self.multiply(other, **kw) + + def __isub__(self, other): + kw = {'out':self} + return self.subtract(other, **kw) + + def __idiv__(self, other): + kw = {'out':self} + return self.divide(other, **kw) + + def __itruediv__(self, other): + kw = {'out':self} + return self.divide(other, **kw) + + + + def __str__ (self, representation=False): + repres = "" + repres += "Number of dimensions: {0}\n".format(self.number_of_dimensions) + repres += "Shape: {0}\n".format(self.shape) + repres += "Axis labels: {0}\n".format(self.dimension_labels) + if representation: + repres += "Representation: \n{0}\n".format(self.array) + return repres + + def clone(self): + '''returns a copy of itself''' + + return type(self)(self.array, + dimension_labels=self.dimension_labels, + deep_copy=True, + geometry=self.geometry ) + + def get_data_axes_order(self,new_order=None): + '''returns the axes label of self as a list + + if new_order is None returns the labels of the axes as a sorted-by-key list + if new_order is a list of length number_of_dimensions, returns a list + with the indices of the axes in new_order with respect to those in + self.dimension_labels: i.e. + self.dimension_labels = {0:'horizontal',1:'vertical'} + new_order = ['vertical','horizontal'] + returns [1,0] + ''' + if new_order is None: + + axes_order = [i for i in range(len(self.shape))] + for k,v in self.dimension_labels.items(): + axes_order[k] = v + return axes_order + else: + if len(new_order) == self.number_of_dimensions: + axes_order = [i for i in range(self.number_of_dimensions)] + + for i in range(len(self.shape)): + found = False + for k,v in self.dimension_labels.items(): + if new_order[i] == v: + axes_order[i] = k + found = True + if not found: + raise ValueError('Axis label {0} not found.'.format(new_order[i])) + return axes_order + else: + raise ValueError('Expecting {0} axes, got {2}'\ + .format(len(self.shape),len(new_order))) + + + def copy(self): + '''alias of clone''' + return self.clone() + + ## binary operations + + def pixel_wise_binary(self, pwop, x2, *args, **kwargs): + out = kwargs.get('out', None) + if out is None: + if isinstance(x2, (int, float, complex)): + out = pwop(self.as_array() , x2 , *args, **kwargs ) + elif isinstance(x2, (numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.int64,\ + numpy.float, numpy.float16, numpy.float32, numpy.float64, \ + numpy.complex)): + out = pwop(self.as_array() , x2 , *args, **kwargs ) + elif issubclass(type(x2) , DataContainer): + out = pwop(self.as_array() , x2.as_array() , *args, **kwargs ) + return type(self)(out, + deep_copy=False, + dimension_labels=self.dimension_labels, + geometry=self.geometry) + + + elif issubclass(type(out), DataContainer) and issubclass(type(x2), DataContainer): + if self.check_dimensions(out) and self.check_dimensions(x2): + kwargs['out'] = out.as_array() + pwop(self.as_array(), x2.as_array(), *args, **kwargs ) + #return type(self)(out.as_array(), + # deep_copy=False, + # dimension_labels=self.dimension_labels, + # geometry=self.geometry) + return out + else: + raise ValueError(message(type(self),"Wrong size for data memory: ", out.shape,self.shape)) + elif issubclass(type(out), DataContainer) and isinstance(x2, (int,float,complex)): + if self.check_dimensions(out): + kwargs['out']=out.as_array() + pwop(self.as_array(), x2, *args, **kwargs ) + return out + else: + raise ValueError(message(type(self),"Wrong size for data memory: ", out.shape,self.shape)) + elif issubclass(type(out), numpy.ndarray): + if self.array.shape == out.shape and self.array.dtype == out.dtype: + kwargs['out'] = out + pwop(self.as_array(), x2, *args, **kwargs) + #return type(self)(out, + # deep_copy=False, + # dimension_labels=self.dimension_labels, + # geometry=self.geometry) + else: + raise ValueError (message(type(self), "incompatible class:" , pwop.__name__, type(out))) + + def add(self, other, *args, **kwargs): + if hasattr(other, '__container_priority__') and \ + self.__class__.__container_priority__ < other.__class__.__container_priority__: + return other.add(self, *args, **kwargs) + return self.pixel_wise_binary(numpy.add, other, *args, **kwargs) + + def subtract(self, other, *args, **kwargs): + if hasattr(other, '__container_priority__') and \ + self.__class__.__container_priority__ < other.__class__.__container_priority__: + return other.subtract(self, *args, **kwargs) + return self.pixel_wise_binary(numpy.subtract, other, *args, **kwargs) + + def multiply(self, other, *args, **kwargs): + if hasattr(other, '__container_priority__') and \ + self.__class__.__container_priority__ < other.__class__.__container_priority__: + return other.multiply(self, *args, **kwargs) + return self.pixel_wise_binary(numpy.multiply, other, *args, **kwargs) + + def divide(self, other, *args, **kwargs): + if hasattr(other, '__container_priority__') and \ + self.__class__.__container_priority__ < other.__class__.__container_priority__: + return other.divide(self, *args, **kwargs) + return self.pixel_wise_binary(numpy.divide, other, *args, **kwargs) + + def power(self, other, *args, **kwargs): + return self.pixel_wise_binary(numpy.power, other, *args, **kwargs) + + def maximum(self, x2, *args, **kwargs): + return self.pixel_wise_binary(numpy.maximum, x2, *args, **kwargs) + + ## unary operations + def pixel_wise_unary(self, pwop, *args, **kwargs): + out = kwargs.get('out', None) + if out is None: + out = pwop(self.as_array() , *args, **kwargs ) + return type(self)(out, + deep_copy=False, + dimension_labels=self.dimension_labels, + geometry=self.geometry) + elif issubclass(type(out), DataContainer): + if self.check_dimensions(out): + kwargs['out'] = out.as_array() + pwop(self.as_array(), *args, **kwargs ) + else: + raise ValueError(message(type(self),"Wrong size for data memory: ", out.shape,self.shape)) + elif issubclass(type(out), numpy.ndarray): + if self.array.shape == out.shape and self.array.dtype == out.dtype: + kwargs['out'] = out + pwop(self.as_array(), *args, **kwargs) + else: + raise ValueError (message(type(self), "incompatible class:" , pwop.__name__, type(out))) + + def abs(self, *args, **kwargs): + return self.pixel_wise_unary(numpy.abs, *args, **kwargs) + + def sign(self, *args, **kwargs): + return self.pixel_wise_unary(numpy.sign, *args, **kwargs) + + def sqrt(self, *args, **kwargs): + return self.pixel_wise_unary(numpy.sqrt, *args, **kwargs) + + def conjugate(self, *args, **kwargs): + return self.pixel_wise_unary(numpy.conjugate, *args, **kwargs) + #def __abs__(self): + # operation = FM.OPERATION.ABS + # return self.callFieldMath(operation, None, self.mask, self.maskOnValue) + # __abs__ + + ## reductions + def sum(self, *args, **kwargs): + return self.as_array().sum(*args, **kwargs) + def squared_norm(self): + '''return the squared euclidean norm of the DataContainer viewed as a vector''' + #shape = self.shape + #size = reduce(lambda x,y:x*y, shape, 1) + #y = numpy.reshape(self.as_array(), (size, )) + return self.dot(self.conjugate()) + #return self.dot(self) + def norm(self): + '''return the euclidean norm of the DataContainer viewed as a vector''' + return numpy.sqrt(self.squared_norm()) + def dot(self, other, *args, **kwargs): + '''return the inner product of 2 DataContainers viewed as vectors''' + if self.shape == other.shape: + return numpy.dot(self.as_array().ravel(), other.as_array().ravel()) + else: + raise ValueError('Shapes are not aligned: {} != {}'.format(self.shape, other.shape)) + + + + + +class ImageData(DataContainer): + '''DataContainer for holding 2D or 3D DataContainer''' + __container_priority__ = 1 + + + def __init__(self, + array = None, + deep_copy=False, + dimension_labels=None, + **kwargs): + + self.geometry = kwargs.get('geometry', None) + if array is None: + if self.geometry is not None: + shape, dimension_labels = self.get_shape_labels(self.geometry) + + array = numpy.zeros( shape , dtype=numpy.float32) + super(ImageData, self).__init__(array, deep_copy, + dimension_labels, **kwargs) + + else: + raise ValueError('Please pass either a DataContainer, ' +\ + 'a numpy array or a geometry') + else: + if self.geometry is not None: + shape, labels = self.get_shape_labels(self.geometry, dimension_labels) + if array.shape != shape: + raise ValueError('Shape mismatch {} {}'.format(shape, array.shape)) + + if issubclass(type(array) , DataContainer): + # if the array is a DataContainer get the info from there + if not ( array.number_of_dimensions == 2 or \ + array.number_of_dimensions == 3 or \ + array.number_of_dimensions == 4): + raise ValueError('Number of dimensions are not 2 or 3 or 4: {0}'\ + .format(array.number_of_dimensions)) + + #DataContainer.__init__(self, array.as_array(), deep_copy, + # array.dimension_labels, **kwargs) + super(ImageData, self).__init__(array.as_array(), deep_copy, + array.dimension_labels, **kwargs) + elif issubclass(type(array) , numpy.ndarray): + if not ( array.ndim == 2 or array.ndim == 3 or array.ndim == 4 ): + raise ValueError( + 'Number of dimensions are not 2 or 3 or 4 : {0}'\ + .format(array.ndim)) + + if dimension_labels is None: + if array.ndim == 4: + dimension_labels = [ImageGeometry.CHANNEL, + ImageGeometry.VERTICAL, + ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + elif array.ndim == 3: + dimension_labels = [ImageGeometry.VERTICAL, + ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + else: + dimension_labels = [ ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + + #DataContainer.__init__(self, array, deep_copy, dimension_labels, **kwargs) + super(ImageData, self).__init__(array, deep_copy, + dimension_labels, **kwargs) + + # load metadata from kwargs if present + for key, value in kwargs.items(): + if (type(value) == list or type(value) == tuple) and \ + ( len (value) == 3 and len (value) == 2) : + if key == 'origin' : + self.origin = value + if key == 'spacing' : + self.spacing = value + + def subset(self, dimensions=None, **kw): + # FIXME: this is clearly not rigth + # it should be something like + # out = DataContainer.subset(self, dimensions, **kw) + # followed by regeneration of the proper geometry. + out = super(ImageData, self).subset(dimensions, **kw) + #out.geometry = self.recalculate_geometry(dimensions , **kw) + out.geometry = self.geometry + return out + + def get_shape_labels(self, geometry, dimension_labels=None): + channels = geometry.channels + horiz_x = geometry.voxel_num_x + horiz_y = geometry.voxel_num_y + vert = 1 if geometry.voxel_num_z is None\ + else geometry.voxel_num_z # this should be 1 for 2D + if dimension_labels is None: + if channels > 1: + if vert > 1: + shape = (channels, vert, horiz_y, horiz_x) + dim_labels = [ImageGeometry.CHANNEL, + ImageGeometry.VERTICAL, + ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + else: + shape = (channels , horiz_y, horiz_x) + dim_labels = [ImageGeometry.CHANNEL, + ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + else: + if vert > 1: + shape = (vert, horiz_y, horiz_x) + dim_labels = [ImageGeometry.VERTICAL, + ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + else: + shape = (horiz_y, horiz_x) + dim_labels = [ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + dimension_labels = dim_labels + else: + shape = [] + for i in range(len(dimension_labels)): + dim = dimension_labels[i] + if dim == ImageGeometry.CHANNEL: + shape.append(channels) + elif dim == ImageGeometry.HORIZONTAL_Y: + shape.append(horiz_y) + elif dim == ImageGeometry.VERTICAL: + shape.append(vert) + elif dim == ImageGeometry.HORIZONTAL_X: + shape.append(horiz_x) + if len(shape) != len(dimension_labels): + raise ValueError('Missing {0} axes {1} shape {2}'.format( + len(dimension_labels) - len(shape), dimension_labels, shape)) + shape = tuple(shape) + + return (shape, dimension_labels) + + +class AcquisitionData(DataContainer): + '''DataContainer for holding 2D or 3D sinogram''' + __container_priority__ = 1 + + + def __init__(self, + array = None, + deep_copy=True, + dimension_labels=None, + **kwargs): + self.geometry = kwargs.get('geometry', None) + if array is None: + if 'geometry' in kwargs.keys(): + geometry = kwargs['geometry'] + self.geometry = geometry + + shape, dimension_labels = self.get_shape_labels(geometry, dimension_labels) + + + array = numpy.zeros( shape , dtype=numpy.float32) + super(AcquisitionData, self).__init__(array, deep_copy, + dimension_labels, **kwargs) + else: + if self.geometry is not None: + shape, labels = self.get_shape_labels(self.geometry, dimension_labels) + if array.shape != shape: + raise ValueError('Shape mismatch {} {}'.format(shape, array.shape)) + + if issubclass(type(array) ,DataContainer): + # if the array is a DataContainer get the info from there + if not ( array.number_of_dimensions == 2 or \ + array.number_of_dimensions == 3 or \ + array.number_of_dimensions == 4): + raise ValueError('Number of dimensions are not 2 or 3 or 4: {0}'\ + .format(array.number_of_dimensions)) + + #DataContainer.__init__(self, array.as_array(), deep_copy, + # array.dimension_labels, **kwargs) + super(AcquisitionData, self).__init__(array.as_array(), deep_copy, + array.dimension_labels, **kwargs) + elif issubclass(type(array) ,numpy.ndarray): + if not ( array.ndim == 2 or array.ndim == 3 or array.ndim == 4 ): + raise ValueError( + 'Number of dimensions are not 2 or 3 or 4 : {0}'\ + .format(array.ndim)) + + if dimension_labels is None: + if array.ndim == 4: + dimension_labels = [AcquisitionGeometry.CHANNEL, + AcquisitionGeometry.ANGLE, + AcquisitionGeometry.VERTICAL, + AcquisitionGeometry.HORIZONTAL] + elif array.ndim == 3: + dimension_labels = [AcquisitionGeometry.ANGLE, + AcquisitionGeometry.VERTICAL, + AcquisitionGeometry.HORIZONTAL] + else: + dimension_labels = [AcquisitionGeometry.ANGLE, + AcquisitionGeometry.HORIZONTAL] + + super(AcquisitionData, self).__init__(array, deep_copy, + dimension_labels, **kwargs) + + def get_shape_labels(self, geometry, dimension_labels=None): + channels = geometry.channels + horiz = geometry.pixel_num_h + vert = geometry.pixel_num_v + angles = geometry.angles + num_of_angles = numpy.shape(angles)[0] + + if dimension_labels is None: + if channels > 1: + if vert > 1: + shape = (channels, num_of_angles , vert, horiz) + dim_labels = [AcquisitionGeometry.CHANNEL, + AcquisitionGeometry.ANGLE, + AcquisitionGeometry.VERTICAL, + AcquisitionGeometry.HORIZONTAL] + else: + shape = (channels , num_of_angles, horiz) + dim_labels = [AcquisitionGeometry.CHANNEL, + AcquisitionGeometry.ANGLE, + AcquisitionGeometry.HORIZONTAL] + else: + if vert > 1: + shape = (num_of_angles, vert, horiz) + dim_labels = [AcquisitionGeometry.ANGLE, + AcquisitionGeometry.VERTICAL, + AcquisitionGeometry.HORIZONTAL + ] + else: + shape = (num_of_angles, horiz) + dim_labels = [AcquisitionGeometry.ANGLE, + AcquisitionGeometry.HORIZONTAL + ] + + dimension_labels = dim_labels + else: + shape = [] + for i in range(len(dimension_labels)): + dim = dimension_labels[i] + + if dim == AcquisitionGeometry.CHANNEL: + shape.append(channels) + elif dim == AcquisitionGeometry.ANGLE: + shape.append(num_of_angles) + elif dim == AcquisitionGeometry.VERTICAL: + shape.append(vert) + elif dim == AcquisitionGeometry.HORIZONTAL: + shape.append(horiz) + if len(shape) != len(dimension_labels): + raise ValueError('Missing {0} axes.\nExpected{1} got {2}'\ + .format( + len(dimension_labels) - len(shape), + dimension_labels, shape) + ) + shape = tuple(shape) + return (shape, dimension_labels) + + + +class DataProcessor(object): + + '''Defines a generic DataContainer processor + + accepts DataContainer as inputs and + outputs DataContainer + additional attributes can be defined with __setattr__ + ''' + + def __init__(self, **attributes): + if not 'store_output' in attributes.keys(): + attributes['store_output'] = True + attributes['output'] = False + attributes['runTime'] = -1 + attributes['mTime'] = datetime.now() + attributes['input'] = None + for key, value in attributes.items(): + self.__dict__[key] = value + + + def __setattr__(self, name, value): + if name == 'input': + self.set_input(value) + elif name in self.__dict__.keys(): + self.__dict__[name] = value + self.__dict__['mTime'] = datetime.now() + else: + raise KeyError('Attribute {0} not found'.format(name)) + #pass + + def set_input(self, dataset): + if issubclass(type(dataset), DataContainer): + if self.check_input(dataset): + self.__dict__['input'] = dataset + else: + raise TypeError("Input type mismatch: got {0} expecting {1}"\ + .format(type(dataset), DataContainer)) + + def check_input(self, dataset): + '''Checks parameters of the input DataContainer + + Should raise an Error if the DataContainer does not match expectation, e.g. + if the expected input DataContainer is 3D and the Processor expects 2D. + ''' + raise NotImplementedError('Implement basic checks for input DataContainer') + + def get_output(self, out=None): + + for k,v in self.__dict__.items(): + if v is None and k != 'output': + raise ValueError('Key {0} is None'.format(k)) + shouldRun = False + if self.runTime == -1: + shouldRun = True + elif self.mTime > self.runTime: + shouldRun = True + + # CHECK this + if self.store_output and shouldRun: + self.runTime = datetime.now() + try: + self.output = self.process(out=out) + return self.output + except TypeError as te: + self.output = self.process() + return self.output + self.runTime = datetime.now() + try: + return self.process(out=out) + except TypeError as te: + return self.process() + + + def set_input_processor(self, processor): + if issubclass(type(processor), DataProcessor): + self.__dict__['input'] = processor + else: + raise TypeError("Input type mismatch: got {0} expecting {1}"\ + .format(type(processor), DataProcessor)) + + def get_input(self): + '''returns the input DataContainer + + It is useful in the case the user has provided a DataProcessor as + input + ''' + if issubclass(type(self.input), DataProcessor): + dsi = self.input.get_output() + else: + dsi = self.input + return dsi + + def process(self, out=None): + raise NotImplementedError('process must be implemented') + + + + +class DataProcessor23D(DataProcessor): + '''Regularizers DataProcessor + ''' + + def check_input(self, dataset): + '''Checks number of dimensions input DataContainer + + Expected input is 2D or 3D + ''' + if dataset.number_of_dimensions == 2 or \ + dataset.number_of_dimensions == 3: + return True + else: + raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ + .format(dataset.number_of_dimensions)) + +###### Example of DataProcessors + +class AX(DataProcessor): + '''Example DataProcessor + The AXPY routines perform a vector multiplication operation defined as + + y := a*x + where: + + a is a scalar + + x a DataContainer. + ''' + + def __init__(self): + kwargs = {'scalar':None, + 'input':None, + } + + #DataProcessor.__init__(self, **kwargs) + super(AX, self).__init__(**kwargs) + + def check_input(self, dataset): + return True + + def process(self, out=None): + + dsi = self.get_input() + a = self.scalar + if out is None: + y = DataContainer( a * dsi.as_array() , True, + dimension_labels=dsi.dimension_labels ) + #self.setParameter(output_dataset=y) + return y + else: + out.fill(a * dsi.as_array()) + + +###### Example of DataProcessors + +class CastDataContainer(DataProcessor): + '''Example DataProcessor + Cast a DataContainer array to a different type. + + y := a*x + where: + + a is a scalar + + x a DataContainer. + ''' + + def __init__(self, dtype=None): + kwargs = {'dtype':dtype, + 'input':None, + } + + #DataProcessor.__init__(self, **kwargs) + super(CastDataContainer, self).__init__(**kwargs) + + def check_input(self, dataset): + return True + + def process(self, out=None): + + dsi = self.get_input() + dtype = self.dtype + if out is None: + y = numpy.asarray(dsi.as_array(), dtype=dtype) + + return type(dsi)(numpy.asarray(dsi.as_array(), dtype=dtype), + dimension_labels=dsi.dimension_labels ) + else: + out.fill(numpy.asarray(dsi.as_array(), dtype=dtype)) + + + + + +class PixelByPixelDataProcessor(DataProcessor): + '''Example DataProcessor + + This processor applies a python function to each pixel of the DataContainer + + f is a python function + + x a DataSet. + ''' + + def __init__(self): + kwargs = {'pyfunc':None, + 'input':None, + } + #DataProcessor.__init__(self, **kwargs) + super(PixelByPixelDataProcessor, self).__init__(**kwargs) + + def check_input(self, dataset): + return True + + def process(self, out=None): + + pyfunc = self.pyfunc + dsi = self.get_input() + + eval_func = numpy.frompyfunc(pyfunc,1,1) + + + y = DataContainer( eval_func( dsi.as_array() ) , True, + dimension_labels=dsi.dimension_labels ) + return y + + + + +if __name__ == '__main__': + shape = (2,3,4,5) + size = shape[0] + for i in range(1, len(shape)): + size = size * shape[i] + #print("a refcount " , sys.getrefcount(a)) + a = numpy.asarray([i for i in range( size )]) + print("a refcount " , sys.getrefcount(a)) + a = numpy.reshape(a, shape) + print("a refcount " , sys.getrefcount(a)) + ds = DataContainer(a, False, ['X', 'Y','Z' ,'W']) + print("a refcount " , sys.getrefcount(a)) + print ("ds label {0}".format(ds.dimension_labels)) + subset = ['W' ,'X'] + b = ds.subset( subset ) + print("a refcount " , sys.getrefcount(a)) + print ("b label {0} shape {1}".format(b.dimension_labels, + numpy.shape(b.as_array()))) + c = ds.subset(['Z','W','X']) + print("a refcount " , sys.getrefcount(a)) + + # Create a ImageData sharing the array with c + volume0 = ImageData(c.as_array(), False, dimensions = c.dimension_labels) + volume1 = ImageData(c, False) + + print ("volume0 {0} volume1 {1}".format(id(volume0.array), + id(volume1.array))) + + # Create a ImageData copying the array from c + volume2 = ImageData(c.as_array(), dimensions = c.dimension_labels) + volume3 = ImageData(c) + + print ("volume2 {0} volume3 {1}".format(id(volume2.array), + id(volume3.array))) + + # single number DataSet + sn = DataContainer(numpy.asarray([1])) + + ax = AX() + ax.scalar = 2 + ax.set_input(c) + #ax.apply() + print ("ax in {0} out {1}".format(c.as_array().flatten(), + ax.get_output().as_array().flatten())) + + cast = CastDataContainer(dtype=numpy.float32) + cast.set_input(c) + out = cast.get_output() + out *= 0 + axm = AX() + axm.scalar = 0.5 + axm.set_input_processor(cast) + axm.get_output(out) + #axm.apply() + print ("axm in {0} out {1}".format(c.as_array(), axm.get_output().as_array())) + + # check out in DataSetProcessor + #a = numpy.asarray([i for i in range( size )]) + + + # create a PixelByPixelDataProcessor + + #define a python function which will take only one input (the pixel value) + pyfunc = lambda x: -x if x > 20 else x + clip = PixelByPixelDataProcessor() + clip.pyfunc = pyfunc + clip.set_input(c) + #clip.apply() + + print ("clip in {0} out {1}".format(c.as_array(), clip.get_output().as_array())) + + #dsp = DataProcessor() + #dsp.set_input(ds) + #dsp.input = a + # pipeline + + chain = AX() + chain.scalar = 0.5 + chain.set_input_processor(ax) + print ("chain in {0} out {1}".format(ax.get_output().as_array(), chain.get_output().as_array())) + + # testing arithmetic operations + + print (b) + print ((b+1)) + print ((1+b)) + + print (b) + print ((b*2)) + + print (b) + print ((2*b)) + + print (b) + print ((b/2)) + + print (b) + print ((2/b)) + + print (b) + print ((b**2)) + + print (b) + print ((2**b)) + + print (type(volume3 + 2)) + + s = [i for i in range(3 * 4 * 4)] + s = numpy.reshape(numpy.asarray(s), (3,4,4)) + sino = AcquisitionData( s ) + + shape = (4,3,2) + a = [i for i in range(2*3*4)] + a = numpy.asarray(a) + a = numpy.reshape(a, shape) + print (numpy.shape(a)) + ds = DataContainer(a, True, ['X', 'Y','Z']) + # this means that I expect the X to be of length 2 , + # y of length 3 and z of length 4 + subset = ['Y' ,'Z'] + b0 = ds.subset( subset ) + print ("shape b 3,2? {0}".format(numpy.shape(b0.as_array()))) + # expectation on b is that it is + # 3x2 cut at z = 0 + + subset = ['X' ,'Y'] + b1 = ds.subset( subset , Z=1) + print ("shape b 2,3? {0}".format(numpy.shape(b1.as_array()))) + + + + # create VolumeData from geometry + vgeometry = ImageGeometry(voxel_num_x=2, voxel_num_y=3, channels=2) + vol = ImageData(geometry=vgeometry) + + sgeometry = AcquisitionGeometry(dimension=2, angles=numpy.linspace(0, 180, num=20), + geom_type='parallel', pixel_num_v=3, + pixel_num_h=5 , channels=2) + sino = AcquisitionData(geometry=sgeometry) + sino2 = sino.clone() + + a0 = numpy.asarray([i for i in range(2*3*4)]) + a1 = numpy.asarray([2*i for i in range(2*3*4)]) + + + ds0 = DataContainer(numpy.reshape(a0,(2,3,4))) + ds1 = DataContainer(numpy.reshape(a1,(2,3,4))) + + numpy.testing.assert_equal(ds0.dot(ds1), a0.dot(a1)) + + a2 = numpy.asarray([2*i for i in range(2*3*5)]) + ds2 = DataContainer(numpy.reshape(a2,(2,3,5))) + +# # it should fail if the shape is wrong +# try: +# ds2.dot(ds0) +# self.assertTrue(False) +# except ValueError as ve: +# self.assertTrue(True) + diff --git a/Wrappers/Python/build/lib/ccpi/io/__init__.py b/Wrappers/Python/build/lib/ccpi/io/__init__.py new file mode 100644 index 0000000..9233d7a --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/io/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/io/reader.py b/Wrappers/Python/build/lib/ccpi/io/reader.py new file mode 100644 index 0000000..07e3bf9 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/io/reader.py @@ -0,0 +1,511 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Jakob Jorgensen, Daniil Kazantsev, Edoardo Pasca and Srikanth Nagella + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' +This is a reader module with classes for loading 3D datasets. + +@author: Mr. Srikanth Nagella +''' +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from ccpi.framework import AcquisitionGeometry +from ccpi.framework import AcquisitionData +import numpy as np +import os + +h5pyAvailable = True +try: + from h5py import File as NexusFile +except: + h5pyAvailable = False + +pilAvailable = True +try: + from PIL import Image +except: + pilAvailable = False + +class NexusReader(object): + ''' + Reader class for loading Nexus files. + ''' + + def __init__(self, nexus_filename=None): + ''' + This takes in input as filename and loads the data dataset. + ''' + self.flat = None + self.dark = None + self.angles = None + self.geometry = None + self.filename = nexus_filename + self.key_path = 'entry1/tomo_entry/instrument/detector/image_key' + self.data_path = 'entry1/tomo_entry/data/data' + self.angle_path = 'entry1/tomo_entry/data/rotation_angle' + + def get_image_keys(self): + try: + with NexusFile(self.filename,'r') as file: + return np.array(file[self.key_path]) + except KeyError as ke: + raise KeyError("get_image_keys: " , ke.args[0] , self.key_path) + + + def load(self, dimensions=None, image_key_id=0): + ''' + This is generic loading function of flat field, dark field and projection data. + ''' + if not h5pyAvailable: + raise Exception("Error: h5py is not installed") + if self.filename is None: + return + try: + with NexusFile(self.filename,'r') as file: + image_keys = np.array(file[self.key_path]) + projections = None + if dimensions == None: + projections = np.array(file[self.data_path]) + result = projections[image_keys==image_key_id] + return result + else: + #When dimensions are specified they need to be mapped to image_keys + index_array = np.where(image_keys==image_key_id) + projection_indexes = index_array[0][dimensions[0]] + new_dimensions = list(dimensions) + new_dimensions[0]= projection_indexes + new_dimensions = tuple(new_dimensions) + result = np.array(file[self.data_path][new_dimensions]) + return result + except: + print("Error reading nexus file") + raise + + def load_projection(self, dimensions=None): + ''' + Loads the projection data from the nexus file. + returns: numpy array with projection data + ''' + try: + if 0 not in self.get_image_keys(): + raise ValueError("Projections are not in the data. Data Path " , + self.data_path) + except KeyError as ke: + raise KeyError(ke.args[0] , self.data_path) + return self.load(dimensions, 0) + + def load_flat(self, dimensions=None): + ''' + Loads the flat field data from the nexus file. + returns: numpy array with flat field data + ''' + try: + if 1 not in self.get_image_keys(): + raise ValueError("Flats are not in the data. Data Path " , + self.data_path) + except KeyError as ke: + raise KeyError(ke.args[0] , self.data_path) + return self.load(dimensions, 1) + + def load_dark(self, dimensions=None): + ''' + Loads the Dark field data from the nexus file. + returns: numpy array with dark field data + ''' + try: + if 2 not in self.get_image_keys(): + raise ValueError("Darks are not in the data. Data Path " , + self.data_path) + except KeyError as ke: + raise KeyError(ke.args[0] , self.data_path) + return self.load(dimensions, 2) + + def get_projection_angles(self): + ''' + This function returns the projection angles + ''' + if not h5pyAvailable: + raise Exception("Error: h5py is not installed") + if self.filename is None: + return + try: + with NexusFile(self.filename,'r') as file: + angles = np.array(file[self.angle_path],np.float32) + image_keys = np.array(file[self.key_path]) + return angles[image_keys==0] + except: + print("get_projection_angles Error reading nexus file") + raise + + + def get_sinogram_dimensions(self): + ''' + Return the dimensions of the dataset + ''' + if not h5pyAvailable: + raise Exception("Error: h5py is not installed") + if self.filename is None: + return + try: + with NexusFile(self.filename,'r') as file: + projections = file[self.data_path] + image_keys = np.array(file[self.key_path]) + dims = list(projections.shape) + dims[0] = dims[1] + dims[1] = np.sum(image_keys==0) + return tuple(dims) + except: + print("Error reading nexus file") + raise + + def get_projection_dimensions(self): + ''' + Return the dimensions of the dataset + ''' + if not h5pyAvailable: + raise Exception("Error: h5py is not installed") + if self.filename is None: + return + try: + with NexusFile(self.filename,'r') as file: + try: + projections = file[self.data_path] + except KeyError as ke: + raise KeyError('Error: data path {0} not found\n{1}'\ + .format(self.data_path, + ke.args[0])) + #image_keys = np.array(file[self.key_path]) + image_keys = self.get_image_keys() + dims = list(projections.shape) + dims[0] = np.sum(image_keys==0) + return tuple(dims) + except: + print("Warning: Error reading image_keys trying accessing data on " , self.data_path) + with NexusFile(self.filename,'r') as file: + dims = file[self.data_path].shape + return tuple(dims) + + + + def get_acquisition_data(self, dimensions=None): + ''' + This method load the acquisition data and given dimension and returns an AcquisitionData Object + ''' + data = self.load_projection(dimensions) + dims = self.get_projection_dimensions() + geometry = AcquisitionGeometry('parallel', '3D', + self.get_projection_angles(), + pixel_num_h = dims[2], + pixel_size_h = 1 , + pixel_num_v = dims[1], + pixel_size_v = 1, + dist_source_center = None, + dist_center_detector = None, + channels = 1) + return AcquisitionData(data, geometry=geometry, + dimension_labels=['angle','vertical','horizontal']) + + def get_acquisition_data_subset(self, ymin=None, ymax=None): + ''' + This method load the acquisition data and given dimension and returns an AcquisitionData Object + ''' + if not h5pyAvailable: + raise Exception("Error: h5py is not installed") + if self.filename is None: + return + try: + + + with NexusFile(self.filename,'r') as file: + try: + dims = self.get_projection_dimensions() + except KeyError: + pass + dims = file[self.data_path].shape + if ymin is None and ymax is None: + + try: + image_keys = self.get_image_keys() + print ("image_keys", image_keys) + projections = np.array(file[self.data_path]) + data = projections[image_keys==0] + except KeyError as ke: + print (ke) + data = np.array(file[self.data_path]) + + else: + image_keys = self.get_image_keys() + print ("image_keys", image_keys) + projections = np.array(file[self.data_path])[image_keys==0] + if ymin is None: + ymin = 0 + if ymax > dims[1]: + raise ValueError('ymax out of range') + data = projections[:,:ymax,:] + elif ymax is None: + ymax = dims[1] + if ymin < 0: + raise ValueError('ymin out of range') + data = projections[:,ymin:,:] + else: + if ymax > dims[1]: + raise ValueError('ymax out of range') + if ymin < 0: + raise ValueError('ymin out of range') + + data = projections[: , ymin:ymax , :] + + except: + print("Error reading nexus file") + raise + + + try: + angles = self.get_projection_angles() + except KeyError as ke: + n = data.shape[0] + angles = np.linspace(0, n, n+1, dtype=np.float32) + + if ymax-ymin > 1: + + geometry = AcquisitionGeometry('parallel', '3D', + angles, + pixel_num_h = dims[2], + pixel_size_h = 1 , + pixel_num_v = ymax-ymin, + pixel_size_v = 1, + dist_source_center = None, + dist_center_detector = None, + channels = 1) + return AcquisitionData(data, False, geometry=geometry, + dimension_labels=['angle','vertical','horizontal']) + elif ymax-ymin == 1: + geometry = AcquisitionGeometry('parallel', '2D', + angles, + pixel_num_h = dims[2], + pixel_size_h = 1 , + dist_source_center = None, + dist_center_detector = None, + channels = 1) + return AcquisitionData(data.squeeze(), False, geometry=geometry, + dimension_labels=['angle','horizontal']) + def get_acquisition_data_slice(self, y_slice=0): + return self.get_acquisition_data_subset(ymin=y_slice , ymax=y_slice+1) + def get_acquisition_data_whole(self): + with NexusFile(self.filename,'r') as file: + try: + dims = self.get_projection_dimensions() + except KeyError: + print ("Warning: ") + dims = file[self.data_path].shape + + ymin = 0 + ymax = dims[1] - 1 + + return self.get_acquisition_data_subset(ymin=ymin, ymax=ymax) + + + + def list_file_content(self): + try: + with NexusFile(self.filename,'r') as file: + file.visit(print) + except: + print("Error reading nexus file") + raise + def get_acquisition_data_batch(self, bmin=None, bmax=None): + if not h5pyAvailable: + raise Exception("Error: h5py is not installed") + if self.filename is None: + return + try: + + + with NexusFile(self.filename,'r') as file: + try: + dims = self.get_projection_dimensions() + except KeyError: + dims = file[self.data_path].shape + if bmin is None or bmax is None: + raise ValueError('get_acquisition_data_batch: please specify fastest index batch limits') + + if bmin >= 0 and bmin < bmax and bmax <= dims[0]: + data = np.array(file[self.data_path][bmin:bmax]) + else: + raise ValueError('get_acquisition_data_batch: bmin {0}>0 bmax {1}<{2}'.format(bmin, bmax, dims[0])) + + except: + print("Error reading nexus file") + raise + + + try: + angles = self.get_projection_angles()[bmin:bmax] + except KeyError as ke: + n = data.shape[0] + angles = np.linspace(0, n, n+1, dtype=np.float32)[bmin:bmax] + + if bmax-bmin > 1: + + geometry = AcquisitionGeometry('parallel', '3D', + angles, + pixel_num_h = dims[2], + pixel_size_h = 1 , + pixel_num_v = bmax-bmin, + pixel_size_v = 1, + dist_source_center = None, + dist_center_detector = None, + channels = 1) + return AcquisitionData(data, False, geometry=geometry, + dimension_labels=['angle','vertical','horizontal']) + elif bmax-bmin == 1: + geometry = AcquisitionGeometry('parallel', '2D', + angles, + pixel_num_h = dims[2], + pixel_size_h = 1 , + dist_source_center = None, + dist_center_detector = None, + channels = 1) + return AcquisitionData(data.squeeze(), False, geometry=geometry, + dimension_labels=['angle','horizontal']) + + + +class XTEKReader(object): + ''' + Reader class for loading XTEK files + ''' + + def __init__(self, xtek_config_filename=None): + ''' + This takes in the xtek config filename and loads the dataset and the + required geometry parameters + ''' + self.projections = None + self.geometry = {} + self.filename = xtek_config_filename + self.load() + + def load(self): + pixel_num_h = 0 + pixel_num_v = 0 + xpixel_size = 0 + ypixel_size = 0 + source_x = 0 + detector_x = 0 + with open(self.filename) as f: + content = f.readlines() + content = [x.strip() for x in content] + for line in content: + if line.startswith("SrcToObject"): + source_x = float(line.split('=')[1]) + elif line.startswith("SrcToDetector"): + detector_x = float(line.split('=')[1]) + elif line.startswith("DetectorPixelsY"): + pixel_num_v = int(line.split('=')[1]) + #self.num_of_vertical_pixels = self.calc_v_alighment(self.num_of_vertical_pixels, self.pixels_per_voxel) + elif line.startswith("DetectorPixelsX"): + pixel_num_h = int(line.split('=')[1]) + elif line.startswith("DetectorPixelSizeX"): + xpixel_size = float(line.split('=')[1]) + elif line.startswith("DetectorPixelSizeY"): + ypixel_size = float(line.split('=')[1]) + elif line.startswith("Projections"): + self.num_projections = int(line.split('=')[1]) + elif line.startswith("InitialAngle"): + self.initial_angle = float(line.split('=')[1]) + elif line.startswith("Name"): + self.experiment_name = line.split('=')[1] + elif line.startswith("Scattering"): + self.scattering = float(line.split('=')[1]) + elif line.startswith("WhiteLevel"): + self.white_level = float(line.split('=')[1]) + elif line.startswith("MaskRadius"): + self.mask_radius = float(line.split('=')[1]) + + #Read Angles + angles = self.read_angles() + self.geometry = AcquisitionGeometry('cone', '3D', angles, pixel_num_h, xpixel_size, pixel_num_v, ypixel_size, -1 * source_x, + detector_x - source_x, + ) + + def read_angles(self): + """ + Read the angles file .ang or _ctdata.txt file and returns the angles + as an numpy array. + """ + input_path = os.path.dirname(self.filename) + angles_ctdata_file = os.path.join(input_path, '_ctdata.txt') + angles_named_file = os.path.join(input_path, self.experiment_name+'.ang') + angles = np.zeros(self.num_projections,dtype='f') + #look for _ctdata.txt + if os.path.exists(angles_ctdata_file): + #read txt file with angles + with open(angles_ctdata_file) as f: + content = f.readlines() + #skip firt three lines + #read the middle value of 3 values in each line as angles in degrees + index = 0 + for line in content[3:]: + self.angles[index]=float(line.split(' ')[1]) + index+=1 + angles = np.deg2rad(self.angles+self.initial_angle); + elif os.path.exists(angles_named_file): + #read the angles file which is text with first line as header + with open(angles_named_file) as f: + content = f.readlines() + #skip first line + index = 0 + for line in content[1:]: + angles[index] = float(line.split(':')[1]) + index+=1 + angles = np.flipud(angles+self.initial_angle) #angles are in the reverse order + else: + raise RuntimeError("Can't find angles file") + return angles + + def load_projection(self, dimensions=None): + ''' + This method reads the projection images from the directory and returns a numpy array + ''' + if not pilAvailable: + raise('Image library pillow is not installed') + if dimensions != None: + raise('Extracting subset of data is not implemented') + input_path = os.path.dirname(self.filename) + pixels = np.zeros((self.num_projections, self.geometry.pixel_num_h, self.geometry.pixel_num_v), dtype='float32') + for i in range(1, self.num_projections+1): + im = Image.open(os.path.join(input_path,self.experiment_name+"_%04d"%i+".tif")) + pixels[i-1,:,:] = np.fliplr(np.transpose(np.array(im))) ##Not sure this is the correct way to populate the image + + #normalising the data + #TODO: Move this to a processor + pixels = pixels - (self.white_level*self.scattering)/100.0 + pixels[pixels < 0.0] = 0.000001 # all negative values to approximately 0 as the std log of zero and non negative number is not defined + return pixels + + def get_acquisition_data(self, dimensions=None): + ''' + This method load the acquisition data and given dimension and returns an AcquisitionData Object + ''' + data = self.load_projection(dimensions) + return AcquisitionData(data, geometry=self.geometry) + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/__init__.py b/Wrappers/Python/build/lib/ccpi/optimisation/__init__.py new file mode 100644 index 0000000..cf2d93d --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py new file mode 100644 index 0000000..ed95c3f --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2019 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import time +from numbers import Integral + +class Algorithm(object): + '''Base class for iterative algorithms + + provides the minimal infrastructure. + Algorithms are iterables so can be easily run in a for loop. They will + stop as soon as the stop cryterion is met. + The user is required to implement the set_up, __init__, update and + and update_objective methods + + A courtesy method run is available to run n iterations. The method accepts + a callback function that receives the current iteration number and the actual objective + value and can be used to trigger print to screens and other user interactions. The run + method will stop when the stopping cryterion is met. + ''' + + def __init__(self): + '''Constructor + + Set the minimal number of parameters: + iteration: current iteration number + max_iteration: maximum number of iterations + memopt: whether to use memory optimisation () + timing: list to hold the times it took to run each iteration + update_objectice_interval: the interval every which we would save the current + objective. 1 means every iteration, 2 every 2 iteration + and so forth. This is by default 1 and should be increased + when evaluating the objective is computationally expensive. + ''' + self.iteration = 0 + self.__max_iteration = 0 + self.__loss = [] + self.memopt = False + self.timing = [] + self.update_objective_interval = 1 + def set_up(self, *args, **kwargs): + '''Set up the algorithm''' + raise NotImplementedError() + def update(self): + '''A single iteration of the algorithm''' + raise NotImplementedError() + + def should_stop(self): + '''default stopping cryterion: number of iterations + + The user can change this in concrete implementatition of iterative algorithms.''' + return self.max_iteration_stop_cryterion() + + def max_iteration_stop_cryterion(self): + '''default stop cryterion for iterative algorithm: max_iteration reached''' + return self.iteration >= self.max_iteration + def __iter__(self): + '''Algorithm is an iterable''' + return self + def next(self): + '''Algorithm is an iterable + + python2 backwards compatibility''' + return self.__next__() + def __next__(self): + '''Algorithm is an iterable + + calling this method triggers update and update_objective + ''' + if self.should_stop(): + raise StopIteration() + else: + time0 = time.time() + self.update() + self.timing.append( time.time() - time0 ) + if self.iteration % self.update_objective_interval == 0: + self.update_objective() + self.iteration += 1 + def get_output(self): + '''Returns the solution found''' + return self.x + def get_last_loss(self): + '''Returns the last stored value of the loss function + + if update_objective_interval is 1 it is the value of the objective at the current + iteration. If update_objective_interval > 1 it is the last stored value. + ''' + return self.__loss[-1] + def get_last_objective(self): + '''alias to get_last_loss''' + return self.get_last_loss() + def update_objective(self): + '''calculates the objective with the current solution''' + raise NotImplementedError() + @property + def loss(self): + '''returns the list of the values of the objective during the iteration + + The length of this list may be shorter than the number of iterations run when + the update_objective_interval > 1 + ''' + return self.__loss + @property + def objective(self): + '''alias of loss''' + return self.loss + @property + def max_iteration(self): + '''gets the maximum number of iterations''' + return self.__max_iteration + @max_iteration.setter + def max_iteration(self, value): + '''sets the maximum number of iterations''' + assert isinstance(value, int) + self.__max_iteration = value + @property + def update_objective_interval(self): + return self.__update_objective_interval + @update_objective_interval.setter + def update_objective_interval(self, value): + if isinstance(value, Integral): + if value >= 1: + self.__update_objective_interval = value + else: + raise ValueError('Update objective interval must be an integer >= 1') + else: + raise ValueError('Update objective interval must be an integer >= 1') + def run(self, iterations, verbose=True, callback=None): + '''run n iterations and update the user with the callback if specified''' + if self.should_stop(): + print ("Stop cryterion has been reached.") + i = 0 + for _ in self: + if verbose and self.iteration % self.update_objective_interval == 0: + print ("Iteration {}/{}, objective {}".format(self.iteration, + self.max_iteration, self.get_last_objective()) ) + else: + if callback is not None: + callback(self.iteration, self.get_last_objective()) + i += 1 + if i == iterations: + break + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/CGLS.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/CGLS.py new file mode 100644 index 0000000..e65bc89 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/CGLS.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Created on Thu Feb 21 11:11:23 2019 + +@author: ofn77899 +""" + +from ccpi.optimisation.algorithms import Algorithm +class CGLS(Algorithm): + + '''Conjugate Gradient Least Squares algorithm + + Parameters: + x_init: initial guess + operator: operator for forward/backward projections + data: data to operate on + ''' + def __init__(self, **kwargs): + super(CGLS, self).__init__() + self.x = kwargs.get('x_init', None) + self.operator = kwargs.get('operator', None) + self.data = kwargs.get('data', None) + if self.x is not None and self.operator is not None and \ + self.data is not None: + print ("Calling from creator") + self.set_up(x_init =kwargs['x_init'], + operator=kwargs['operator'], + data =kwargs['data']) + + def set_up(self, x_init, operator , data ): + + self.r = data.copy() + self.x = x_init.copy() + + self.operator = operator + self.d = operator.adjoint(self.r) + + + self.normr2 = self.d.squared_norm() + #if isinstance(self.normr2, Iterable): + # self.normr2 = sum(self.normr2) + #self.normr2 = numpy.sqrt(self.normr2) + #print ("set_up" , self.normr2) + + def update(self): + + Ad = self.operator.direct(self.d) + #norm = (Ad*Ad).sum() + #if isinstance(norm, Iterable): + # norm = sum(norm) + norm = Ad.squared_norm() + + alpha = self.normr2/norm + self.x += (self.d * alpha) + self.r -= (Ad * alpha) + s = self.operator.adjoint(self.r) + + normr2_new = s.squared_norm() + #if isinstance(normr2_new, Iterable): + # normr2_new = sum(normr2_new) + #normr2_new = numpy.sqrt(normr2_new) + #print (normr2_new) + + beta = normr2_new/self.normr2 + self.normr2 = normr2_new + self.d = s + beta*self.d + + def update_objective(self): + self.loss.append(self.r.squared_norm()) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FBPD.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FBPD.py new file mode 100644 index 0000000..aa07359 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FBPD.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2019 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Created on Thu Feb 21 11:09:03 2019 + +@author: ofn77899 +""" + +from ccpi.optimisation.algorithms import Algorithm +from ccpi.optimisation.functions import ZeroFunction + +class FBPD(Algorithm): + '''FBPD Algorithm + + Parameters: + x_init: initial guess + f: constraint + g: data fidelity + h: regularizer + opt: additional algorithm + ''' + constraint = None + data_fidelity = None + regulariser = None + def __init__(self, **kwargs): + pass + def set_up(self, x_init, operator=None, constraint=None, data_fidelity=None,\ + regulariser=None, opt=None): + + # default inputs + if constraint is None: + self.constraint = ZeroFun() + else: + self.constraint = constraint + if data_fidelity is None: + data_fidelity = ZeroFun() + else: + self.data_fidelity = data_fidelity + if regulariser is None: + self.regulariser = ZeroFun() + else: + self.regulariser = regulariser + + # algorithmic parameters + + + # step-sizes + self.tau = 2 / (self.data_fidelity.L + 2) + self.sigma = (1/self.tau - self.data_fidelity.L/2) / self.regulariser.L + + self.inv_sigma = 1/self.sigma + + # initialization + self.x = x_init + self.y = operator.direct(self.x) + + + def update(self): + + # primal forward-backward step + x_old = self.x + self.x = self.x - self.tau * ( self.data_fidelity.grad(self.x) + self.operator.adjoint(self.y) ) + self.x = self.constraint.prox(self.x, self.tau); + + # dual forward-backward step + self.y = self.y + self.sigma * self.operator.direct(2*self.x - x_old); + self.y = self.y - self.sigma * self.regulariser.prox(self.inv_sigma*self.y, self.inv_sigma); + + # time and criterion + self.loss = self.constraint(self.x) + self.data_fidelity(self.x) + self.regulariser(self.operator.direct(self.x)) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FISTA.py new file mode 100644 index 0000000..8ea2b6c --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FISTA.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Feb 21 11:07:30 2019 + +@author: ofn77899 +""" + +from ccpi.optimisation.algorithms import Algorithm +from ccpi.optimisation.functions import ZeroFunction +import numpy + +class FISTA(Algorithm): + '''Fast Iterative Shrinkage-Thresholding Algorithm + + Beck, A. and Teboulle, M., 2009. A fast iterative shrinkage-thresholding + algorithm for linear inverse problems. + SIAM journal on imaging sciences,2(1), pp.183-202. + + Parameters: + x_init: initial guess + f: data fidelity + g: regularizer + h: + opt: additional algorithm + ''' + + def __init__(self, **kwargs): + '''initialisation can be done at creation time if all + proper variables are passed or later with set_up''' + super(FISTA, self).__init__() + self.f = None + self.g = None + self.invL = None + self.t_old = 1 + args = ['x_init', 'f', 'g', 'opt'] + for k,v in kwargs.items(): + if k in args: + args.pop(args.index(k)) + if len(args) == 0: + return self.set_up(kwargs['x_init'], + f=kwargs['f'], + g=kwargs['g'], + opt=kwargs['opt']) + + def set_up(self, x_init, f=None, g=None, opt=None): + + # default inputs + if f is None: + self.f = ZeroFunction() + else: + self.f = f + if g is None: + g = ZeroFunction() + self.g = g + else: + self.g = g + + # algorithmic parameters + if opt is None: + opt = {'tol': 1e-4, 'memopt':False} + + self.tol = opt['tol'] if 'tol' in opt.keys() else 1e-4 + memopt = opt['memopt'] if 'memopt' in opt.keys() else False + self.memopt = memopt + + # initialization + if memopt: + self.y = x_init.clone() + self.x_old = x_init.clone() + self.x = x_init.clone() + self.u = x_init.clone() + else: + self.x_old = x_init.copy() + self.y = x_init.copy() + + #timing = numpy.zeros(max_iter) + #criter = numpy.zeros(max_iter) + + + self.invL = 1/f.L + + self.t_old = 1 + + def update(self): + # algorithm loop + #for it in range(0, max_iter): + + if self.memopt: + # u = y - invL*f.grad(y) + # store the result in x_old + self.f.gradient(self.y, out=self.u) + self.u.__imul__( -self.invL ) + self.u.__iadd__( self.y ) + # x = g.prox(u,invL) + self.g.proximal(self.u, self.invL, out=self.x) + + self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) + + # y = x + (t_old-1)/t*(x-x_old) + self.x.subtract(self.x_old, out=self.y) + self.y.__imul__ ((self.t_old-1)/self.t) + self.y.__iadd__( self.x ) + + self.x_old.fill(self.x) + self.t_old = self.t + + + else: + u = self.y - self.invL*self.f.gradient(self.y) + + self.x = self.g.proximal(u,self.invL) + + self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) + + self.y = self.x + (self.t_old-1)/self.t*(self.x-self.x_old) + + self.x_old = self.x.copy() + self.t_old = self.t + + def update_objective(self): + self.loss.append( self.f(self.x) + self.g(self.x) ) \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/GradientDescent.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/GradientDescent.py new file mode 100644 index 0000000..14763c5 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/GradientDescent.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2019 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Created on Thu Feb 21 11:05:09 2019 + +@author: ofn77899 +""" +from ccpi.optimisation.algorithms import Algorithm + +class GradientDescent(Algorithm): + '''Implementation of Gradient Descent algorithm + ''' + + def __init__(self, **kwargs): + '''initialisation can be done at creation time if all + proper variables are passed or later with set_up''' + super(GradientDescent, self).__init__() + self.x = None + self.rate = 0 + self.objective_function = None + self.regulariser = None + args = ['x_init', 'objective_function', 'rate'] + for k,v in kwargs.items(): + if k in args: + args.pop(args.index(k)) + if len(args) == 0: + return self.set_up(x_init=kwargs['x_init'], + objective_function=kwargs['objective_function'], + rate=kwargs['rate']) + + def should_stop(self): + '''stopping cryterion, currently only based on number of iterations''' + return self.iteration >= self.max_iteration + + def set_up(self, x_init, objective_function, rate): + '''initialisation of the algorithm''' + self.x = x_init.copy() + self.objective_function = objective_function + self.rate = rate + self.loss.append(objective_function(x_init)) + self.iteration = 0 + try: + self.memopt = self.objective_function.memopt + except AttributeError as ae: + self.memopt = False + if self.memopt: + self.x_update = x_init.copy() + + def update(self): + '''Single iteration''' + if self.memopt: + self.objective_function.gradient(self.x, out=self.x_update) + self.x_update *= -self.rate + self.x += self.x_update + else: + self.x += -self.rate * self.objective_function.gradient(self.x) + + def update_objective(self): + self.loss.append(self.objective_function(self.x)) + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py new file mode 100644 index 0000000..a41bd48 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Mon Feb 4 16:18:06 2019 + +@author: evangelos +""" +from ccpi.optimisation.algorithms import Algorithm +from ccpi.framework import ImageData, DataContainer +import numpy as np +import numpy +import time +from ccpi.optimisation.operators import BlockOperator +from ccpi.framework import BlockDataContainer +from ccpi.optimisation.functions import FunctionOperatorComposition + +class PDHG(Algorithm): + '''Primal Dual Hybrid Gradient''' + + def __init__(self, **kwargs): + super(PDHG, self).__init__() + self.f = kwargs.get('f', None) + self.operator = kwargs.get('operator', None) + self.g = kwargs.get('g', None) + self.tau = kwargs.get('tau', None) + self.sigma = kwargs.get('sigma', None) + self.memopt = kwargs.get('memopt', False) + + if self.f is not None and self.operator is not None and \ + self.g is not None: + print ("Calling from creator") + self.set_up(self.f, + self.operator, + self.g, + self.tau, + self.sigma) + + def set_up(self, f, g, operator, tau = None, sigma = None, opt = None, **kwargs): + # algorithmic parameters + + if sigma is None and tau is None: + raise ValueError('Need sigma*tau||K||^2<1') + + + self.x_old = self.operator.domain_geometry().allocate() + self.y_old = self.operator.range_geometry().allocate() + + self.xbar = self.x_old.copy() + + self.x = self.x_old.copy() + self.y = self.y_old.copy() + if self.memopt: + self.y_tmp = self.y_old.copy() + self.x_tmp = self.x_old.copy() + #y = y_tmp + + # relaxation parameter + self.theta = 1 + + def update(self): + if self.memopt: + # Gradient descent, Dual problem solution + # self.y_old += self.sigma * self.operator.direct(self.xbar) + self.operator.direct(self.xbar, out=self.y_tmp) + self.y_tmp *= self.sigma + self.y_old += self.y_tmp + + #self.y = self.f.proximal_conjugate(self.y_old, self.sigma) + self.f.proximal_conjugate(self.y_old, self.sigma, out=self.y) + + # Gradient ascent, Primal problem solution + self.operator.adjoint(self.y, out=self.x_tmp) + self.x_tmp *= self.tau + self.x_old -= self.x_tmp + + self.g.proximal(self.x_old, self.tau, out=self.x) + + #Update + self.x.subtract(self.x_old, out=self.xbar) + self.xbar *= self.theta + self.xbar += self.x + + self.x_old.fill(self.x) + self.y_old.fill(self.y) + + else: + # Gradient descent, Dual problem solution + self.y_old += self.sigma * self.operator.direct(self.xbar) + self.y = self.f.proximal_conjugate(self.y_old, self.sigma) + + # Gradient ascent, Primal problem solution + self.x_old -= self.tau * self.operator.adjoint(self.y) + self.x = self.g.proximal(self.x_old, self.tau) + + #Update + #xbar = x + theta * (x - x_old) + self.xbar.fill(self.x) + self.xbar -= self.x_old + self.xbar *= self.theta + self.xbar += self.x + + self.x_old = self.x + self.y_old = self.y + + def update_objective(self): + p1 = self.f(self.operator.direct(self.x)) + self.g(self.x) + d1 = -(self.f.convex_conjugate(self.y) + self.g(-1*self.operator.adjoint(self.y))) + + self.loss.append([p1,d1,p1-d1]) + + + +def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): + + # algorithmic parameters + if opt is None: + opt = {'tol': 1e-6, 'niter': 500, 'show_iter': 100, \ + 'memopt': False} + + if sigma is None and tau is None: + raise ValueError('Need sigma*tau||K||^2<1') + + niter = opt['niter'] if 'niter' in opt.keys() else 1000 + tol = opt['tol'] if 'tol' in opt.keys() else 1e-4 + memopt = opt['memopt'] if 'memopt' in opt.keys() else False + show_iter = opt['show_iter'] if 'show_iter' in opt.keys() else False + stop_crit = opt['stop_crit'] if 'stop_crit' in opt.keys() else False + + x_old = operator.domain_geometry().allocate() + y_old = operator.range_geometry().allocate() + + xbar = x_old.copy() + x_tmp = x_old.copy() + x = x_old.copy() + + y_tmp = y_old.copy() + y = y_tmp.copy() + + + # relaxation parameter + theta = 1 + + t = time.time() + + primal = [] + dual = [] + pdgap = [] + + + for i in range(niter): + + + if not memopt: + + y_tmp = y_old + sigma * operator.direct(xbar) + y = f.proximal_conjugate(y_tmp, sigma) + + x_tmp = x_old - tau*operator.adjoint(y) + x = g.proximal(x_tmp, tau) + + x.subtract(x_old, out=xbar) + xbar *= theta + xbar += x + + x_old.fill(x) + y_old.fill(y) + + + if i%10==0: +# + p1 = f(operator.direct(x)) + g(x) + print(p1) +# d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) +# primal.append(p1) +# dual.append(d1) +# pdgap.append(p1-d1) +# print(p1, d1, p1-d1) + + else: + + operator.direct(xbar, out = y_tmp) + y_tmp *= sigma + y_tmp += y_old + f.proximal_conjugate(y_tmp, sigma, out=y) + + operator.adjoint(y, out = x_tmp) + x_tmp *= -tau + x_tmp += x_old + + g.proximal(x_tmp, tau, out = x) + + x.subtract(x_old, out=xbar) + xbar *= theta + xbar += x + + x_old.fill(x) + y_old.fill(y) + + if i%10==0: +# + p1 = f(operator.direct(x)) + g(x) + print(p1) +# d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) +# primal.append(p1) +# dual.append(d1) +# pdgap.append(p1-d1) +# print(p1, d1, p1-d1) + + + t_end = time.time() + + return x, t_end - t, primal, dual, pdgap + + + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/__init__.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/__init__.py new file mode 100644 index 0000000..f562973 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/__init__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2019 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Created on Thu Feb 21 11:03:13 2019 + +@author: ofn77899 +""" + +from .Algorithm import Algorithm +from .CGLS import CGLS +from .GradientDescent import GradientDescent +from .FISTA import FISTA +from .FBPD import FBPD +from .PDHG import PDHG +from .PDHG import PDHG_old + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algs.py b/Wrappers/Python/build/lib/ccpi/optimisation/algs.py new file mode 100644 index 0000000..2f819d3 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algs.py @@ -0,0 +1,319 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy +import time + +from ccpi.optimisation.functions import Function +from ccpi.optimisation.functions import ZeroFunction +from ccpi.framework import ImageData +from ccpi.framework import AcquisitionData +from ccpi.optimisation.spdhg import spdhg +from ccpi.optimisation.spdhg import KullbackLeibler +from ccpi.optimisation.spdhg import KullbackLeiblerConvexConjugate + +def FISTA(x_init, f=None, g=None, opt=None): + '''Fast Iterative Shrinkage-Thresholding Algorithm + + Beck, A. and Teboulle, M., 2009. A fast iterative shrinkage-thresholding + algorithm for linear inverse problems. + SIAM journal on imaging sciences,2(1), pp.183-202. + + Parameters: + x_init: initial guess + f: data fidelity + g: regularizer + h: + opt: additional algorithm + ''' + # default inputs + if f is None: f = ZeroFun() + if g is None: g = ZeroFun() + + # algorithmic parameters + if opt is None: + opt = {'tol': 1e-4, 'iter': 1000, 'memopt':False} + + max_iter = opt['iter'] if 'iter' in opt.keys() else 1000 + tol = opt['tol'] if 'tol' in opt.keys() else 1e-4 + memopt = opt['memopt'] if 'memopt' in opt.keys() else False + + + # initialization + if memopt: + y = x_init.clone() + x_old = x_init.clone() + x = x_init.clone() + u = x_init.clone() + else: + x_old = x_init + y = x_init; + + timing = numpy.zeros(max_iter) + criter = numpy.zeros(max_iter) + + invL = 1/f.L + + t_old = 1 + +# c = f(x_init) + g(x_init) + + # algorithm loop + for it in range(0, max_iter): + + time0 = time.time() + if memopt: + # u = y - invL*f.grad(y) + # store the result in x_old + f.gradient(y, out=u) + u.__imul__( -invL ) + u.__iadd__( y ) + # x = g.prox(u,invL) + g.proximal(u, invL, out=x) + + t = 0.5*(1 + numpy.sqrt(1 + 4*(t_old**2))) + + # y = x + (t_old-1)/t*(x-x_old) + x.subtract(x_old, out=y) + y.__imul__ ((t_old-1)/t) + y.__iadd__( x ) + + x_old.fill(x) + t_old = t + + + else: + u = y - invL*f.gradient(y) + + x = g.proximal(u,invL) + + t = 0.5*(1 + numpy.sqrt(1 + 4*(t_old**2))) + + y = x + (t_old-1)/t*(x-x_old) + + x_old = x.copy() + t_old = t + + # time and criterion +# timing[it] = time.time() - time0 +# criter[it] = f(x) + g(x); + + # stopping rule + #if np.linalg.norm(x - x_old) < tol * np.linalg.norm(x_old) and it > 10: + # break + + #print(it, 'out of', 10, 'iterations', end='\r'); + + #criter = criter[0:it+1]; +# timing = numpy.cumsum(timing[0:it+1]); + + return x #, it, timing, criter + +def FBPD(x_init, operator=None, constraint=None, data_fidelity=None,\ + regulariser=None, opt=None): + '''FBPD Algorithm + + Parameters: + x_init: initial guess + f: constraint + g: data fidelity + h: regularizer + opt: additional algorithm + ''' + # default inputs + if constraint is None: constraint = ZeroFun() + if data_fidelity is None: data_fidelity = ZeroFun() + if regulariser is None: regulariser = ZeroFun() + + # algorithmic parameters + if opt is None: + opt = {'tol': 1e-4, 'iter': 1000} + else: + try: + max_iter = opt['iter'] + except KeyError as ke: + opt[ke] = 1000 + try: + opt['tol'] = 1000 + except KeyError as ke: + opt[ke] = 1e-4 + tol = opt['tol'] + max_iter = opt['iter'] + memopt = opt['memopts'] if 'memopts' in opt.keys() else False + + # step-sizes + tau = 2 / (data_fidelity.L + 2) + sigma = (1/tau - data_fidelity.L/2) / regulariser.L + inv_sigma = 1/sigma + + # initialization + x = x_init + y = operator.direct(x); + + timing = numpy.zeros(max_iter) + criter = numpy.zeros(max_iter) + + + + + # algorithm loop + for it in range(0, max_iter): + + t = time.time() + + # primal forward-backward step + x_old = x; + x = x - tau * ( data_fidelity.grad(x) + operator.adjoint(y) ); + x = constraint.prox(x, tau); + + # dual forward-backward step + y = y + sigma * operator.direct(2*x - x_old); + y = y - sigma * regulariser.prox(inv_sigma*y, inv_sigma); + + # time and criterion + timing[it] = time.time() - t + criter[it] = constraint(x) + data_fidelity(x) + regulariser(operator.direct(x)) + + # stopping rule + #if np.linalg.norm(x - x_old) < tol * np.linalg.norm(x_old) and it > 10: + # break + + criter = criter[0:it+1] + timing = numpy.cumsum(timing[0:it+1]) + + return x, it, timing, criter + +def CGLS(x_init, operator , data , opt=None): + '''Conjugate Gradient Least Squares algorithm + + Parameters: + x_init: initial guess + operator: operator for forward/backward projections + data: data to operate on + opt: additional algorithm + ''' + + if opt is None: + opt = {'tol': 1e-4, 'iter': 1000} + else: + try: + max_iter = opt['iter'] + except KeyError as ke: + opt[ke] = 1000 + try: + opt['tol'] = 1000 + except KeyError as ke: + opt[ke] = 1e-4 + tol = opt['tol'] + max_iter = opt['iter'] + + r = data.copy() + x = x_init.copy() + + d = operator.adjoint(r) + + normr2 = (d**2).sum() + + timing = numpy.zeros(max_iter) + criter = numpy.zeros(max_iter) + + # algorithm loop + for it in range(0, max_iter): + + t = time.time() + + Ad = operator.direct(d) + alpha = normr2/( (Ad**2).sum() ) + x = x + alpha*d + r = r - alpha*Ad + s = operator.adjoint(r) + + normr2_new = (s**2).sum() + beta = normr2_new/normr2 + normr2 = normr2_new + d = s + beta*d + + # time and criterion + timing[it] = time.time() - t + criter[it] = (r**2).sum() + + return x, it, timing, criter + +def SIRT(x_init, operator , data , opt=None, constraint=None): + '''Simultaneous Iterative Reconstruction Technique + + Parameters: + x_init: initial guess + operator: operator for forward/backward projections + data: data to operate on + opt: additional algorithm + constraint: func of Indicator type specifying convex constraint. + ''' + + if opt is None: + opt = {'tol': 1e-4, 'iter': 1000} + else: + try: + max_iter = opt['iter'] + except KeyError as ke: + opt[ke] = 1000 + try: + opt['tol'] = 1000 + except KeyError as ke: + opt[ke] = 1e-4 + tol = opt['tol'] + max_iter = opt['iter'] + + # Set default constraint to unconstrained + if constraint==None: + constraint = Function() + + x = x_init.clone() + + timing = numpy.zeros(max_iter) + criter = numpy.zeros(max_iter) + + # Relaxation parameter must be strictly between 0 and 2. For now fix at 1.0 + relax_par = 1.0 + + # Set up scaling matrices D and M. + im1 = ImageData(geometry=x_init.geometry) + im1.array[:] = 1.0 + M = 1/operator.direct(im1) + del im1 + aq1 = AcquisitionData(geometry=M.geometry) + aq1.array[:] = 1.0 + D = 1/operator.adjoint(aq1) + del aq1 + + # algorithm loop + for it in range(0, max_iter): + t = time.time() + r = data - operator.direct(x) + + x = constraint.prox(x + relax_par * (D*operator.adjoint(M*r)),None) + + timing[it] = time.time() - t + if it > 0: + criter[it-1] = (r**2).sum() + + r = data - operator.direct(x) + criter[it] = (r**2).sum() + return x, it, timing, criter + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/funcs.py b/Wrappers/Python/build/lib/ccpi/optimisation/funcs.py new file mode 100644 index 0000000..efc465c --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/funcs.py @@ -0,0 +1,272 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.optimisation.ops import Identity, FiniteDiff2D +import numpy +from ccpi.framework import DataContainer +import warnings +from ccpi.optimisation.functions import Function +def isSizeCorrect(data1 ,data2): + if issubclass(type(data1), DataContainer) and \ + issubclass(type(data2), DataContainer): + # check dimensionality + if data1.check_dimensions(data2): + return True + elif issubclass(type(data1) , numpy.ndarray) and \ + issubclass(type(data2) , numpy.ndarray): + return data1.shape == data2.shape + else: + raise ValueError("{0}: getting two incompatible types: {1} {2}"\ + .format('Function', type(data1), type(data2))) + return False +class Norm2(Function): + + def __init__(self, + gamma=1.0, + direction=None): + super(Norm2, self).__init__() + self.gamma = gamma; + self.direction = direction; + + def __call__(self, x, out=None): + + if out is None: + xx = numpy.sqrt(numpy.sum(numpy.square(x.as_array()), self.direction, + keepdims=True)) + else: + if isSizeCorrect(out, x): + # check dimensionality + if issubclass(type(out), DataContainer): + arr = out.as_array() + numpy.square(x.as_array(), out=arr) + xx = numpy.sqrt(numpy.sum(arr, self.direction, keepdims=True)) + + elif issubclass(type(out) , numpy.ndarray): + numpy.square(x.as_array(), out=out) + xx = numpy.sqrt(numpy.sum(out, self.direction, keepdims=True)) + else: + raise ValueError ('Wrong size: x{0} out{1}'.format(x.shape,out.shape) ) + + p = numpy.sum(self.gamma*xx) + + return p + + def prox(self, x, tau): + + xx = numpy.sqrt(numpy.sum( numpy.square(x.as_array()), self.direction, + keepdims=True )) + xx = numpy.maximum(0, 1 - tau*self.gamma / xx) + p = x.as_array() * xx + + return type(x)(p,geometry=x.geometry) + def proximal(self, x, tau, out=None): + if out is None: + return self.prox(x,tau) + else: + if isSizeCorrect(out, x): + # check dimensionality + if issubclass(type(out), DataContainer): + numpy.square(x.as_array(), out = out.as_array()) + xx = numpy.sqrt(numpy.sum( out.as_array() , self.direction, + keepdims=True )) + xx = numpy.maximum(0, 1 - tau*self.gamma / xx) + x.multiply(xx, out= out.as_array()) + + + elif issubclass(type(out) , numpy.ndarray): + numpy.square(x.as_array(), out=out) + xx = numpy.sqrt(numpy.sum(out, self.direction, keepdims=True)) + + xx = numpy.maximum(0, 1 - tau*self.gamma / xx) + x.multiply(xx, out= out) + else: + raise ValueError ('Wrong size: x{0} out{1}'.format(x.shape,out.shape) ) + + +class TV2D(Norm2): + + def __init__(self, gamma): + super(TV2D,self).__init__(gamma, 0) + self.op = FiniteDiff2D() + self.L = self.op.get_max_sing_val() + + +# Define a class for squared 2-norm +class Norm2sq(Function): + ''' + f(x) = c*||A*x-b||_2^2 + + which has + + grad[f](x) = 2*c*A^T*(A*x-b) + + and Lipschitz constant + + L = 2*c*||A||_2^2 = 2*s1(A)^2 + + where s1(A) is the largest singular value of A. + + ''' + + def __init__(self,A,b,c=1.0,memopt=False): + super(Norm2sq, self).__init__() + + self.A = A # Should be an operator, default identity + self.b = b # Default zero DataSet? + self.c = c # Default 1. + if memopt: + try: + self.range_tmp = A.range_geometry().allocate() + self.domain_tmp = A.domain_geometry().allocate() + self.memopt = True + except NameError as ne: + warnings.warn(str(ne)) + self.memopt = False + except NotImplementedError as nie: + print (nie) + warnings.warn(str(nie)) + self.memopt = False + else: + self.memopt = False + + # Compute the Lipschitz parameter from the operator if possible + # Leave it initialised to None otherwise + try: + self.L = 2.0*self.c*(self.A.norm()**2) + except AttributeError as ae: + pass + except NotImplementedError as noe: + pass + + #def grad(self,x): + # return self.gradient(x, out=None) + + def __call__(self,x): + #return self.c* np.sum(np.square((self.A.direct(x) - self.b).ravel())) + #if out is None: + # return self.c*( ( (self.A.direct(x)-self.b)**2).sum() ) + #else: + y = self.A.direct(x) + y.__isub__(self.b) + #y.__imul__(y) + #return y.sum() * self.c + try: + return y.squared_norm() * self.c + except AttributeError as ae: + # added for compatibility with SIRF + return (y.norm()**2) * self.c + + def gradient(self, x, out = None): + if self.memopt: + #return 2.0*self.c*self.A.adjoint( self.A.direct(x) - self.b ) + + self.A.direct(x, out=self.range_tmp) + self.range_tmp -= self.b + self.A.adjoint(self.range_tmp, out=out) + #self.direct_placehold.multiply(2.0*self.c, out=out) + out *= (self.c * 2.0) + else: + return (2.0*self.c)*self.A.adjoint( self.A.direct(x) - self.b ) + + + +# Box constraints indicator function. Calling returns 0 if argument is within +# the box. The prox operator is projection onto the box. Only implements one +# scalar lower and one upper as constraint on all elements. Should generalise +# to vectors to allow different constraints one elements. +class IndicatorBox(Function): + + def __init__(self,lower=-numpy.inf,upper=numpy.inf): + # Do nothing + super(IndicatorBox, self).__init__() + self.lower = lower + self.upper = upper + + + def __call__(self,x): + + if (numpy.all(x.array>=self.lower) and + numpy.all(x.array <= self.upper) ): + val = 0 + else: + val = numpy.inf + return val + + def prox(self,x,tau=None): + return (x.maximum(self.lower)).minimum(self.upper) + + def proximal(self, x, tau, out=None): + if out is None: + return self.prox(x, tau) + else: + if not x.shape == out.shape: + raise ValueError('Norm1 Incompatible size:', + x.shape, out.shape) + #(x.abs() - tau*self.gamma).maximum(0) * x.sign() + x.abs(out = out) + out.__isub__(tau*self.gamma) + out.maximum(0, out=out) + if self.sign_x is None or not x.shape == self.sign_x.shape: + self.sign_x = x.sign() + else: + x.sign(out=self.sign_x) + + out.__imul__( self.sign_x ) + +# A more interesting example, least squares plus 1-norm minimization. +# Define class to represent 1-norm including prox function +class Norm1(Function): + + def __init__(self,gamma): + super(Norm1, self).__init__() + self.gamma = gamma + self.L = 1 + self.sign_x = None + + def __call__(self,x,out=None): + if out is None: + return self.gamma*(x.abs().sum()) + else: + if not x.shape == out.shape: + raise ValueError('Norm1 Incompatible size:', + x.shape, out.shape) + x.abs(out=out) + return out.sum() * self.gamma + + def prox(self,x,tau): + return (x.abs() - tau*self.gamma).maximum(0) * x.sign() + + def proximal(self, x, tau, out=None): + if out is None: + return self.prox(x, tau) + else: + if isSizeCorrect(x,out): + # check dimensionality + if issubclass(type(out), DataContainer): + v = (x.abs() - tau*self.gamma).maximum(0) + x.sign(out=out) + out *= v + #out.fill(self.prox(x,tau)) + elif issubclass(type(out) , numpy.ndarray): + v = (x.abs() - tau*self.gamma).maximum(0) + out[:] = x.sign() + out *= v + #out[:] = self.prox(x,tau) + else: + raise ValueError ('Wrong size: x{0} out{1}'.format(x.shape,out.shape) ) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/BlockFunction.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/BlockFunction.py new file mode 100644 index 0000000..bf627a5 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/BlockFunction.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 8 10:01:31 2019 + +@author: evangelos +""" + +from ccpi.optimisation.functions import Function +from ccpi.framework import BlockDataContainer +from numbers import Number + +class BlockFunction(Function): + + '''BlockFunction acts as a separable sum function, i.e., + + f = [f_1,...,f_n] + + f([x_1,...,x_n]) = f_1(x_1) + .... + f_n(x_n) + + ''' + def __init__(self, *functions): + + self.functions = functions + self.length = len(self.functions) + + super(BlockFunction, self).__init__() + + def __call__(self, x): + + '''Evaluates the BlockFunction at a BlockDataContainer x + + :param: x (BlockDataContainer): must have as many rows as self.length + + returns sum(f_i(x_i)) + ''' + + if self.length != x.shape[0]: + raise ValueError('BlockFunction and BlockDataContainer have incompatible size') + t = 0 + for i in range(x.shape[0]): + t += self.functions[i](x.get_item(i)) + return t + + def convex_conjugate(self, x): + + ''' Evaluate convex conjugate of BlockFunction at x + + returns sum(f_i^{*}(x_i)) + + ''' + t = 0 + for i in range(x.shape[0]): + t += self.functions[i].convex_conjugate(x.get_item(i)) + return t + + + def proximal_conjugate(self, x, tau, out = None): + + ''' Evaluate Proximal Operator of tau * f(\cdot) at x + + prox_{tau*f}(x) = sum_{i} prox_{tau*f_{i}}(x_{i}) + + + ''' + + if out is not None: + if isinstance(tau, Number): + for i in range(self.length): + self.functions[i].proximal_conjugate(x.get_item(i), tau, out=out.get_item(i)) + else: + for i in range(self.length): + self.functions[i].proximal_conjugate(x.get_item(i), tau.get_item(i),out=out.get_item(i)) + + else: + + out = [None]*self.length + if isinstance(tau, Number): + for i in range(self.length): + out[i] = self.functions[i].proximal_conjugate(x.get_item(i), tau) + else: + for i in range(self.length): + out[i] = self.functions[i].proximal_conjugate(x.get_item(i), tau.get_item(i)) + + return BlockDataContainer(*out) + + + def proximal(self, x, tau, out = None): + + ''' Evaluate Proximal Operator of tau * f^{*}(\cdot) at x + + prox_{tau*f^{*}}(x) = sum_{i} prox_{tau*f^{*}_{i}}(x_{i}) + + + ''' + + out = [None]*self.length + if isinstance(tau, Number): + for i in range(self.length): + out[i] = self.functions[i].proximal(x.get_item(i), tau) + else: + for i in range(self.length): + out[i] = self.functions[i].proximal(x.get_item(i), tau.get_item(i)) + + return BlockDataContainer(*out) + + def gradient(self,x, out=None): + + ''' Evaluate gradient of f at x: f'(x) + + returns: BlockDataContainer [f_{1}'(x_{1}), ... , f_{n}'(x_{n})] + + ''' + + out = [None]*self.length + for i in range(self.length): + out[i] = self.functions[i].gradient(x.get_item(i)) + + return BlockDataContainer(*out) + + + +if __name__ == '__main__': + + M, N, K = 2,3,5 + + from ccpi.optimisation.functions import L2NormSquared, MixedL21Norm + from ccpi.framework import ImageGeometry, BlockGeometry + from ccpi.optimisation.operators import Gradient, Identity, BlockOperator + import numpy + import numpy as np + + + ig = ImageGeometry(M, N) + BG = BlockGeometry(ig, ig) + + u = ig.allocate('random_int') + B = BlockOperator( Gradient(ig), Identity(ig) ) + + U = B.direct(u) + b = ig.allocate('random_int') + + f1 = 10 * MixedL21Norm() + f2 = 0.5 * L2NormSquared(b=b) + + f = BlockFunction(f1, f2) + tau = 0.3 + + print( " without out " ) + res_no_out = f.proximal_conjugate( U, tau) + res_out = B.range_geometry().allocate() + f.proximal_conjugate( U, tau, out = res_out) + + numpy.testing.assert_array_almost_equal(res_no_out[0][0].as_array(), \ + res_out[0][0].as_array(), decimal=4) + + numpy.testing.assert_array_almost_equal(res_no_out[0][1].as_array(), \ + res_out[0][1].as_array(), decimal=4) + + numpy.testing.assert_array_almost_equal(res_no_out[1].as_array(), \ + res_out[1].as_array(), decimal=4) + + + + ########################################################################## + + + + + + + +# zzz = B.range_geometry().allocate('random_int') +# www = B.range_geometry().allocate() +# www.fill(zzz) + +# res[0].fill(z) + + + + +# f.proximal_conjugate(z, sigma, out = res) + +# print(z1[0][0].as_array()) +# print(res[0][0].as_array()) + + + + +# U = BG.allocate('random_int') +# RES = BG.allocate() +# f = BlockFunction(f1, f2) +# +# z = f.proximal_conjugate(U, 0.2) +# f.proximal_conjugate(U, 0.2, out = RES) +# +# print(z[0].as_array()) +# print(RES[0].as_array()) +# +# print(z[1].as_array()) +# print(RES[1].as_array()) + + + + + + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/Function.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/Function.py new file mode 100644 index 0000000..ba33666 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/Function.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import warnings +from ccpi.optimisation.functions.ScaledFunction import ScaledFunction + +class Function(object): + '''Abstract class representing a function + + Members: + L is the Lipschitz constant of the gradient of the Function + ''' + def __init__(self): + self.L = None + + def __call__(self,x, out=None): + '''Evaluates the function at x ''' + raise NotImplementedError + + def gradient(self, x, out=None): + '''Returns the gradient of the function at x, if the function is differentiable''' + raise NotImplementedError + + def proximal(self, x, tau, out=None): + '''This returns the proximal operator for the function at x, tau''' + raise NotImplementedError + + def convex_conjugate(self, x, out=None): + '''This evaluates the convex conjugate of the function at x''' + raise NotImplementedError + + def proximal_conjugate(self, x, tau, out = None): + '''This returns the proximal operator for the convex conjugate of the function at x, tau''' + raise NotImplementedError + + def grad(self, x): + '''Alias of gradient(x,None)''' + warnings.warn('''This method will disappear in following + versions of the CIL. Use gradient instead''', DeprecationWarning) + return self.gradient(x, out=None) + + def prox(self, x, tau): + '''Alias of proximal(x, tau, None)''' + warnings.warn('''This method will disappear in following + versions of the CIL. Use proximal instead''', DeprecationWarning) + return self.proximal(x, tau, out=None) + + def __rmul__(self, scalar): + '''Defines the multiplication by a scalar on the left + + returns a ScaledFunction''' + return ScaledFunction(self, scalar) + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition.py new file mode 100644 index 0000000..70511bb --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 8 09:55:36 2019 + +@author: evangelos +""" + +from ccpi.optimisation.functions import Function +from ccpi.optimisation.functions import ScaledFunction + + +class FunctionOperatorComposition(Function): + + ''' Function composition with Operator, i.e., f(Ax) + + A: operator + f: function + + ''' + + def __init__(self, operator, function): + + super(FunctionOperatorComposition, self).__init__() + self.function = function + self.operator = operator + alpha = 1 + + if isinstance (function, ScaledFunction): + alpha = function.scalar + self.L = 2 * alpha * operator.norm()**2 + + + def __call__(self, x): + + ''' Evaluate FunctionOperatorComposition at x + + returns f(Ax) + + ''' + + return self.function(self.operator.direct(x)) + + #TODO do not know if we need it + def call_adjoint(self, x): + + return self.function(self.operator.adjoint(x)) + + + def convex_conjugate(self, x): + + ''' convex_conjugate does not take into account the Operator''' + return self.function.convex_conjugate(x) + + def proximal(self, x, tau, out=None): + + '''proximal does not take into account the Operator''' + if out is None: + return self.function.proximal(x, tau) + else: + self.function.proximal(x, tau, out=out) + + + def proximal_conjugate(self, x, tau, out=None): + + ''' proximal conjugate does not take into account the Operator''' + if out is None: + return self.function.proximal_conjugate(x, tau) + else: + self.function.proximal_conjugate(x, tau, out=out) + + def gradient(self, x, out=None): + + ''' Gradient takes into account the Operator''' + if out is None: + return self.operator.adjoint( + self.function.gradient(self.operator.direct(x)) + ) + else: + self.operator.adjoint( + self.function.gradient(self.operator.direct(x), + out=out) + ) + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/IndicatorBox.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/IndicatorBox.py new file mode 100644 index 0000000..df8dc89 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/IndicatorBox.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from ccpi.optimisation.functions import Function +import numpy + +class IndicatorBox(Function): + '''Box constraints indicator function. + + Calling returns 0 if argument is within the box. The prox operator is projection onto the box. + Only implements one scalar lower and one upper as constraint on all elements. Should generalise + to vectors to allow different constraints one elements. +''' + + def __init__(self,lower=-numpy.inf,upper=numpy.inf): + # Do nothing + super(IndicatorBox, self).__init__() + self.lower = lower + self.upper = upper + + + def __call__(self,x): + + if (numpy.all(x.array>=self.lower) and + numpy.all(x.array <= self.upper) ): + val = 0 + else: + val = numpy.inf + return val + + def prox(self,x,tau=None): + return (x.maximum(self.lower)).minimum(self.upper) + + def proximal(self, x, tau, out=None): + if out is None: + return self.prox(x, tau) + else: + if not x.shape == out.shape: + raise ValueError('Norm1 Incompatible size:', + x.shape, out.shape) + #(x.abs() - tau*self.gamma).maximum(0) * x.sign() + x.abs(out = out) + out.__isub__(tau*self.gamma) + out.maximum(0, out=out) + if self.sign_x is None or not x.shape == self.sign_x.shape: + self.sign_x = x.sign() + else: + x.sign(out=self.sign_x) + + out.__imul__( self.sign_x ) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py new file mode 100644 index 0000000..7889cad --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy +from ccpi.optimisation.functions import Function +from ccpi.optimisation.functions.ScaledFunction import ScaledFunction +from ccpi.framework import ImageData + +class KullbackLeibler(Function): + + ''' Assume that data > 0 + + ''' + + def __init__(self,data, **kwargs): + + super(KullbackLeibler, self).__init__() + + self.b = data + self.bnoise = kwargs.get('bnoise', 0) + + + def __call__(self, x): + + # TODO check + + tmp = x + self.bnoise + ind = tmp.as_array()>0 + + res = x.as_array()[ind] - self.b.as_array()[ind] * numpy.log(tmp.as_array()[ind]) + + return sum(res) + + + def gradient(self, x, out=None): + + #TODO Division check + if out is None: + return 1 - self.b/(x + self.bnoise) + else: + self.b.divide(x+self.bnoise, out=out) + out.subtract(1, out=out) + + def convex_conjugate(self, x): + + tmp = self.b/( 1 - x ) + ind = tmp.as_array()>0 + + sel + + return (self.b * ( ImageData( numpy.log(tmp) ) - 1 ) - self.bnoise * (x - 1)).sum() + + def proximal(self, x, tau, out=None): + + if out is None: + return 0.5 *( (x - self.bnoise - tau) + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() ) + else: + tmp = 0.5 *( (x - self.bnoise - tau) + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() ) + out.fill(tmp) + + + def proximal_conjugate(self, x, tau, out=None): + + + if out is None: + z = x + tau * self.bnoise + return 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) + else: + z = x + tau * self.bnoise + res = 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) + out.fill(res) + + + + def __rmul__(self, scalar): + + ''' Multiplication of L2NormSquared with a scalar + + Returns: ScaledFunction + + ''' + + return ScaledFunction(self, scalar) + + + + +if __name__ == '__main__': + + + from ccpi.framework import ImageGeometry + import numpy + N, M = 2,3 + ig = ImageGeometry(N, M) + data = ImageData(numpy.random.randint(-10, 10, size=(M, N))) + x = ImageData(numpy.random.randint(-10, 10, size=(M, N))) + + bnoise = ImageData(numpy.random.randint(-10, 10, size=(M, N))) + + f = KullbackLeibler(data) + + print(f(x)) + +# numpy.random.seed(10) +# +# +# x = numpy.random.randint(-10, 10, size = (2,3)) +# b = numpy.random.randint(1, 10, size = (2,3)) +# +# ind1 = x>0 +# +# res = x[ind1] - b * numpy.log(x[ind1]) +# +## ind = x>0 +# +## y = x[ind] +# +# +# +# +# + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/L1Norm.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/L1Norm.py new file mode 100644 index 0000000..4e53f2c --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/L1Norm.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from ccpi.optimisation.functions import Function +from ccpi.optimisation.functions.ScaledFunction import ScaledFunction +from ccpi.optimisation.operators import ShrinkageOperator + + +class L1Norm(Function): + + ''' + + Class: L1Norm + + Cases: a) f(x) = ||x||_{1} + + b) f(x) = ||x - b||_{1} + + ''' + + def __init__(self, **kwargs): + + super(L1Norm, self).__init__() + self.b = kwargs.get('b',None) + + def __call__(self, x): + + ''' Evaluate L1Norm at x: f(x) ''' + + y = x + if self.b is not None: + y = x - self.b + return y.abs().sum() + + def gradient(self,x): + #TODO implement subgradient??? + return ValueError('Not Differentiable') + + def convex_conjugate(self,x): + #TODO implement Indicator infty??? + + y = 0 + if self.b is not None: + y = 0 + (self.b * x).sum() + return y + + def proximal(self, x, tau, out=None): + + # TODO implement shrinkage operator, we will need it later e.g SplitBregman + + if out is None: + if self.b is not None: + return self.b + ShrinkageOperator.__call__(self, x - self.b, tau) + else: + return ShrinkageOperator.__call__(self, x, tau) + else: + if self.b is not None: + out.fill(self.b + ShrinkageOperator.__call__(self, x - self.b, tau)) + else: + out.fill(ShrinkageOperator.__call__(self, x, tau)) + + def proximal_conjugate(self, x, tau, out=None): + + if out is None: + if self.b is not None: + return (x - tau*self.b).divide((x - tau*self.b).abs().maximum(1.0)) + else: + return x.divide(x.abs().maximum(1.0)) + else: + if self.b is not None: + out.fill((x - tau*self.b).divide((x - tau*self.b).abs().maximum(1.0))) + else: + out.fill(x.divide(x.abs().maximum(1.0)) ) + + def __rmul__(self, scalar): + return ScaledFunction(self, scalar) + + +#import numpy as np +##from ccpi.optimisation.funcs import Function +#from ccpi.optimisation.functions import Function +#from ccpi.framework import DataContainer, ImageData +# +# +############################# L1NORM FUNCTIONS ############################# +#class SimpleL1Norm(Function): +# +# def __init__(self, alpha=1): +# +# super(SimpleL1Norm, self).__init__() +# self.alpha = alpha +# +# def __call__(self, x): +# return self.alpha * x.abs().sum() +# +# def gradient(self,x): +# return ValueError('Not Differentiable') +# +# def convex_conjugate(self,x): +# return 0 +# +# def proximal(self, x, tau): +# ''' Soft Threshold''' +# return x.sign() * (x.abs() - tau * self.alpha).maximum(0) +# +# def proximal_conjugate(self, x, tau): +# return x.divide((x.abs()/self.alpha).maximum(1.0)) + +#class L1Norm(SimpleL1Norm): +# +# def __init__(self, alpha=1, **kwargs): +# +# super(L1Norm, self).__init__() +# self.alpha = alpha +# self.b = kwargs.get('b',None) +# +# def __call__(self, x): +# +# if self.b is None: +# return SimpleL1Norm.__call__(self, x) +# else: +# return SimpleL1Norm.__call__(self, x - self.b) +# +# def gradient(self, x): +# return ValueError('Not Differentiable') +# +# def convex_conjugate(self,x): +# if self.b is None: +# return SimpleL1Norm.convex_conjugate(self, x) +# else: +# return SimpleL1Norm.convex_conjugate(self, x) + (self.b * x).sum() +# +# def proximal(self, x, tau): +# +# if self.b is None: +# return SimpleL1Norm.proximal(self, x, tau) +# else: +# return self.b + SimpleL1Norm.proximal(self, x - self.b , tau) +# +# def proximal_conjugate(self, x, tau): +# +# if self.b is None: +# return SimpleL1Norm.proximal_conjugate(self, x, tau) +# else: +# return SimpleL1Norm.proximal_conjugate(self, x - tau*self.b, tau) +# + +############################################################################### + + + + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + import numpy + N, M = 40,40 + ig = ImageGeometry(N, M) + scalar = 10 + b = ig.allocate('random_int') + u = ig.allocate('random_int') + + f = L1Norm() + f_scaled = scalar * L1Norm() + + f_b = L1Norm(b=b) + f_scaled_b = scalar * L1Norm(b=b) + + # call + + a1 = f(u) + a2 = f_scaled(u) + numpy.testing.assert_equal(scalar * a1, a2) + + a3 = f_b(u) + a4 = f_scaled_b(u) + numpy.testing.assert_equal(scalar * a3, a4) + + # proximal + tau = 0.4 + b1 = f.proximal(u, tau*scalar) + b2 = f_scaled.proximal(u, tau) + + numpy.testing.assert_array_almost_equal(b1.as_array(), b2.as_array(), decimal=4) + + b3 = f_b.proximal(u, tau*scalar) + b4 = f_scaled_b.proximal(u, tau) + + z1 = b + (u-b).sign() * ((u-b).abs() - tau * scalar).maximum(0) + + numpy.testing.assert_array_almost_equal(b3.as_array(), b4.as_array(), decimal=4) +# +# #proximal conjugate +# + c1 = f_scaled.proximal_conjugate(u, tau) + c2 = u.divide((u.abs()/scalar).maximum(1.0)) + + numpy.testing.assert_array_almost_equal(c1.as_array(), c2.as_array(), decimal=4) + + c3 = f_scaled_b.proximal_conjugate(u, tau) + c4 = (u - tau*b).divide( ((u-tau*b).abs()/scalar).maximum(1.0) ) + + numpy.testing.assert_array_almost_equal(c3.as_array(), c4.as_array(), decimal=4) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py new file mode 100644 index 0000000..6d3bf86 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py @@ -0,0 +1,303 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.optimisation.functions import Function +from ccpi.optimisation.functions.ScaledFunction import ScaledFunction + +class L2NormSquared(Function): + + ''' + + Cases: a) f(x) = \|x\|^{2}_{2} + + b) f(x) = ||x - b||^{2}_{2} + + ''' + + def __init__(self, **kwargs): + + super(L2NormSquared, self).__init__() + self.b = kwargs.get('b',None) + self.L = 2 + + def __call__(self, x): + + ''' Evaluate L2NormSquared at x: f(x) ''' + + y = x + if self.b is not None: + y = x - self.b + try: + return y.squared_norm() + except AttributeError as ae: + # added for compatibility with SIRF + return (y.norm()**2) + + def gradient(self, x, out=None): + + ''' Evaluate gradient of L2NormSquared at x: f'(x) ''' + + if out is not None: + + out.fill(x) + if self.b is not None: + out -= self.b + out *= 2 + + else: + + y = x + if self.b is not None: + y = x - self.b + return 2*y + + + def convex_conjugate(self, x): + + ''' Evaluate convex conjugate of L2NormSquared at x: f^{*}(x)''' + + tmp = 0 + + if self.b is not None: + tmp = (x * self.b).sum() + + return (1./4.) * x.squared_norm() + tmp + + + def proximal(self, x, tau, out = None): + + ''' Evaluate Proximal Operator of tau * f(\cdot) at x: + + prox_{tau*f(\cdot)}(x) = \argmin_{z} \frac{1}{2}|| z - x ||^{2}_{2} + tau * f(z) + + ''' + + if out is None: + + if self.b is None: + return x/(1+2*tau) + else: +# tmp = x +# tmp -= self.b +# tmp /= (1+2*tau) +# tmp += self.b +# return tmp + return (x-self.b)/(1+2*tau) + self.b + +# if self.b is not None: +# out=x +# if self.b is not None: +# out -= self.b +# out /= (1+2*tau) +# if self.b is not None: +# out += self.b +# return out + else: + out.fill(x) + if self.b is not None: + out -= self.b + out /= (1+2*tau) + if self.b is not None: + out += self.b + + + def proximal_conjugate(self, x, tau, out=None): + + ''' Evaluate Proximal Operator of tau * f^{*}(\cdot) at x (i.e., the convex conjugate of f) : + + prox_{tau*f(\cdot)}(x) = \argmin_{z} \frac{1}{2}|| z - x ||^{2}_{2} + tau * f^{*}(z) + + ''' + + if out is None: + if self.b is not None: + return (x - tau*self.b)/(1 + tau/2) + else: + return x/(1 + tau/2) + else: + if self.b is not None: + x.subtract(tau*self.b, out=out) + out.divide(1+tau/2, out=out) + else: + x.divide(1 + tau/2, out=out) + + def __rmul__(self, scalar): + + ''' Multiplication of L2NormSquared with a scalar + + Returns: ScaledFunction + + ''' + + return ScaledFunction(self, scalar) + + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + import numpy + # TESTS for L2 and scalar * L2 + + M, N, K = 2,3,5 + ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N, voxel_num_z = K) + u = ig.allocate('random_int') + b = ig.allocate('random_int') + + # check grad/call no data + f = L2NormSquared() + a1 = f.gradient(u) + a2 = 2 * u + numpy.testing.assert_array_almost_equal(a1.as_array(), a2.as_array(), decimal=4) + numpy.testing.assert_equal(f(u), u.squared_norm()) + + # check grad/call with data + f1 = L2NormSquared(b=b) + b1 = f1.gradient(u) + b2 = 2 * (u-b) + + numpy.testing.assert_array_almost_equal(b1.as_array(), b2.as_array(), decimal=4) + numpy.testing.assert_equal(f1(u), (u-b).squared_norm()) + + #check convex conjuagate no data + c1 = f.convex_conjugate(u) + c2 = 1/4 * u.squared_norm() + numpy.testing.assert_equal(c1, c2) + + #check convex conjuagate with data + d1 = f1.convex_conjugate(u) + d2 = (1/4) * u.squared_norm() + (u*b).sum() + numpy.testing.assert_equal(d1, d2) + + # check proximal no data + tau = 5 + e1 = f.proximal(u, tau) + e2 = u/(1+2*tau) + numpy.testing.assert_array_almost_equal(e1.as_array(), e2.as_array(), decimal=4) + + # check proximal with data + tau = 5 + h1 = f1.proximal(u, tau) + h2 = (u-b)/(1+2*tau) + b + numpy.testing.assert_array_almost_equal(h1.as_array(), h2.as_array(), decimal=4) + + # check proximal conjugate no data + tau = 0.2 + k1 = f.proximal_conjugate(u, tau) + k2 = u/(1 + tau/2 ) + numpy.testing.assert_array_almost_equal(k1.as_array(), k2.as_array(), decimal=4) + + # check proximal conjugate with data + l1 = f1.proximal_conjugate(u, tau) + l2 = (u - tau * b)/(1 + tau/2 ) + numpy.testing.assert_array_almost_equal(l1.as_array(), l2.as_array(), decimal=4) + + + # check scaled function properties + + # scalar + scalar = 100 + f_scaled_no_data = scalar * L2NormSquared() + f_scaled_data = scalar * L2NormSquared(b=b) + + # call + numpy.testing.assert_equal(f_scaled_no_data(u), scalar*f(u)) + numpy.testing.assert_equal(f_scaled_data(u), scalar*f1(u)) + + # grad + numpy.testing.assert_array_almost_equal(f_scaled_no_data.gradient(u).as_array(), scalar*f.gradient(u).as_array(), decimal=4) + numpy.testing.assert_array_almost_equal(f_scaled_data.gradient(u).as_array(), scalar*f1.gradient(u).as_array(), decimal=4) + + # conj + numpy.testing.assert_almost_equal(f_scaled_no_data.convex_conjugate(u), \ + f.convex_conjugate(u/scalar) * scalar, decimal=4) + + numpy.testing.assert_almost_equal(f_scaled_data.convex_conjugate(u), \ + scalar * f1.convex_conjugate(u/scalar), decimal=4) + + # proximal + numpy.testing.assert_array_almost_equal(f_scaled_no_data.proximal(u, tau).as_array(), \ + f.proximal(u, tau*scalar).as_array()) + + + numpy.testing.assert_array_almost_equal(f_scaled_data.proximal(u, tau).as_array(), \ + f1.proximal(u, tau*scalar).as_array()) + + + # proximal conjugate + numpy.testing.assert_array_almost_equal(f_scaled_no_data.proximal_conjugate(u, tau).as_array(), \ + (u/(1 + tau/(2*scalar) )).as_array(), decimal=4) + + numpy.testing.assert_array_almost_equal(f_scaled_data.proximal_conjugate(u, tau).as_array(), \ + ((u - tau * b)/(1 + tau/(2*scalar) )).as_array(), decimal=4) + + + + print( " ####### check without out ######### " ) + + + u_out_no_out = ig.allocate('random_int') + res_no_out = f_scaled_data.proximal_conjugate(u_out_no_out, 0.5) + print(res_no_out.as_array()) + + print( " ####### check with out ######### " ) + + res_out = ig.allocate() + f_scaled_data.proximal_conjugate(u_out_no_out, 0.5, out = res_out) + + print(res_out.as_array()) + + numpy.testing.assert_array_almost_equal(res_no_out.as_array(), \ + res_out.as_array(), decimal=4) + + + + ig1 = ImageGeometry(2,3) + + tau = 0.1 + + u = ig1.allocate('random_int') + b = ig1.allocate('random_int') + + scalar = 0.5 + f_scaled = scalar * L2NormSquared(b=b) + f_noscaled = L2NormSquared(b=b) + + + res1 = f_scaled.proximal(u, tau) + res2 = f_noscaled.proximal(u, tau*scalar) + +# res2 = (u + tau*b)/(1+tau) + + numpy.testing.assert_array_almost_equal(res1.as_array(), \ + res2.as_array(), decimal=4) + + + + + + + + + + + + + + + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/MixedL21Norm.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/MixedL21Norm.py new file mode 100644 index 0000000..2004e5f --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/MixedL21Norm.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.optimisation.functions import Function, ScaledFunction +from ccpi.framework import BlockDataContainer + +import functools + +class MixedL21Norm(Function): + + + ''' + f(x) = ||x||_{2,1} = \sum |x|_{2} + ''' + + def __init__(self, **kwargs): + + super(MixedL21Norm, self).__init__() + self.SymTensor = kwargs.get('SymTensor',False) + + def __call__(self, x): + + ''' Evaluates L2,1Norm at point x + + :param: x is a BlockDataContainer + + ''' + if not isinstance(x, BlockDataContainer): + raise ValueError('__call__ expected BlockDataContainer, got {}'.format(type(x))) + + if self.SymTensor: + + #TODO fix this case + param = [1]*x.shape[0] + param[-1] = 2 + tmp = [param[i]*(x[i] ** 2) for i in range(x.shape[0])] + res = sum(tmp).sqrt().sum() + + else: + + tmp = [ el**2 for el in x.containers ] + res = sum(tmp).sqrt().sum() + + return res + + def gradient(self, x, out=None): + return ValueError('Not Differentiable') + + def convex_conjugate(self,x): + + ''' This is the Indicator function of ||\cdot||_{2, \infty} + which is either 0 if ||x||_{2, \infty} or \infty + ''' + return 0.0 + + def proximal(self, x, tau, out=None): + + ''' + For this we need to define a MixedL2,2 norm acting on BDC, + different form L2NormSquared which acts on DC + + ''' + pass + + def proximal_conjugate(self, x, tau, out=None): + + if self.SymTensor: + + param = [1]*x.shape[0] + param[-1] = 2 + tmp = [param[i]*(x[i] ** 2) for i in range(x.shape[0])] + frac = [x[i]/(sum(tmp).sqrt()).maximum(1.0) for i in range(x.shape[0])] + res = BlockDataContainer(*frac) + + return res + + else: + + if out is None: + tmp = [ el*el for el in x.containers] + res = sum(tmp).sqrt().maximum(1.0) + frac = [el/res for el in x.containers] + return BlockDataContainer(*frac) + + + #TODO this is slow, why??? +# return x.divide(x.pnorm().maximum(1.0)) + else: + + res1 = functools.reduce(lambda a,b: a + b*b, x.containers, x.get_item(0) * 0 ) + res = res1.sqrt().maximum(1.0) + x.divide(res, out=out) + +# x.divide(sum([el*el for el in x.containers]).sqrt().maximum(1.0), out=out) + #TODO this is slow, why ??? +# x.divide(x.pnorm().maximum(1.0), out=out) + + + def __rmul__(self, scalar): + + ''' Multiplication of L2NormSquared with a scalar + + Returns: ScaledFunction + + ''' + return ScaledFunction(self, scalar) + + +# +if __name__ == '__main__': + + M, N, K = 2,3,5 + from ccpi.framework import BlockGeometry + import numpy + + ig = ImageGeometry(M, N) + + BG = BlockGeometry(ig, ig) + + U = BG.allocate('random_int') + + # Define no scale and scaled + f_no_scaled = MixedL21Norm() + f_scaled = 0.5 * MixedL21Norm() + + # call + + a1 = f_no_scaled(U) + a2 = f_scaled(U) + print(a1, 2*a2) + + + print( " ####### check without out ######### " ) + + + u_out_no_out = BG.allocate('random_int') + res_no_out = f_scaled.proximal_conjugate(u_out_no_out, 0.5) + print(res_no_out[0].as_array()) + + print( " ####### check with out ######### " ) +# + res_out = BG.allocate() + f_scaled.proximal_conjugate(u_out_no_out, 0.5, out = res_out) +# + print(res_out[0].as_array()) +# + numpy.testing.assert_array_almost_equal(res_no_out[0].as_array(), \ + res_out[0].as_array(), decimal=4) + + numpy.testing.assert_array_almost_equal(res_no_out[1].as_array(), \ + res_out[1].as_array(), decimal=4) +# + + + + + + + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/Norm2Sq.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/Norm2Sq.py new file mode 100644 index 0000000..b553d7c --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/Norm2Sq.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from ccpi.optimisation.functions import Function +import numpy +import warnings + +# Define a class for squared 2-norm +class Norm2sq(Function): + ''' + f(x) = c*||A*x-b||_2^2 + + which has + + grad[f](x) = 2*c*A^T*(A*x-b) + + and Lipschitz constant + + L = 2*c*||A||_2^2 = 2*s1(A)^2 + + where s1(A) is the largest singular value of A. + + ''' + + def __init__(self,A,b,c=1.0,memopt=False): + super(Norm2sq, self).__init__() + + self.A = A # Should be an operator, default identity + self.b = b # Default zero DataSet? + self.c = c # Default 1. + if memopt: + try: + self.range_tmp = A.range_geometry().allocate() + self.domain_tmp = A.domain_geometry().allocate() + self.memopt = True + except NameError as ne: + warnings.warn(str(ne)) + self.memopt = False + except NotImplementedError as nie: + print (nie) + warnings.warn(str(nie)) + self.memopt = False + else: + self.memopt = False + + # Compute the Lipschitz parameter from the operator if possible + # Leave it initialised to None otherwise + try: + self.L = 2.0*self.c*(self.A.norm()**2) + except AttributeError as ae: + pass + except NotImplementedError as noe: + pass + + #def grad(self,x): + # return self.gradient(x, out=None) + + def __call__(self,x): + #return self.c* np.sum(np.square((self.A.direct(x) - self.b).ravel())) + #if out is None: + # return self.c*( ( (self.A.direct(x)-self.b)**2).sum() ) + #else: + y = self.A.direct(x) + y.__isub__(self.b) + #y.__imul__(y) + #return y.sum() * self.c + try: + return y.squared_norm() * self.c + except AttributeError as ae: + # added for compatibility with SIRF + return (y.norm()**2) * self.c + + def gradient(self, x, out = None): + if self.memopt: + #return 2.0*self.c*self.A.adjoint( self.A.direct(x) - self.b ) + + self.A.direct(x, out=self.range_tmp) + self.range_tmp -= self.b + self.A.adjoint(self.range_tmp, out=out) + #self.direct_placehold.multiply(2.0*self.c, out=out) + out *= (self.c * 2.0) + else: + return (2.0*self.c)*self.A.adjoint( self.A.direct(x) - self.b ) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/ScaledFunction.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/ScaledFunction.py new file mode 100644 index 0000000..7caeab2 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/ScaledFunction.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from numbers import Number +import numpy + +class ScaledFunction(object): + + '''ScaledFunction + + A class to represent the scalar multiplication of an Function with a scalar. + It holds a function and a scalar. Basically it returns the multiplication + of the product of the function __call__, convex_conjugate and gradient with the scalar. + For the rest it behaves like the function it holds. + + Args: + function (Function): a Function or BlockOperator + scalar (Number): a scalar multiplier + Example: + The scaled operator behaves like the following: + + ''' + def __init__(self, function, scalar): + super(ScaledFunction, self).__init__() + + if not isinstance (scalar, Number): + raise TypeError('expected scalar: got {}'.format(type(scalar))) + self.scalar = scalar + self.function = function + + if self.function.L is not None: + self.L = self.scalar * self.function.L + + def __call__(self,x, out=None): + '''Evaluates the function at x ''' + return self.scalar * self.function(x) + + def convex_conjugate(self, x): + '''returns the convex_conjugate of the scaled function ''' + return self.scalar * self.function.convex_conjugate(x/self.scalar) + + def gradient(self, x, out=None): + '''Returns the gradient of the function at x, if the function is differentiable''' + if out is None: + return self.scalar * self.function.gradient(x) + else: + out.fill( self.scalar * self.function.gradient(x) ) + + def proximal(self, x, tau, out=None): + '''This returns the proximal operator for the function at x, tau + ''' + if out is None: + return self.function.proximal(x, tau*self.scalar) + else: + self.function.proximal(x, tau*self.scalar, out = out) + + def proximal_conjugate(self, x, tau, out = None): + '''This returns the proximal operator for the function at x, tau + ''' + if out is None: + return self.scalar * self.function.proximal_conjugate(x/self.scalar, tau/self.scalar) + else: + self.function.proximal_conjugate(x/self.scalar, tau/self.scalar, out=out) + out *= self.scalar + + def grad(self, x): + '''Alias of gradient(x,None)''' + warnings.warn('''This method will disappear in following + versions of the CIL. Use gradient instead''', DeprecationWarning) + return self.gradient(x, out=None) + + def prox(self, x, tau): + '''Alias of proximal(x, tau, None)''' + warnings.warn('''This method will disappear in following + versions of the CIL. Use proximal instead''', DeprecationWarning) + return self.proximal(x, out=None) + + + +if __name__ == '__main__': + + from ccpi.optimisation.functions import L2NormSquared, MixedL21Norm + from ccpi.framework import ImageGeometry, BlockGeometry + + M, N, K = 2,3,5 + ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N, voxel_num_z = K) + + u = ig.allocate('random_int') + b = ig.allocate('random_int') + + BG = BlockGeometry(ig, ig) + U = BG.allocate('random_int') + + f2 = 0.5 * L2NormSquared(b=b) + f1 = 30 * MixedL21Norm() + tau = 0.355 + + res_no_out1 = f1.proximal_conjugate(U, tau) + res_no_out2 = f2.proximal_conjugate(u, tau) + + +# print( " ######## with out ######## ") + res_out1 = BG.allocate() + res_out2 = ig.allocate() + + f1.proximal_conjugate(U, tau, out = res_out1) + f2.proximal_conjugate(u, tau, out = res_out2) + + + numpy.testing.assert_array_almost_equal(res_no_out1[0].as_array(), \ + res_out1[0].as_array(), decimal=4) + + numpy.testing.assert_array_almost_equal(res_no_out2.as_array(), \ + res_out2.as_array(), decimal=4) + + + + + + + + + + + + + + + + + + + + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFun.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFun.py new file mode 100644 index 0000000..cce519a --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFun.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.optimisation.functions import Function +from ccpi.framework import BlockDataContainer + +class ZeroFunction(Function): + + ''' ZeroFunction: f(x) = 0 + + + ''' + + def __init__(self): + super(ZeroFunction, self).__init__() + + def __call__(self,x): + return 0 + + def convex_conjugate(self, x): + + ''' This is the support function sup which in fact is the + indicator function for the set = {0} + So 0 if x=0, or inf if x neq 0 + ''' + + if x.shape[0]==1: + return x.maximum(0).sum() + else: + if isinstance(x, BlockDataContainer): + return x.get_item(0).maximum(0).sum() + x.get_item(1).maximum(0).sum() + else: + return x.maximum(0).sum() + x.maximum(0).sum() + + def proximal(self, x, tau, out=None): + if out is None: + return x.copy() + else: + out.fill(x) + + def proximal_conjugate(self, x, tau, out = None): + if out is None: + return 0 + else: + return 0 diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFunction.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFunction.py new file mode 100644 index 0000000..cce519a --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFunction.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.optimisation.functions import Function +from ccpi.framework import BlockDataContainer + +class ZeroFunction(Function): + + ''' ZeroFunction: f(x) = 0 + + + ''' + + def __init__(self): + super(ZeroFunction, self).__init__() + + def __call__(self,x): + return 0 + + def convex_conjugate(self, x): + + ''' This is the support function sup which in fact is the + indicator function for the set = {0} + So 0 if x=0, or inf if x neq 0 + ''' + + if x.shape[0]==1: + return x.maximum(0).sum() + else: + if isinstance(x, BlockDataContainer): + return x.get_item(0).maximum(0).sum() + x.get_item(1).maximum(0).sum() + else: + return x.maximum(0).sum() + x.maximum(0).sum() + + def proximal(self, x, tau, out=None): + if out is None: + return x.copy() + else: + out.fill(x) + + def proximal_conjugate(self, x, tau, out = None): + if out is None: + return 0 + else: + return 0 diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/__init__.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/__init__.py new file mode 100644 index 0000000..a82ee3e --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +from .Function import Function +from .ZeroFunction import ZeroFunction +from .L1Norm import L1Norm +from .L2NormSquared import L2NormSquared +from .ScaledFunction import ScaledFunction +from .BlockFunction import BlockFunction +from .FunctionOperatorComposition import FunctionOperatorComposition +from .MixedL21Norm import MixedL21Norm +from .IndicatorBox import IndicatorBox +from .KullbackLeibler import KullbackLeibler +from .Norm2Sq import Norm2sq diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/untitled0.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/untitled0.py new file mode 100644 index 0000000..3508f8e --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/untitled0.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Tue Apr 16 10:30:46 2019 + +@author: evangelos +""" + +import odl + + +# --- Set up problem definition --- # + + +# Define function space: discretized functions on the rectangle +# [-20, 20]^2 with 300 samples per dimension. +space = odl.uniform_discr( + min_pt=[-20, -20], max_pt=[20, 20], shape=[300, 300]) + +# Create phantom +data = odl.phantom.shepp_logan(space, modified=True) +data = odl.phantom.salt_pepper_noise(data) + +# Create gradient operator +grad = odl.Gradient(space) + + +# --- Set up the inverse problem --- # + +# Create data discrepancy by translating the l1 norm +l1_norm = odl.solvers.L1Norm(space) +data_discrepancy = l1_norm.translated(data) + +# l2-squared norm of gradient +regularizer = 0.05 * odl.solvers.L2NormSquared(grad.range) * grad + +# --- Select solver parameters and solve using proximal gradient --- # + +# Select step-size that guarantees convergence. +gamma = 0.01 + +# Optionally pass callback to the solver to display intermediate results +callback = (odl.solvers.CallbackPrintIteration() & + odl.solvers.CallbackShow()) + +# Run the algorithm (ISTA) +x = space.zero() +odl.solvers.proximal_gradient( + x, f=data_discrepancy, g=regularizer, niter=200, gamma=gamma, + callback=callback) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockOperator.py new file mode 100644 index 0000000..1d77510 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockOperator.py @@ -0,0 +1,366 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Feb 14 12:36:40 2019 + +@author: ofn77899 +""" +#from ccpi.optimisation.ops import Operator +import numpy +from numbers import Number +import functools +from ccpi.framework import AcquisitionData, ImageData, BlockDataContainer, DataContainer +from ccpi.optimisation.operators import Operator, LinearOperator +from ccpi.optimisation.operators.BlockScaledOperator import BlockScaledOperator +from ccpi.framework import BlockGeometry + +class BlockOperator(Operator): + '''A Block matrix containing Operators + + The Block Framework is a generic strategy to treat variational problems in the + following form: + + .. math:: + + min Regulariser + Fidelity + + + BlockOperators have a generic shape M x N, and when applied on an + Nx1 BlockDataContainer, will yield and Mx1 BlockDataContainer. + Notice: BlockDatacontainer are only allowed to have the shape of N x 1, with + N rows and 1 column. + + User may specify the shape of the block, by default is a row vector + + Operators in a Block are required to have the same domain column-wise and the + same range row-wise. + ''' + __array_priority__ = 1 + def __init__(self, *args, **kwargs): + ''' + Class creator + + Note: + Do not include the `self` parameter in the ``Args`` section. + + Args: + :param: vararg (Operator): Operators in the block. + :param: shape (:obj:`tuple`, optional): If shape is passed the Operators in + vararg are considered input in a row-by-row fashion. + Shape and number of Operators must match. + + Example: + BlockOperator(op0,op1) results in a row block + BlockOperator(op0,op1,shape=(1,2)) results in a column block + ''' + self.operators = args + shape = kwargs.get('shape', None) + if shape is None: + shape = (len(args),1) + self.shape = shape + n_elements = functools.reduce(lambda x,y: x*y, shape, 1) + if len(args) != n_elements: + raise ValueError( + 'Dimension and size do not match: expected {} got {}' + .format(n_elements,len(args))) + # test if operators are compatible + if not self.column_wise_compatible(): + raise ValueError('Operators in each column must have the same domain') + if not self.row_wise_compatible(): + raise ValueError('Operators in each row must have the same range') + + def column_wise_compatible(self): + '''Operators in a Block should have the same domain per column''' + rows, cols = self.shape + compatible = True + for col in range(cols): + column_compatible = True + for row in range(1,rows): + dg0 = self.get_item(row-1,col).domain_geometry() + dg1 = self.get_item(row,col).domain_geometry() + column_compatible = dg0.__dict__ == dg1.__dict__ and column_compatible + compatible = compatible and column_compatible + return compatible + + def row_wise_compatible(self): + '''Operators in a Block should have the same range per row''' + rows, cols = self.shape + compatible = True + for row in range(rows): + row_compatible = True + for col in range(1,cols): + dg0 = self.get_item(row,col-1).range_geometry() + dg1 = self.get_item(row,col).range_geometry() + row_compatible = dg0.__dict__ == dg1.__dict__ and row_compatible + compatible = compatible and row_compatible + return compatible + + def get_item(self, row, col): + '''returns the Operator at specified row and col''' + if row > self.shape[0]: + raise ValueError('Requested row {} > max {}'.format(row, self.shape[0])) + if col > self.shape[1]: + raise ValueError('Requested col {} > max {}'.format(col, self.shape[1])) + + index = row*self.shape[1]+col + return self.operators[index] + + def norm(self): + norm = [op.norm()**2 for op in self.operators] + return numpy.sqrt(sum(norm)) + + def direct(self, x, out=None): + '''Direct operation for the BlockOperator + + BlockOperator work on BlockDataContainer, but they will work on DataContainers + and inherited classes by simple wrapping the input in a BlockDataContainer of shape (1,1) + ''' + + if not isinstance (x, BlockDataContainer): + x_b = BlockDataContainer(x) + else: + x_b = x + shape = self.get_output_shape(x_b.shape) + res = [] + + if out is None: + + for row in range(self.shape[0]): + for col in range(self.shape[1]): + if col == 0: + prod = self.get_item(row,col).direct(x_b.get_item(col)) + else: + prod += self.get_item(row,col).direct(x_b.get_item(col)) + res.append(prod) + return BlockDataContainer(*res, shape=shape) + + else: + + tmp = self.range_geometry().allocate() + for row in range(self.shape[0]): + for col in range(self.shape[1]): + if col == 0: + self.get_item(row,col).direct( + x_b.get_item(col), + out=out.get_item(row)) + else: + a = out.get_item(row) + self.get_item(row,col).direct( + x_b.get_item(col), + out=tmp.get_item(row)) + a += tmp.get_item(row) + + def adjoint(self, x, out=None): + '''Adjoint operation for the BlockOperator + + BlockOperator may contain both LinearOperator and Operator + This method exists in BlockOperator as it is not known what type of + Operator it will contain. + + BlockOperator work on BlockDataContainer, but they will work on DataContainers + and inherited classes by simple wrapping the input in a BlockDataContainer of shape (1,1) + + Raises: ValueError if the contained Operators are not linear + ''' + if not self.is_linear(): + raise ValueError('Not all operators in Block are linear.') + if not isinstance (x, BlockDataContainer): + x_b = BlockDataContainer(x) + else: + x_b = x + shape = self.get_output_shape(x_b.shape, adjoint=True) + if out is None: + res = [] + for col in range(self.shape[1]): + for row in range(self.shape[0]): + if row == 0: + prod = self.get_item(row, col).adjoint(x_b.get_item(row)) + else: + prod += self.get_item(row, col).adjoint(x_b.get_item(row)) + res.append(prod) + if self.shape[1]==1: + return ImageData(*res) + else: + return BlockDataContainer(*res, shape=shape) + else: + #tmp = self.domain_geometry().allocate() + + for col in range(self.shape[1]): + for row in range(self.shape[0]): + if row == 0: + if issubclass(out.__class__, DataContainer): + self.get_item(row, col).adjoint( + x_b.get_item(row), + out=out) + else: + op = self.get_item(row,col) + self.get_item(row, col).adjoint( + x_b.get_item(row), + out=out.get_item(col)) + else: + if issubclass(out.__class__, DataContainer): + out += self.get_item(row,col).adjoint( + x_b.get_item(row)) + else: + a = out.get_item(col) + a += self.get_item(row,col).adjoint( + x_b.get_item(row), + ) + def is_linear(self): + '''returns whether all the elements of the BlockOperator are linear''' + return functools.reduce(lambda x, y: x and y.is_linear(), self.operators, True) + + def get_output_shape(self, xshape, adjoint=False): + '''returns the shape of the output BlockDataContainer + + A(N,M) direct u(M,1) -> N,1 + A(N,M)^T adjoint u(N,1) -> M,1 + ''' + rows , cols = self.shape + xrows, xcols = xshape + if xcols != 1: + raise ValueError('BlockDataContainer cannot have more than 1 column') + if adjoint: + if rows != xrows: + raise ValueError('Incompatible shapes {} {}'.format(self.shape, xshape)) + return (cols,xcols) + if cols != xrows: + raise ValueError('Incompatible shapes {} {}'.format((rows,cols), xshape)) + return (rows,xcols) + + def __rmul__(self, scalar): + '''Defines the left multiplication with a scalar + + Args: scalar (number or iterable containing numbers): + + Returns: a block operator with Scaled Operators inside''' + if isinstance (scalar, list) or isinstance(scalar, tuple) or \ + isinstance(scalar, numpy.ndarray): + if len(scalar) != len(self.operators): + raise ValueError('dimensions of scalars and operators do not match') + scalars = scalar + else: + scalars = [scalar for _ in self.operators] + # create a list of ScaledOperator-s + ops = [ v * op for v,op in zip(scalars, self.operators)] + #return BlockScaledOperator(self, scalars ,shape=self.shape) + return type(self)(*ops, shape=self.shape) + @property + def T(self): + '''Return the transposed of self + + input in a row-by-row''' + newshape = (self.shape[1], self.shape[0]) + oplist = [] + for col in range(newshape[1]): + for row in range(newshape[0]): + oplist.append(self.get_item(col,row)) + return type(self)(*oplist, shape=newshape) + + def domain_geometry(self): + '''returns the domain of the BlockOperator + + If the shape of the BlockOperator is (N,1) the domain is a ImageGeometry or AcquisitionGeometry. + Otherwise it is a BlockGeometry. + ''' + if self.shape[1] == 1: + # column BlockOperator + return self.get_item(0,0).domain_geometry() + else: + shape = (self.shape[0], 1) + return BlockGeometry(*[el.domain_geometry() for el in self.operators], + shape=shape) + + def range_geometry(self): + '''returns the range of the BlockOperator''' + shape = (self.shape[1], 1) + return BlockGeometry(*[el.range_geometry() for el in self.operators], + shape=shape) + + def sum_abs_row(self): + + res = [] + for row in range(self.shape[0]): + for col in range(self.shape[1]): + if col == 0: + prod = self.get_item(row,col).sum_abs_row() + else: + prod += self.get_item(row,col).sum_abs_row() + res.append(prod) + + if self.shape[1]==1: + tmp = sum(res) + return ImageData(tmp) + else: + + return BlockDataContainer(*res) + + def sum_abs_col(self): + + res = [] + for row in range(self.shape[0]): + for col in range(self.shape[1]): + if col == 0: + prod = self.get_item(row, col).sum_abs_col() + else: + prod += self.get_item(row, col).sum_abs_col() + res.append(prod) + + return BlockDataContainer(*res) + + + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + from ccpi.optimisation.operators import Gradient, Identity, SparseFiniteDiff + + + M, N = 4, 3 + ig = ImageGeometry(M, N) + arr = ig.allocate('random_int') + + G = Gradient(ig) + Id = Identity(ig) + + B = BlockOperator(G, Id) + + print(B.sum_abs_row()) +# + Gx = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + Gy = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + + d1 = abs(Gx.matrix()).toarray().sum(axis=0) + d2 = abs(Gy.matrix()).toarray().sum(axis=0) + d3 = abs(Id.matrix()).toarray().sum(axis=0) + + + d_res = numpy.reshape(d1 + d2 + d3, ig.shape, 'F') + + print(d_res) +# + z1 = abs(Gx.matrix()).toarray().sum(axis=1) + z2 = abs(Gy.matrix()).toarray().sum(axis=1) + z3 = abs(Id.matrix()).toarray().sum(axis=1) +# + z_res = BlockDataContainer(BlockDataContainer(ImageData(numpy.reshape(z2, ig.shape, 'F')),\ + ImageData(numpy.reshape(z1, ig.shape, 'F'))),\ + ImageData(numpy.reshape(z3, ig.shape, 'F'))) +# + ttt = B.sum_abs_col() +# + #TODO this is not working +# numpy.testing.assert_array_almost_equal(z_res[0][0].as_array(), ttt[0][0].as_array(), decimal=4) +# numpy.testing.assert_array_almost_equal(z_res[0][1].as_array(), ttt[0][1].as_array(), decimal=4) +# numpy.testing.assert_array_almost_equal(z_res[1].as_array(), ttt[1].as_array(), decimal=4) + + + u = ig.allocate('random_int') + + z1 = B.direct(u) + res = B.range_geometry().allocate() + + B.direct(u, out = res) + + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockScaledOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockScaledOperator.py new file mode 100644 index 0000000..aeb6c53 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockScaledOperator.py @@ -0,0 +1,67 @@ +from numbers import Number +import numpy +from ccpi.optimisation.operators import ScaledOperator +import functools + +class BlockScaledOperator(ScaledOperator): + '''ScaledOperator + + A class to represent the scalar multiplication of an Operator with a scalar. + It holds an operator and a scalar. Basically it returns the multiplication + of the result of direct and adjoint of the operator with the scalar. + For the rest it behaves like the operator it holds. + + Args: + operator (Operator): a Operator or LinearOperator + scalar (Number): a scalar multiplier + Example: + The scaled operator behaves like the following: + sop = ScaledOperator(operator, scalar) + sop.direct(x) = scalar * operator.direct(x) + sop.adjoint(x) = scalar * operator.adjoint(x) + sop.norm() = operator.norm() + sop.range_geometry() = operator.range_geometry() + sop.domain_geometry() = operator.domain_geometry() + ''' + def __init__(self, operator, scalar, shape=None): + if shape is None: + shape = operator.shape + + if isinstance(scalar, (list, tuple, numpy.ndarray)): + size = functools.reduce(lambda x,y:x*y, shape, 1) + if len(scalar) != size: + raise ValueError('Scalar and operators size do not match: {}!={}' + .format(len(scalar), len(operator))) + self.scalar = scalar[:] + print ("BlockScaledOperator ", self.scalar) + elif isinstance (scalar, Number): + self.scalar = scalar + else: + raise TypeError('expected scalar to be a number of an iterable: got {}'.format(type(scalar))) + self.operator = operator + self.shape = shape + def direct(self, x, out=None): + print ("BlockScaledOperator self.scalar", self.scalar) + #print ("self.scalar", self.scalar[0]* x.get_item(0).as_array()) + return self.scalar * (self.operator.direct(x, out=out)) + def adjoint(self, x, out=None): + if self.operator.is_linear(): + return self.scalar * self.operator.adjoint(x, out=out) + else: + raise TypeError('Operator is not linear') + def norm(self): + return numpy.abs(self.scalar) * self.operator.norm() + def range_geometry(self): + return self.operator.range_geometry() + def domain_geometry(self): + return self.operator.domain_geometry() + @property + def T(self): + '''Return the transposed of self''' + #print ("transpose before" , self.shape) + #shape = (self.shape[1], self.shape[0]) + ##self.shape = shape + ##self.operator.shape = shape + #print ("transpose" , shape) + #return self + return type(self)(self.operator.T, self.scalar) \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator.py new file mode 100644 index 0000000..835f96d --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 1 22:51:17 2019 + +@author: evangelos +""" + +from ccpi.optimisation.operators import LinearOperator +from ccpi.optimisation.ops import PowerMethodNonsquare +from ccpi.framework import ImageData, BlockDataContainer +import numpy as np + +class FiniteDiff(LinearOperator): + + # Works for Neum/Symmetric & periodic boundary conditions + # TODO add central differences??? + # TODO not very well optimised, too many conditions + # TODO add discretisation step, should get that from imageGeometry + + # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] + # Grad_order = ['channels', 'direction_y', 'direction_x'] + # Grad_order = ['direction_z', 'direction_y', 'direction_x'] + # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] + + def __init__(self, gm_domain, gm_range=None, direction=0, bnd_cond = 'Neumann'): + '''''' + super(FiniteDiff, self).__init__() + '''FIXME: domain and range should be geometries''' + self.gm_domain = gm_domain + self.gm_range = gm_range + + self.direction = direction + self.bnd_cond = bnd_cond + + # Domain Geometry = Range Geometry if not stated + if self.gm_range is None: + self.gm_range = self.gm_domain + # check direction and "length" of geometry + if self.direction + 1 > len(self.gm_domain.shape): + raise ValueError('Gradient directions more than geometry domain') + + #self.voxel_size = kwargs.get('voxel_size',1) + # this wrongly assumes a homogeneous voxel size + self.voxel_size = self.gm_domain.voxel_size_x + + + def direct(self, x, out=None): + + x_asarr = x.as_array() + x_sz = len(x.shape) + + if out is None: + out = np.zeros_like(x_asarr) + else: + out = out.as_array() + out[:]=0 + + + + ######################## Direct for 2D ############################### + if x_sz == 2: + + if self.direction == 1: + + np.subtract( x_asarr[:,1:], x_asarr[:,0:-1], out = out[:,0:-1] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0], x_asarr[:,-1], out = out[:,-1] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 0: + + np.subtract( x_asarr[1:], x_asarr[0:-1], out = out[0:-1,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:], x_asarr[-1,:], out = out[-1,:] ) + else: + raise ValueError('No valid boundary conditions') + + ######################## Direct for 3D ############################### + elif x_sz == 3: + + if self.direction == 0: + + np.subtract( x_asarr[1:,:,:], x_asarr[0:-1,:,:], out = out[0:-1,:,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:,:], x_asarr[-1,:,:], out = out[-1,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 1: + + np.subtract( x_asarr[:,1:,:], x_asarr[:,0:-1,:], out = out[:,0:-1,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0,:], x_asarr[:,-1,:], out = out[:,-1,:] ) + else: + raise ValueError('No valid boundary conditions') + + + if self.direction == 2: + + np.subtract( x_asarr[:,:,1:], x_asarr[:,:,0:-1], out = out[:,:,0:-1] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,0], x_asarr[:,:,-1], out = out[:,:,-1] ) + else: + raise ValueError('No valid boundary conditions') + + ######################## Direct for 4D ############################### + elif x_sz == 4: + + if self.direction == 0: + np.subtract( x_asarr[1:,:,:,:], x_asarr[0:-1,:,:,:], out = out[0:-1,:,:,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:,:,:], x_asarr[-1,:,:,:], out = out[-1,:,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 1: + np.subtract( x_asarr[:,1:,:,:], x_asarr[:,0:-1,:,:], out = out[:,0:-1,:,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0,:,:], x_asarr[:,-1,:,:], out = out[:,-1,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 2: + np.subtract( x_asarr[:,:,1:,:], x_asarr[:,:,0:-1,:], out = out[:,:,0:-1,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,0,:], x_asarr[:,:,-1,:], out = out[:,:,-1,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 3: + np.subtract( x_asarr[:,:,:,1:], x_asarr[:,:,:,0:-1], out = out[:,:,:,0:-1] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,:,0], x_asarr[:,:,:,-1], out = out[:,:,:,-1] ) + else: + raise ValueError('No valid boundary conditions') + + else: + raise NotImplementedError + +# res = out #/self.voxel_size + return type(x)(out) + + + def adjoint(self, x, out=None): + + x_asarr = x.as_array() + x_sz = len(x.shape) + + if out is None: + out = np.zeros_like(x_asarr) + else: + out = out.as_array() + out[:]=0 + + + ######################## Adjoint for 2D ############################### + if x_sz == 2: + + if self.direction == 1: + + np.subtract( x_asarr[:,1:], x_asarr[:,0:-1], out = out[:,1:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,0], 0, out = out[:,0] ) + np.subtract( -x_asarr[:,-2], 0, out = out[:,-1] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0], x_asarr[:,-1], out = out[:,0] ) + + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 0: + + np.subtract( x_asarr[1:,:], x_asarr[0:-1,:], out = out[1:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[0,:], 0, out = out[0,:] ) + np.subtract( -x_asarr[-2,:], 0, out = out[-1,:] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:], x_asarr[-1,:], out = out[0,:] ) + + else: + raise ValueError('No valid boundary conditions') + + ######################## Adjoint for 3D ############################### + elif x_sz == 3: + + if self.direction == 0: + + np.subtract( x_asarr[1:,:,:], x_asarr[0:-1,:,:], out = out[1:,:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[0,:,:], 0, out = out[0,:,:] ) + np.subtract( -x_asarr[-2,:,:], 0, out = out[-1,:,:] ) + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:,:], x_asarr[-1,:,:], out = out[0,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 1: + np.subtract( x_asarr[:,1:,:], x_asarr[:,0:-1,:], out = out[:,1:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,0,:], 0, out = out[:,0,:] ) + np.subtract( -x_asarr[:,-2,:], 0, out = out[:,-1,:] ) + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0,:], x_asarr[:,-1,:], out = out[:,0,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 2: + np.subtract( x_asarr[:,:,1:], x_asarr[:,:,0:-1], out = out[:,:,1:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,:,0], 0, out = out[:,:,0] ) + np.subtract( -x_asarr[:,:,-2], 0, out = out[:,:,-1] ) + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,0], x_asarr[:,:,-1], out = out[:,:,0] ) + else: + raise ValueError('No valid boundary conditions') + + ######################## Adjoint for 4D ############################### + elif x_sz == 4: + + if self.direction == 0: + np.subtract( x_asarr[1:,:,:,:], x_asarr[0:-1,:,:,:], out = out[1:,:,:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[0,:,:,:], 0, out = out[0,:,:,:] ) + np.subtract( -x_asarr[-2,:,:,:], 0, out = out[-1,:,:,:] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:,:,:], x_asarr[-1,:,:,:], out = out[0,:,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 1: + np.subtract( x_asarr[:,1:,:,:], x_asarr[:,0:-1,:,:], out = out[:,1:,:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,0,:,:], 0, out = out[:,0,:,:] ) + np.subtract( -x_asarr[:,-2,:,:], 0, out = out[:,-1,:,:] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0,:,:], x_asarr[:,-1,:,:], out = out[:,0,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + + if self.direction == 2: + np.subtract( x_asarr[:,:,1:,:], x_asarr[:,:,0:-1,:], out = out[:,:,1:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,:,0,:], 0, out = out[:,:,0,:] ) + np.subtract( -x_asarr[:,:,-2,:], 0, out = out[:,:,-1,:] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,0,:], x_asarr[:,:,-1,:], out = out[:,:,0,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 3: + np.subtract( x_asarr[:,:,:,1:], x_asarr[:,:,:,0:-1], out = out[:,:,:,1:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,:,:,0], 0, out = out[:,:,:,0] ) + np.subtract( -x_asarr[:,:,:,-2], 0, out = out[:,:,:,-1] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,:,0], x_asarr[:,:,:,-1], out = out[:,:,:,0] ) + else: + raise ValueError('No valid boundary conditions') + + else: + raise NotImplementedError + + out *= -1 #/self.voxel_size + return type(x)(out) + + def range_geometry(self): + '''Returns the range geometry''' + return self.gm_range + + def domain_geometry(self): + '''Returns the domain geometry''' + return self.gm_domain + + def norm(self): + x0 = self.gm_domain.allocate('random_int') + self.s1, sall, svec = PowerMethodNonsquare(self, 25, x0) + return self.s1 + + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + import numpy + + N, M = 2, 3 + + ig = ImageGeometry(N, M) + + + FD = FiniteDiff(ig, direction = 1, bnd_cond = 'Neumann') + u = FD.domain_geometry().allocate('random_int') + + res = FD.domain_geometry().allocate() + res1 = FD.range_geometry().allocate() + FD.direct(u, out=res) + + z = FD.direct(u) +# print(z.as_array(), res.as_array()) + + for i in range(10): +# + z1 = FD.direct(u) + FD.direct(u, out=res) + + u = ig.allocate('random_int') + res = u + z1 = u + numpy.testing.assert_array_almost_equal(z1.as_array(), \ + res.as_array(), decimal=4) + +# print(z1.as_array(), res.as_array()) + z2 = FD.adjoint(z1) + FD.adjoint(z1, out=res1) + numpy.testing.assert_array_almost_equal(z2.as_array(), \ + res1.as_array(), decimal=4) + + + + + + + + +# w = G.range_geometry().allocate('random_int') + + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py new file mode 100644 index 0000000..387fb4b --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py @@ -0,0 +1,374 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 1 22:51:17 2019 + +@author: evangelos +""" + +from ccpi.optimisation.operators import LinearOperator +from ccpi.optimisation.ops import PowerMethodNonsquare +from ccpi.framework import ImageData, BlockDataContainer +import numpy as np + +class FiniteDiff(LinearOperator): + + # Works for Neum/Symmetric & periodic boundary conditions + # TODO add central differences??? + # TODO not very well optimised, too many conditions + # TODO add discretisation step, should get that from imageGeometry + + # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] + # Grad_order = ['channels', 'direction_y', 'direction_x'] + # Grad_order = ['direction_z', 'direction_y', 'direction_x'] + # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] + + def __init__(self, gm_domain, gm_range=None, direction=0, bnd_cond = 'Neumann'): + '''''' + super(FiniteDiff, self).__init__() + '''FIXME: domain and range should be geometries''' + self.gm_domain = gm_domain + self.gm_range = gm_range + + self.direction = direction + self.bnd_cond = bnd_cond + + # Domain Geometry = Range Geometry if not stated + if self.gm_range is None: + self.gm_range = self.gm_domain + # check direction and "length" of geometry + if self.direction + 1 > len(self.gm_domain.shape): + raise ValueError('Gradient directions more than geometry domain') + + #self.voxel_size = kwargs.get('voxel_size',1) + # this wrongly assumes a homogeneous voxel size + self.voxel_size = self.gm_domain.voxel_size_x + + + def direct(self, x, out=None): + + x_asarr = x.as_array() + x_sz = len(x.shape) + + if out is None: + out = np.zeros_like(x_asarr) + fd_arr = out + else: + fd_arr = out.as_array() +# fd_arr[:]=0 + +# if out is None: +# out = self.gm_domain.allocate().as_array() +# +# fd_arr = out.as_array() +# fd_arr = self.gm_domain.allocate().as_array() + + ######################## Direct for 2D ############################### + if x_sz == 2: + + if self.direction == 1: + + np.subtract( x_asarr[:,1:], x_asarr[:,0:-1], out = fd_arr[:,0:-1] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0], x_asarr[:,-1], out = fd_arr[:,-1] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 0: + + np.subtract( x_asarr[1:], x_asarr[0:-1], out = fd_arr[0:-1,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:], x_asarr[-1,:], out = fd_arr[-1,:] ) + else: + raise ValueError('No valid boundary conditions') + + ######################## Direct for 3D ############################### + elif x_sz == 3: + + if self.direction == 0: + + np.subtract( x_asarr[1:,:,:], x_asarr[0:-1,:,:], out = fd_arr[0:-1,:,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:,:], x_asarr[-1,:,:], out = fd_arr[-1,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 1: + + np.subtract( x_asarr[:,1:,:], x_asarr[:,0:-1,:], out = fd_arr[:,0:-1,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0,:], x_asarr[:,-1,:], out = fd_arr[:,-1,:] ) + else: + raise ValueError('No valid boundary conditions') + + + if self.direction == 2: + + np.subtract( x_asarr[:,:,1:], x_asarr[:,:,0:-1], out = fd_arr[:,:,0:-1] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,0], x_asarr[:,:,-1], out = fd_arr[:,:,-1] ) + else: + raise ValueError('No valid boundary conditions') + + ######################## Direct for 4D ############################### + elif x_sz == 4: + + if self.direction == 0: + np.subtract( x_asarr[1:,:,:,:], x_asarr[0:-1,:,:,:], out = fd_arr[0:-1,:,:,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:,:,:], x_asarr[-1,:,:,:], out = fd_arr[-1,:,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 1: + np.subtract( x_asarr[:,1:,:,:], x_asarr[:,0:-1,:,:], out = fd_arr[:,0:-1,:,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0,:,:], x_asarr[:,-1,:,:], out = fd_arr[:,-1,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 2: + np.subtract( x_asarr[:,:,1:,:], x_asarr[:,:,0:-1,:], out = fd_arr[:,:,0:-1,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,0,:], x_asarr[:,:,-1,:], out = fd_arr[:,:,-1,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 3: + np.subtract( x_asarr[:,:,:,1:], x_asarr[:,:,:,0:-1], out = fd_arr[:,:,:,0:-1] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,:,0], x_asarr[:,:,:,-1], out = fd_arr[:,:,:,-1] ) + else: + raise ValueError('No valid boundary conditions') + + else: + raise NotImplementedError + +# res = out #/self.voxel_size + return type(x)(out) + + + def adjoint(self, x, out=None): + + x_asarr = x.as_array() + #x_asarr = x + x_sz = len(x.shape) + + if out is None: + out = np.zeros_like(x_asarr) + fd_arr = out + else: + fd_arr = out.as_array() + +# if out is None: +# out = self.gm_domain.allocate().as_array() +# fd_arr = out +# else: +# fd_arr = out.as_array() +## fd_arr = self.gm_domain.allocate().as_array() + + ######################## Adjoint for 2D ############################### + if x_sz == 2: + + if self.direction == 1: + + np.subtract( x_asarr[:,1:], x_asarr[:,0:-1], out = fd_arr[:,1:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,0], 0, out = fd_arr[:,0] ) + np.subtract( -x_asarr[:,-2], 0, out = fd_arr[:,-1] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0], x_asarr[:,-1], out = fd_arr[:,0] ) + + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 0: + + np.subtract( x_asarr[1:,:], x_asarr[0:-1,:], out = fd_arr[1:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[0,:], 0, out = fd_arr[0,:] ) + np.subtract( -x_asarr[-2,:], 0, out = fd_arr[-1,:] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:], x_asarr[-1,:], out = fd_arr[0,:] ) + + else: + raise ValueError('No valid boundary conditions') + + ######################## Adjoint for 3D ############################### + elif x_sz == 3: + + if self.direction == 0: + + np.subtract( x_asarr[1:,:,:], x_asarr[0:-1,:,:], out = fd_arr[1:,:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[0,:,:], 0, out = fd_arr[0,:,:] ) + np.subtract( -x_asarr[-2,:,:], 0, out = fd_arr[-1,:,:] ) + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:,:], x_asarr[-1,:,:], out = fd_arr[0,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 1: + np.subtract( x_asarr[:,1:,:], x_asarr[:,0:-1,:], out = fd_arr[:,1:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,0,:], 0, out = fd_arr[:,0,:] ) + np.subtract( -x_asarr[:,-2,:], 0, out = fd_arr[:,-1,:] ) + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0,:], x_asarr[:,-1,:], out = fd_arr[:,0,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 2: + np.subtract( x_asarr[:,:,1:], x_asarr[:,:,0:-1], out = fd_arr[:,:,1:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,:,0], 0, out = fd_arr[:,:,0] ) + np.subtract( -x_asarr[:,:,-2], 0, out = fd_arr[:,:,-1] ) + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,0], x_asarr[:,:,-1], out = fd_arr[:,:,0] ) + else: + raise ValueError('No valid boundary conditions') + + ######################## Adjoint for 4D ############################### + elif x_sz == 4: + + if self.direction == 0: + np.subtract( x_asarr[1:,:,:,:], x_asarr[0:-1,:,:,:], out = fd_arr[1:,:,:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[0,:,:,:], 0, out = fd_arr[0,:,:,:] ) + np.subtract( -x_asarr[-2,:,:,:], 0, out = fd_arr[-1,:,:,:] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:,:,:], x_asarr[-1,:,:,:], out = fd_arr[0,:,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 1: + np.subtract( x_asarr[:,1:,:,:], x_asarr[:,0:-1,:,:], out = fd_arr[:,1:,:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,0,:,:], 0, out = fd_arr[:,0,:,:] ) + np.subtract( -x_asarr[:,-2,:,:], 0, out = fd_arr[:,-1,:,:] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0,:,:], x_asarr[:,-1,:,:], out = fd_arr[:,0,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + + if self.direction == 2: + np.subtract( x_asarr[:,:,1:,:], x_asarr[:,:,0:-1,:], out = fd_arr[:,:,1:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,:,0,:], 0, out = fd_arr[:,:,0,:] ) + np.subtract( -x_asarr[:,:,-2,:], 0, out = fd_arr[:,:,-1,:] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,0,:], x_asarr[:,:,-1,:], out = fd_arr[:,:,0,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 3: + np.subtract( x_asarr[:,:,:,1:], x_asarr[:,:,:,0:-1], out = fd_arr[:,:,:,1:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,:,:,0], 0, out = fd_arr[:,:,:,0] ) + np.subtract( -x_asarr[:,:,:,-2], 0, out = fd_arr[:,:,:,-1] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,:,0], x_asarr[:,:,:,-1], out = fd_arr[:,:,:,0] ) + else: + raise ValueError('No valid boundary conditions') + + else: + raise NotImplementedError + + out *= -1 #/self.voxel_size + return type(x)(out) + + def range_geometry(self): + '''Returns the range geometry''' + return self.gm_range + + def domain_geometry(self): + '''Returns the domain geometry''' + return self.gm_domain + + def norm(self): + x0 = self.gm_domain.allocate() + x0.fill( np.random.random_sample(x0.shape) ) + self.s1, sall, svec = PowerMethodNonsquare(self, 25, x0) + return self.s1 + + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + import numpy + + N, M = 2, 3 + + ig = ImageGeometry(N, M) + + + FD = FiniteDiff(ig, direction = 0, bnd_cond = 'Neumann') + u = FD.domain_geometry().allocate('random_int') + + + res = FD.domain_geometry().allocate() + FD.direct(u, out=res) + + z = FD.direct(u) + print(z.as_array(), res.as_array()) + + for i in range(10): + + z1 = FD.direct(u) + FD.direct(u, out=res) + numpy.testing.assert_array_almost_equal(z1.as_array(), \ + res.as_array(), decimal=4) + + + + + + +# w = G.range_geometry().allocate('random_int') + + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py new file mode 100644 index 0000000..4935435 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 1 22:50:04 2019 + +@author: evangelos +""" + +from ccpi.optimisation.operators import Operator, LinearOperator, ScaledOperator +from ccpi.optimisation.ops import PowerMethodNonsquare +from ccpi.framework import ImageData, ImageGeometry, BlockGeometry, BlockDataContainer +import numpy +from ccpi.optimisation.operators import FiniteDiff, SparseFiniteDiff + +#%% + +class Gradient(LinearOperator): + + def __init__(self, gm_domain, bnd_cond = 'Neumann', **kwargs): + + super(Gradient, self).__init__() + + self.gm_domain = gm_domain # Domain of Grad Operator + + self.correlation = kwargs.get('correlation','Space') + + if self.correlation=='Space': + if self.gm_domain.channels>1: + self.gm_range = BlockGeometry(*[self.gm_domain for _ in range(self.gm_domain.length-1)] ) + self.ind = numpy.arange(1,self.gm_domain.length) + else: + self.gm_range = BlockGeometry(*[self.gm_domain for _ in range(self.gm_domain.length) ] ) + self.ind = numpy.arange(self.gm_domain.length) + elif self.correlation=='SpaceChannels': + if self.gm_domain.channels>1: + self.gm_range = BlockGeometry(*[self.gm_domain for _ in range(self.gm_domain.length)]) + self.ind = range(self.gm_domain.length) + else: + raise ValueError('No channels to correlate') + + self.bnd_cond = bnd_cond + + # Call FiniteDiff operator + + self.FD = FiniteDiff(self.gm_domain, direction = 0, bnd_cond = self.bnd_cond) + + + def direct(self, x, out=None): + + + if out is not None: + + for i in range(self.gm_range.shape[0]): + self.FD.direction = self.ind[i] + self.FD.direct(x, out = out[i]) + else: + tmp = self.gm_range.allocate() + for i in range(tmp.shape[0]): + self.FD.direction=self.ind[i] + tmp.get_item(i).fill(self.FD.direct(x)) + return tmp + + def adjoint(self, x, out=None): + + if out is not None: + + tmp = self.gm_domain.allocate() + for i in range(x.shape[0]): + self.FD.direction=self.ind[i] + self.FD.adjoint(x.get_item(i), out = tmp) + if i == 0: + out.fill(tmp) + else: + out += tmp + else: + tmp = self.gm_domain.allocate() + for i in range(x.shape[0]): + self.FD.direction=self.ind[i] + + tmp += self.FD.adjoint(x.get_item(i)) + return tmp + + + def domain_geometry(self): + return self.gm_domain + + def range_geometry(self): + return self.gm_range + + def norm(self): + + x0 = self.gm_domain.allocate('random') + self.s1, sall, svec = PowerMethodNonsquare(self, 10, x0) + return self.s1 + + def __rmul__(self, scalar): + return ScaledOperator(self, scalar) + + ########################################################################### + ############### For preconditioning ###################################### + ########################################################################### + def matrix(self): + + tmp = self.gm_range.allocate() + + mat = [] + for i in range(tmp.shape[0]): + + spMat = SparseFiniteDiff(self.gm_domain, direction=self.ind[i], bnd_cond=self.bnd_cond) + mat.append(spMat.matrix()) + + return BlockDataContainer(*mat) + + + def sum_abs_row(self): + + tmp = self.gm_range.allocate() + res = self.gm_domain.allocate() + for i in range(tmp.shape[0]): + spMat = SparseFiniteDiff(self.gm_domain, direction=self.ind[i], bnd_cond=self.bnd_cond) + res += spMat.sum_abs_row() + return res + + def sum_abs_col(self): + + tmp = self.gm_range.allocate() + res = [] + for i in range(tmp.shape[0]): + spMat = SparseFiniteDiff(self.gm_domain, direction=self.ind[i], bnd_cond=self.bnd_cond) + res.append(spMat.sum_abs_col()) + return BlockDataContainer(*res) + + +if __name__ == '__main__': + + + from ccpi.optimisation.operators import Identity, BlockOperator + + + M, N = 2, 3 + ig = ImageGeometry(M, N) + arr = ig.allocate('random_int' ) + + # check direct of Gradient and sparse matrix + G = Gradient(ig) + G_sp = G.matrix() + + res1 = G.direct(arr) + res1y = numpy.reshape(G_sp[0].toarray().dot(arr.as_array().flatten('F')), ig.shape, 'F') + + print(res1[0].as_array()) + print(res1y) + + res1x = numpy.reshape(G_sp[1].toarray().dot(arr.as_array().flatten('F')), ig.shape, 'F') + + print(res1[1].as_array()) + print(res1x) + + #check sum abs row + conc_spmat = numpy.abs(numpy.concatenate( (G_sp[0].toarray(), G_sp[1].toarray() ))) + print(numpy.reshape(conc_spmat.sum(axis=0), ig.shape, 'F')) + print(G.sum_abs_row().as_array()) + + print(numpy.reshape(conc_spmat.sum(axis=1), ((2,) + ig.shape), 'F')) + + print(G.sum_abs_col()[0].as_array()) + print(G.sum_abs_col()[1].as_array()) + + # Check Blockoperator sum abs col and row + + op1 = Gradient(ig) + op2 = Identity(ig) + + B = BlockOperator( op1, op2) + + Brow = B.sum_abs_row() + Bcol = B.sum_abs_col() + + concB = numpy.concatenate( (numpy.abs(numpy.concatenate( (G_sp[0].toarray(), G_sp[1].toarray() ))), op2.matrix().toarray())) + + print(numpy.reshape(concB.sum(axis=0), ig.shape, 'F')) + print(Brow.as_array()) + + print(numpy.reshape(concB.sum(axis=1)[0:12], ((2,) + ig.shape), 'F')) + print(Bcol[1].as_array()) + + +# print(numpy.concatene(G_sp[0].toarray()+ )) +# print(G_sp[1].toarray()) +# +# d1 = G.sum_abs_row() +# print(d1.as_array()) +# +# d2 = G_neum.sum_abs_col() +## print(d2) +# +# +# ########################################################### + a = BlockDataContainer( BlockDataContainer(arr, arr), arr) + b = BlockDataContainer( BlockDataContainer(arr+5, arr+3), arr+2) + c = a/b + + print(c[0][0].as_array(), (arr/(arr+5)).as_array()) + print(c[0][1].as_array(), (arr/(arr+3)).as_array()) + print(c[1].as_array(), (arr/(arr+2)).as_array()) + + + a1 = BlockDataContainer( arr, BlockDataContainer(arr, arr)) +# +# c1 = arr + a +# c2 = arr + a +# c2 = a1 + arr + + from ccpi.framework import ImageGeometry +# from ccpi.optimisation.operators import Gradient +# + N, M = 2, 3 +# + ig = ImageGeometry(N, M) +# + G = Gradient(ig) +# + u = G.domain_geometry().allocate('random_int') + w = G.range_geometry().allocate('random_int') + + + print( "################ without out #############") + + print( (G.direct(u)*w).sum(), (u*G.adjoint(w)).sum() ) + + + print( "################ with out #############") + + res = G.range_geometry().allocate() + res1 = G.domain_geometry().allocate() + G.direct(u, out = res) + G.adjoint(w, out = res1) + + print( (res*w).sum(), (u*res1).sum() ) + + + + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/IdentityOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/IdentityOperator.py new file mode 100644 index 0000000..a58a296 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/IdentityOperator.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 6 19:30:51 2019 + +@author: evangelos +""" + +from ccpi.optimisation.operators import LinearOperator +import scipy.sparse as sp +import numpy as np +from ccpi.framework import ImageData + + +class Identity(LinearOperator): + + def __init__(self, gm_domain, gm_range=None): + + self.gm_domain = gm_domain + self.gm_range = gm_range + if self.gm_range is None: + self.gm_range = self.gm_domain + + super(Identity, self).__init__() + + def direct(self,x,out=None): + if out is None: + return x.copy() + else: + out.fill(x) + + def adjoint(self,x, out=None): + if out is None: + return x.copy() + else: + out.fill(x) + + def norm(self): + return 1.0 + + def domain_geometry(self): + return self.gm_domain + + def range_geometry(self): + return self.gm_range + + def matrix(self): + + return sp.eye(np.prod(self.gm_domain.shape)) + + def sum_abs_row(self): + + return self.gm_domain.allocate(1)#ImageData(np.array(np.reshape(abs(self.matrix()).sum(axis=0), self.gm_domain.shape, 'F'))) + + def sum_abs_col(self): + + return self.gm_domain.allocate(1)#ImageData(np.array(np.reshape(abs(self.matrix()).sum(axis=1), self.gm_domain.shape, 'F'))) + + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + + M, N = 2, 3 + ig = ImageGeometry(M, N) + arr = ig.allocate('random_int') + + Id = Identity(ig) + d = Id.matrix() + print(d.toarray()) + + d1 = Id.sum_abs_col() + print(d1.as_array()) + + + + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperator.py new file mode 100644 index 0000000..e19304f --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperator.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Mar 5 15:57:52 2019 + +@author: ofn77899 +""" + +from ccpi.optimisation.operators import Operator + + +class LinearOperator(Operator): + '''A Linear Operator that maps from a space X <-> Y''' + def __init__(self): + super(LinearOperator, self).__init__() + def is_linear(self): + '''Returns if the operator is linear''' + return True + def adjoint(self,x, out=None): + '''returns the adjoint/inverse operation + + only available to linear operators''' + raise NotImplementedError diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/Operator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/Operator.py new file mode 100644 index 0000000..2d2089b --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/Operator.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Mar 5 15:55:56 2019 + +@author: ofn77899 +""" +from ccpi.optimisation.operators.ScaledOperator import ScaledOperator + +class Operator(object): + '''Operator that maps from a space X -> Y''' + def is_linear(self): + '''Returns if the operator is linear''' + return False + def direct(self,x, out=None): + '''Returns the application of the Operator on x''' + raise NotImplementedError + def norm(self): + '''Returns the norm of the Operator''' + raise NotImplementedError + def range_geometry(self): + '''Returns the range of the Operator: Y space''' + raise NotImplementedError + def domain_geometry(self): + '''Returns the domain of the Operator: X space''' + raise NotImplementedError + def __rmul__(self, scalar): + '''Defines the multiplication by a scalar on the left + + returns a ScaledOperator''' + return ScaledOperator(self, scalar) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ScaledOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ScaledOperator.py new file mode 100644 index 0000000..ba0049e --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ScaledOperator.py @@ -0,0 +1,51 @@ +from numbers import Number +import numpy + +class ScaledOperator(object): + '''ScaledOperator + A class to represent the scalar multiplication of an Operator with a scalar. + It holds an operator and a scalar. Basically it returns the multiplication + of the result of direct and adjoint of the operator with the scalar. + For the rest it behaves like the operator it holds. + Args: + operator (Operator): a Operator or LinearOperator + scalar (Number): a scalar multiplier + Example: + The scaled operator behaves like the following: + sop = ScaledOperator(operator, scalar) + sop.direct(x) = scalar * operator.direct(x) + sop.adjoint(x) = scalar * operator.adjoint(x) + sop.norm() = operator.norm() + sop.range_geometry() = operator.range_geometry() + sop.domain_geometry() = operator.domain_geometry() + ''' + def __init__(self, operator, scalar): + super(ScaledOperator, self).__init__() + if not isinstance (scalar, Number): + raise TypeError('expected scalar: got {}'.format(type(scalar))) + self.scalar = scalar + self.operator = operator + def direct(self, x, out=None): + if out is None: + return self.scalar * self.operator.direct(x, out=out) + else: + self.operator.direct(x, out=out) + out *= self.scalar + def adjoint(self, x, out=None): + if self.operator.is_linear(): + if out is None: + return self.scalar * self.operator.adjoint(x, out=out) + else: + self.operator.adjoint(x, out=out) + out *= self.scalar + else: + raise TypeError('Operator is not linear') + def norm(self): + return numpy.abs(self.scalar) * self.operator.norm() + def range_geometry(self): + return self.operator.range_geometry() + def domain_geometry(self): + return self.operator.domain_geometry() + def is_linear(self): + return self.operator.is_linear() + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ShrinkageOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ShrinkageOperator.py new file mode 100644 index 0000000..f47c655 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ShrinkageOperator.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 6 19:30:51 2019 + +@author: evangelos +""" + +from ccpi.framework import DataContainer + +class ShrinkageOperator(): + + def __init__(self): + pass + + def __call__(self, x, tau, out=None): + + return x.sign() * (x.abs() - tau).maximum(0) + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/SparseFiniteDiff.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/SparseFiniteDiff.py new file mode 100644 index 0000000..5e318ff --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/SparseFiniteDiff.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Tue Apr 2 14:06:15 2019 + +@author: vaggelis +""" + +import scipy.sparse as sp +import numpy as np +from ccpi.framework import ImageData + +class SparseFiniteDiff(): + + def __init__(self, gm_domain, gm_range=None, direction=0, bnd_cond = 'Neumann'): + + super(SparseFiniteDiff, self).__init__() + self.gm_domain = gm_domain + self.gm_range = gm_range + self.direction = direction + self.bnd_cond = bnd_cond + + if self.gm_range is None: + self.gm_range = self.gm_domain + + self.get_dims = [i for i in gm_domain.shape] + + if self.direction + 1 > len(self.gm_domain.shape): + raise ValueError('Gradient directions more than geometry domain') + + def matrix(self): + + i = self.direction + + mat = sp.spdiags(np.vstack([-np.ones((1,self.get_dims[i])),np.ones((1,self.get_dims[i]))]), [0,1], self.get_dims[i], self.get_dims[i], format = 'lil') + + if self.bnd_cond == 'Neumann': + mat[-1,:] = 0 + elif self.bnd_cond == 'Periodic': + mat[-1,0] = 1 + + tmpGrad = mat if i == 0 else sp.eye(self.get_dims[0]) + + for j in range(1, self.gm_domain.length): + + tmpGrad = sp.kron(mat, tmpGrad ) if j == i else sp.kron(sp.eye(self.get_dims[j]), tmpGrad ) + + return tmpGrad + + def T(self): + return self.matrix().T + + def direct(self, x): + + x_asarr = x.as_array() + res = np.reshape( self.matrix() * x_asarr.flatten('F'), self.gm_domain.shape, 'F') + return type(x)(res) + + def adjoint(self, x): + + x_asarr = x.as_array() + res = np.reshape( self.matrix().T * x_asarr.flatten('F'), self.gm_domain.shape, 'F') + return type(x)(res) + + def sum_abs_row(self): + + res = np.array(np.reshape(abs(self.matrix()).sum(axis=0), self.gm_domain.shape, 'F')) + res[res==0]=1 + return ImageData(res) + + def sum_abs_col(self): + + res = np.array(np.reshape(abs(self.matrix()).sum(axis=1), self.gm_domain.shape, 'F') ) + res[res==0]=1 + return ImageData(res) + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + from ccpi.optimisation.operators import FiniteDiff + + # 2D + M, N= 2, 3 + ig = ImageGeometry(M, N) + arr = ig.allocate('random_int') + + for i in [0,1]: + + # Neumann + sFD_neum = SparseFiniteDiff(ig, direction=i, bnd_cond='Neumann') + G_neum = FiniteDiff(ig, direction=i, bnd_cond='Neumann') + + # Periodic + sFD_per = SparseFiniteDiff(ig, direction=i, bnd_cond='Periodic') + G_per = FiniteDiff(ig, direction=i, bnd_cond='Periodic') + + u_neum_direct = G_neum.direct(arr) + u_neum_sp_direct = sFD_neum.direct(arr) + np.testing.assert_array_almost_equal(u_neum_direct.as_array(), u_neum_sp_direct.as_array(), decimal=4) + + u_neum_adjoint = G_neum.adjoint(arr) + u_neum_sp_adjoint = sFD_neum.adjoint(arr) + np.testing.assert_array_almost_equal(u_neum_adjoint.as_array(), u_neum_sp_adjoint.as_array(), decimal=4) + + u_per_direct = G_neum.direct(arr) + u_per_sp_direct = sFD_neum.direct(arr) + np.testing.assert_array_almost_equal(u_per_direct.as_array(), u_per_sp_direct.as_array(), decimal=4) + + u_per_adjoint = G_per.adjoint(arr) + u_per_sp_adjoint = sFD_per.adjoint(arr) + np.testing.assert_array_almost_equal(u_per_adjoint.as_array(), u_per_sp_adjoint.as_array(), decimal=4) + + # 3D + M, N, K = 2, 3, 4 + ig3D = ImageGeometry(M, N, K) + arr3D = ig3D.allocate('random_int') + + for i in [0,1,2]: + + # Neumann + sFD_neum3D = SparseFiniteDiff(ig3D, direction=i, bnd_cond='Neumann') + G_neum3D = FiniteDiff(ig3D, direction=i, bnd_cond='Neumann') + + # Periodic + sFD_per3D = SparseFiniteDiff(ig3D, direction=i, bnd_cond='Periodic') + G_per3D = FiniteDiff(ig3D, direction=i, bnd_cond='Periodic') + + u_neum_direct3D = G_neum3D.direct(arr3D) + u_neum_sp_direct3D = sFD_neum3D.direct(arr3D) + np.testing.assert_array_almost_equal(u_neum_direct3D.as_array(), u_neum_sp_direct3D.as_array(), decimal=4) + + u_neum_adjoint3D = G_neum3D.adjoint(arr3D) + u_neum_sp_adjoint3D = sFD_neum3D.adjoint(arr3D) + np.testing.assert_array_almost_equal(u_neum_adjoint3D.as_array(), u_neum_sp_adjoint3D.as_array(), decimal=4) + + u_per_direct3D = G_neum3D.direct(arr3D) + u_per_sp_direct3D = sFD_neum3D.direct(arr3D) + np.testing.assert_array_almost_equal(u_per_direct3D.as_array(), u_per_sp_direct3D.as_array(), decimal=4) + + u_per_adjoint3D = G_per3D.adjoint(arr3D) + u_per_sp_adjoint3D = sFD_per3D.adjoint(arr3D) + np.testing.assert_array_almost_equal(u_per_adjoint3D.as_array(), u_per_sp_adjoint3D.as_array(), decimal=4) + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/SymmetrizedGradientOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/SymmetrizedGradientOperator.py new file mode 100644 index 0000000..c38458d --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/SymmetrizedGradientOperator.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 1 22:53:55 2019 + +@author: evangelos +""" + +from ccpi.optimisation.operators import Gradient, Operator, LinearOperator, ScaledOperator +from ccpi.optimisation.ops import PowerMethodNonsquare +from ccpi.framework import ImageData, ImageGeometry, BlockGeometry, BlockDataContainer +import numpy +from ccpi.optimisation.operators import FiniteDiff, SparseFiniteDiff + + +class SymmetrizedGradient(Gradient): + + def __init__(self, gm_domain, bnd_cond = 'Neumann', **kwargs): + + super(SymmetrizedGradient, self).__init__(gm_domain, bnd_cond, **kwargs) + + ''' + Domain of SymGrad is the Range of Gradient + ''' + self.gm_domain = self.gm_range + self.bnd_cond = bnd_cond + + self.channels = self.gm_range.get_item(0).channels + + if self.correlation=='Space': + if self.channels>1: + pass + else: +# # 2D image ---> Dx v1, Dyv2, Dx + tmp = self.gm_domain.geometries + (self.gm_domain.get_item(0),) + self.gm_range = BlockGeometry(*tmp ) + self.ind1 = range(self.gm_domain.get_item(0).length) + self.ind2 = range(self.gm_domain.get_item(0).length-1, -1, -1) +# self.order = myorder = [0,1,2 3] + + elif self.correlation=='SpaceChannels': + if self.channels>1: + pass + else: + raise ValueError('No channels to correlate') + + + def direct(self, x, out=None): + +# tmp = numpy.zeros(self.gm_range) +# tmp[0] = FiniteDiff(self.gm_domain[1:], direction = 1, bnd_cond = self.bnd_cond).adjoint(x.as_array()[0]) +# tmp[1] = FiniteDiff(self.gm_domain[1:], direction = 0, bnd_cond = self.bnd_cond).adjoint(x.as_array()[1]) +# tmp[2] = 0.5 * (FiniteDiff(self.gm_domain[1:], direction = 0, bnd_cond = self.bnd_cond).adjoint(x.as_array()[0]) + +# FiniteDiff(self.gm_domain[1:], direction = 1, bnd_cond = self.bnd_cond).adjoint(x.as_array()[1]) ) +# +# return type(x)(tmp) + + tmp = [[None]*2]*2 + for i in range(2): + for j in range(2): + tmp[i][j]=FiniteDiff(self.gm_domain.get_item(0), direction = i, bnd_cond = self.bnd_cond).adjoint(x.get_item(j)) + tmp = numpy.array(tmp) + z = 0.5 * (tmp.T + tmp) + z = z.to + + return BlockDataContainer(*z.tolist()) + + + def adjoint(self, x, out=None): + pass + + res = [] + for i in range(2): + tmp = ImageData(np.zeros(x.get_item(0))) + for j in range(2): + tmp += FiniteDiff(self.gm_domain.get_item(0), direction = i, bnd_cond = self.bnd_cond).direct(x.get_item(j)) + res.append(tmp) + return res + + + +# for + +# tmp = numpy.zeros(self.gm_domain) +# +# tmp[0] = FiniteDiff(self.gm_domain[1:], direction = 1, bnd_cond = self.bnd_cond).direct(x.as_array()[0]) + \ +# FiniteDiff(self.gm_domain[1:], direction = 0, bnd_cond = self.bnd_cond).direct(x.as_array()[2]) +# +# tmp[1] = FiniteDiff(self.gm_domain[1:], direction = 1, bnd_cond = self.bnd_cond).direct(x.as_array()[2]) + \ +# FiniteDiff(self.gm_domain[1:], direction = 0, bnd_cond = self.bnd_cond).direct(x.as_array()[1]) +# +# return type(x)(tmp) + + def alloc_domain_dim(self): + return ImageData(numpy.zeros(self.gm_domain)) + + def alloc_range_dim(self): + return ImageData(numpy.zeros(self.range_dim)) + + def domain_dim(self): + return self.gm_domain + + def range_dim(self): + return self.gm_range + + def norm(self): +# return np.sqrt(4*len(self.domainDim())) + #TODO this takes time for big ImageData + # for 2D ||grad|| = sqrt(8), 3D ||grad|| = sqrt(12) + x0 = ImageData(np.random.random_sample(self.domain_dim())) + self.s1, sall, svec = PowerMethodNonsquare(self, 25, x0) + return self.s1 + + + +if __name__ == '__main__': + + ########################################################################### + ## Symmetrized Gradient + from ccpi.framework import DataContainer + from ccpi.optimisation.operators import Gradient, BlockOperator, FiniteDiff + import numpy as np + + N, M = 2, 3 + K = 2 + + ig1 = ImageGeometry(N, M) + ig2 = ImageGeometry(N, M, channels=K) + + E1 = SymmetrizedGradient(ig1, correlation = 'Space', bnd_cond='Neumann') + E2 = SymmetrizedGradient(ig2, correlation = 'SpaceChannels', bnd_cond='Periodic') + + print(E1.domain_geometry().shape) + print(E2.domain_geometry().shape) + + u1 = E1.gm_domain.allocate('random_int') + u2 = E2.gm_domain.allocate('random_int') + + + res = E1.direct(u1) + + res1 = E1.adjoint(res) + +# Dx = FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann') +# Dy = FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann') +# +# B = BlockOperator(Dy, Dx) +# V = BlockDataContainer(u1,u2) +# +# res = B.adjoint(V) + +# ig = (N,M) +# ig2 = (2,) + ig +# ig3 = (3,) + ig +# u1 = ig.allocate('random_int') +# w1 = E.gm_range.allocate('random_int') +# DataContainer(np.random.randint(10, size=ig3)) + + + +# d1 = E.direct(u1) +# d2 = E.adjoint(w1) + +# LHS = (d1.as_array()[0]*w1.as_array()[0] + \ +# d1.as_array()[1]*w1.as_array()[1] + \ +# 2*d1.as_array()[2]*w1.as_array()[2]).sum() +# +# RHS = (u1.as_array()[0]*d2.as_array()[0] + \ +# u1.as_array()[1]*d2.as_array()[1]).sum() +# +# +# print(LHS, RHS, E.norm()) +# + +# + + + + + + + + + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ZeroOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ZeroOperator.py new file mode 100644 index 0000000..a7c5f09 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ZeroOperator.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 6 19:25:53 2019 + +@author: evangelos +""" + +import numpy as np +from ccpi.framework import ImageData +from ccpi.optimisation.operators import Operator + +class ZeroOp(Operator): + + def __init__(self, gm_domain, gm_range): + self.gm_domain = gm_domain + self.gm_range = gm_range + super(ZeroOp, self).__init__() + + def direct(self,x,out=None): + if out is None: + return ImageData(np.zeros(self.gm_range)) + else: + return ImageData(np.zeros(self.gm_range)) + + def adjoint(self,x, out=None): + if out is None: + return ImageData(np.zeros(self.gm_domain)) + else: + return ImageData(np.zeros(self.gm_domain)) + + def norm(self): + return 0 + + def domain_dim(self): + return self.gm_domain + + def range_dim(self): + return self.gm_range \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/__init__.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/__init__.py new file mode 100644 index 0000000..7040d3a --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Mar 5 15:56:27 2019 + +@author: ofn77899 +""" + +from .Operator import Operator +from .LinearOperator import LinearOperator +from .ScaledOperator import ScaledOperator +from .BlockOperator import BlockOperator +from .BlockScaledOperator import BlockScaledOperator + +from .SparseFiniteDiff import SparseFiniteDiff +from .ShrinkageOperator import ShrinkageOperator + +from .FiniteDifferenceOperator import FiniteDiff +from .GradientOperator import Gradient +from .SymmetrizedGradientOperator import SymmetrizedGradient +from .IdentityOperator import Identity +from .ZeroOperator import ZeroOp + + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/ops.py b/Wrappers/Python/build/lib/ccpi/optimisation/ops.py new file mode 100644 index 0000000..fcd0d9e --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/ops.py @@ -0,0 +1,294 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy +from scipy.sparse.linalg import svds +from ccpi.framework import DataContainer +from ccpi.framework import AcquisitionData +from ccpi.framework import ImageData +from ccpi.framework import ImageGeometry +from ccpi.framework import AcquisitionGeometry +from numbers import Number +# Maybe operators need to know what types they take as inputs/outputs +# to not just use generic DataContainer + + +class Operator(object): + '''Operator that maps from a space X -> Y''' + def __init__(self, **kwargs): + self.scalar = 1 + def is_linear(self): + '''Returns if the operator is linear''' + return False + def direct(self,x, out=None): + raise NotImplementedError + def size(self): + # To be defined for specific class + raise NotImplementedError + def norm(self): + raise NotImplementedError + def allocate_direct(self): + '''Allocates memory on the Y space''' + raise NotImplementedError + def allocate_adjoint(self): + '''Allocates memory on the X space''' + raise NotImplementedError + def range_geometry(self): + raise NotImplementedError + def domain_geometry(self): + raise NotImplementedError + def __rmul__(self, other): + '''reverse multiplication of Operator with number sets the variable scalar in the Operator''' + assert isinstance(other, Number) + self.scalar = other + return self + +class LinearOperator(Operator): + '''Operator that maps from a space X -> Y''' + def is_linear(self): + '''Returns if the operator is linear''' + return True + def adjoint(self,x, out=None): + raise NotImplementedError + +class Identity(Operator): + def __init__(self): + self.s1 = 1.0 + self.L = 1 + super(Identity, self).__init__() + + def direct(self,x,out=None): + if out is None: + return x.copy() + else: + out.fill(x) + + def adjoint(self,x, out=None): + if out is None: + return x.copy() + else: + out.fill(x) + + def size(self): + return NotImplemented + + def get_max_sing_val(self): + return self.s1 + +class TomoIdentity(Operator): + def __init__(self, geometry, **kwargs): + super(TomoIdentity, self).__init__() + self.s1 = 1.0 + self.geometry = geometry + + def is_linear(self): + return True + def direct(self,x,out=None): + + if out is None: + if self.scalar != 1: + return x * self.scalar + return x.copy() + else: + if self.scalar != 1: + out.fill(x * self.scalar) + return + out.fill(x) + return + + def adjoint(self,x, out=None): + return self.direct(x, out) + + def norm(self): + return self.s1 + + def get_max_sing_val(self): + return self.s1 + def allocate_direct(self): + if issubclass(type(self.geometry), ImageGeometry): + return ImageData(geometry=self.geometry) + elif issubclass(type(self.geometry), AcquisitionGeometry): + return AcquisitionData(geometry=self.geometry) + else: + raise ValueError("Wrong geometry type: expected ImageGeometry of AcquisitionGeometry, got ", type(self.geometry)) + def allocate_adjoint(self): + return self.allocate_direct() + def range_geometry(self): + return self.geometry + def domain_geometry(self): + return self.geometry + + + +class FiniteDiff2D(Operator): + def __init__(self): + self.s1 = 8.0 + super(FiniteDiff2D, self).__init__() + + def direct(self,x, out=None): + '''Forward differences with Neumann BC.''' + # FIXME this seems to be working only with numpy arrays + + d1 = numpy.zeros_like(x.as_array()) + d1[:,:-1] = x.as_array()[:,1:] - x.as_array()[:,:-1] + d2 = numpy.zeros_like(x.as_array()) + d2[:-1,:] = x.as_array()[1:,:] - x.as_array()[:-1,:] + d = numpy.stack((d1,d2),0) + #x.geometry.voxel_num_z = 2 + return type(x)(d,False,geometry=x.geometry) + + def adjoint(self,x, out=None): + '''Backward differences, Neumann BC.''' + Nrows = x.get_dimension_size('horizontal_x') + Ncols = x.get_dimension_size('horizontal_y') + Nchannels = 1 + if len(x.shape) == 4: + Nchannels = x.get_dimension_size('channel') + zer = numpy.zeros((Nrows,1)) + xxx = x.as_array()[0,:,:-1] + # + h = numpy.concatenate((zer,xxx), 1) + h -= numpy.concatenate((xxx,zer), 1) + + zer = numpy.zeros((1,Ncols)) + xxx = x.as_array()[1,:-1,:] + # + v = numpy.concatenate((zer,xxx), 0) + v -= numpy.concatenate((xxx,zer), 0) + return type(x)(h + v, False, geometry=x.geometry) + + def size(self): + return NotImplemented + + def get_max_sing_val(self): + return self.s1 + +def PowerMethodNonsquareOld(op,numiters): + # Initialise random + # Jakob's + #inputsize = op.size()[1] + #x0 = ImageContainer(numpy.random.randn(*inputsize) + # Edo's + #vg = ImageGeometry(voxel_num_x=inputsize[0], + # voxel_num_y=inputsize[1], + # voxel_num_z=inputsize[2]) + # + #x0 = ImageData(geometry = vg, dimension_labels=['vertical','horizontal_y','horizontal_x']) + #print (x0) + #x0.fill(numpy.random.randn(*x0.shape)) + + x0 = op.create_image_data() + + s = numpy.zeros(numiters) + # Loop + for it in numpy.arange(numiters): + x1 = op.adjoint(op.direct(x0)) + x1norm = numpy.sqrt((x1**2).sum()) + #print ("x0 **********" ,x0) + #print ("x1 **********" ,x1) + s[it] = (x1*x0).sum() / (x0*x0).sum() + x0 = (1.0/x1norm)*x1 + return numpy.sqrt(s[-1]), numpy.sqrt(s), x0 + +#def PowerMethod(op,numiters): +# # Initialise random +# x0 = np.random.randn(400) +# s = np.zeros(numiters) +# # Loop +# for it in np.arange(numiters): +# x1 = np.dot(op.transpose(),np.dot(op,x0)) +# x1norm = np.sqrt(np.sum(np.dot(x1,x1))) +# s[it] = np.dot(x1,x0) / np.dot(x1,x0) +# x0 = (1.0/x1norm)*x1 +# return s, x0 + + +def PowerMethodNonsquare(op,numiters , x0=None): + # Initialise random + # Jakob's + # inputsize , outputsize = op.size() + #x0 = ImageContainer(numpy.random.randn(*inputsize) + # Edo's + #vg = ImageGeometry(voxel_num_x=inputsize[0], + # voxel_num_y=inputsize[1], + # voxel_num_z=inputsize[2]) + # + #x0 = ImageData(geometry = vg, dimension_labels=['vertical','horizontal_y','horizontal_x']) + #print (x0) + #x0.fill(numpy.random.randn(*x0.shape)) + + if x0 is None: + #x0 = op.create_image_data() + x0 = op.allocate_direct() + x0.fill(numpy.random.randn(*x0.shape)) + + s = numpy.zeros(numiters) + # Loop + for it in numpy.arange(numiters): + x1 = op.adjoint(op.direct(x0)) + #x1norm = numpy.sqrt((x1*x1).sum()) + x1norm = x1.norm() + #print ("x0 **********" ,x0) + #print ("x1 **********" ,x1) + s[it] = (x1*x0).sum() / (x0.squared_norm()) + x0 = (1.0/x1norm)*x1 + return numpy.sqrt(s[-1]), numpy.sqrt(s), x0 + +class LinearOperatorMatrix(Operator): + def __init__(self,A): + self.A = A + self.s1 = None # Largest singular value, initially unknown + super(LinearOperatorMatrix, self).__init__() + + def direct(self,x, out=None): + if out is None: + return type(x)(numpy.dot(self.A,x.as_array())) + else: + numpy.dot(self.A, x.as_array(), out=out.as_array()) + + + def adjoint(self,x, out=None): + if out is None: + return type(x)(numpy.dot(self.A.transpose(),x.as_array())) + else: + numpy.dot(self.A.transpose(),x.as_array(), out=out.as_array()) + + + def size(self): + return self.A.shape + + def get_max_sing_val(self): + # If unknown, compute and store. If known, simply return it. + if self.s1 is None: + self.s1 = svds(self.A,1,return_singular_vectors=False)[0] + return self.s1 + else: + return self.s1 + def allocate_direct(self): + '''allocates the memory to hold the result of adjoint''' + #numpy.dot(self.A.transpose(),x.as_array()) + M_A, N_A = self.A.shape + out = numpy.zeros((N_A,1)) + return DataContainer(out) + def allocate_adjoint(self): + '''allocate the memory to hold the result of direct''' + #numpy.dot(self.A.transpose(),x.as_array()) + M_A, N_A = self.A.shape + out = numpy.zeros((M_A,1)) + return DataContainer(out) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/spdhg.py b/Wrappers/Python/build/lib/ccpi/optimisation/spdhg.py new file mode 100644 index 0000000..263a7cd --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/spdhg.py @@ -0,0 +1,338 @@ +# Copyright 2018 Matthias Ehrhardt, Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy + +from ccpi.optimisation.funcs import Function +from ccpi.framework import ImageData +from ccpi.framework import AcquisitionData + + +class spdhg(): + """Computes a saddle point with a stochastic PDHG. + + This means, a solution (x*, y*), y* = (y*_1, ..., y*_n) such that + + (x*, y*) in arg min_x max_y sum_i=1^n - f*[i](y_i) + g(x) + + where g : X -> IR_infty and f[i] : Y[i] -> IR_infty are convex, l.s.c. and + proper functionals. For this algorithm, they all may be non-smooth and no + strong convexity is assumed. + + Parameters + ---------- + f : list of functions + Functionals Y[i] -> IR_infty that all have a convex conjugate with a + proximal operator, i.e. + f[i].convex_conj.prox(sigma[i]) : Y[i] -> Y[i]. + g : function + Functional X -> IR_infty that has a proximal operator, i.e. + g.prox(tau) : X -> X. + A : list of functions + Operators A[i] : X -> Y[i] that possess adjoints: A[i].adjoint + x : primal variable, optional + By default equals 0. + y : dual variable, optional + Part of a product space. By default equals 0. + z : variable, optional + Adjoint of dual variable, z = A^* y. By default equals 0 if y = 0. + tau : scalar / vector / matrix, optional + Step size for primal variable. Note that the proximal operator of g + has to be well-defined for this input. + sigma : scalar, optional + Scalar / vector / matrix used as step size for dual variable. Note that + the proximal operator related to f (see above) has to be well-defined + for this input. + prob : list of scalars, optional + Probabilities prob[i] that a subset i is selected in each iteration. + If fun_select is not given, then the sum of all probabilities must + equal 1. + A_norms : list of scalars, optional + Norms of the operators in A. Can be used to determine the step sizes + tau and sigma and the probabilities prob. + fun_select : function, optional + Function that selects blocks at every iteration IN -> {1,...,n}. By + default this is serial sampling, fun_select(k) selects an index + i \in {1,...,n} with probability prob[i]. + + References + ---------- + [CERS2018] A. Chambolle, M. J. Ehrhardt, P. Richtarik and C.-B. Schoenlieb, + *Stochastic Primal-Dual Hybrid Gradient Algorithm with Arbitrary Sampling + and Imaging Applications*. SIAM Journal on Optimization, 28(4), 2783-2808 + (2018) http://doi.org/10.1007/s10851-010-0251-1 + + [E+2017] M. J. Ehrhardt, P. J. Markiewicz, P. Richtarik, J. Schott, + A. Chambolle and C.-B. Schoenlieb, *Faster PET reconstruction with a + stochastic primal-dual hybrid gradient method*. Wavelets and Sparsity XVII, + 58 (2017) http://doi.org/10.1117/12.2272946. + + [EMS2018] M. J. Ehrhardt, P. J. Markiewicz and C.-B. Schoenlieb, *Faster + PET Reconstruction with Non-Smooth Priors by Randomization and + Preconditioning*. (2018) ArXiv: http://arxiv.org/abs/1808.07150 + """ + + def __init__(self, f, g, A, x=None, y=None, z=None, tau=None, sigma=None, + prob=None, A_norms=None, fun_select=None): + # fun_select is optional and by default performs serial sampling + + if x is None: + x = A[0].allocate_direct(0) + + if y is None: + if z is not None: + raise ValueError('y and z have to be defaulted together') + + y = [Ai.allocate_adjoint(0) for Ai in A] + z = 0 * x.copy() + + else: + if z is None: + raise ValueError('y and z have to be defaulted together') + + if A_norms is not None: + if tau is not None or sigma is not None or prob is not None: + raise ValueError('Either A_norms or (tau, sigma, prob) must ' + 'be given') + + tau = 1 / sum(A_norms) + sigma = [1 / nA for nA in A_norms] + prob = [nA / sum(A_norms) for nA in A_norms] + + #uniform prob, needs different sigma and tau + #n = len(A) + #prob = [1./n] * n + + if fun_select is None: + if prob is None: + raise ValueError('prob was not determined') + + def fun_select(k): + return [int(numpy.random.choice(len(A), 1, p=prob))] + + self.iter = 0 + self.x = x + + self.y = y + self.z = z + + self.f = f + self.g = g + self.A = A + self.tau = tau + self.sigma = sigma + self.prob = prob + self.fun_select = fun_select + + # Initialize variables + self.z_relax = z.copy() + self.tmp = self.x.copy() + + def update(self): + # select block + selected = self.fun_select(self.iter) + + # update primal variable + #tmp = (self.x - self.tau * self.z_relax).as_array() + #self.x.fill(self.g.prox(tmp, self.tau)) + self.tmp = - self.tau * self.z_relax + self.tmp += self.x + self.x = self.g.prox(self.tmp, self.tau) + + # update dual variable and z, z_relax + self.z_relax = self.z.copy() + for i in selected: + # save old yi + y_old = self.y[i].copy() + + # y[i]= prox(tmp) + tmp = y_old + self.sigma[i] * self.A[i].direct(self.x) + self.y[i] = self.f[i].convex_conj.prox(tmp, self.sigma[i]) + + # update adjoint of dual variable + dz = self.A[i].adjoint(self.y[i] - y_old) + self.z += dz + + # compute extrapolation + self.z_relax += (1 + 1 / self.prob[i]) * dz + + self.iter += 1 + + +## Functions + +class KullbackLeibler(Function): + def __init__(self, data, background): + self.data = data + self.background = background + self.__offset = None + + def __call__(self, x): + """Return the KL-diveregnce in the point ``x``. + + If any components of ``x`` is non-positive, the value is positive + infinity. + + Needs one extra array of memory of the size of `prior`. + """ + + # define short variable names + y = self.data + r = self.background + + # Compute + # sum(x + r - y + y * log(y / (x + r))) + # = sum(x - y * log(x + r)) + self.offset + # Assume that + # x + r > 0 + + # sum the result up + obj = numpy.sum(x - y * numpy.log(x + r)) + self.offset() + + if numpy.isnan(obj): + # In this case, some element was less than or equal to zero + return numpy.inf + else: + return obj + + @property + def convex_conj(self): + """The convex conjugate functional of the KL-functional.""" + return KullbackLeiblerConvexConjugate(self.data, self.background) + + def offset(self): + """The offset which is independent of the unknown.""" + + if self.__offset is None: + tmp = self.domain.element() + + # define short variable names + y = self.data + r = self.background + + tmp = self.domain.element(numpy.maximum(y, 1)) + tmp = r - y + y * numpy.log(tmp) + + # sum the result up + self.__offset = numpy.sum(tmp) + + return self.__offset + +# def __repr__(self): +# """to be added???""" +# """Return ``repr(self)``.""" + # return '{}({!r}, {!r}, {!r})'.format(self.__class__.__name__, + ## self.domain, self.data, + # self.background) + + +class KullbackLeiblerConvexConjugate(Function): + """The convex conjugate of Kullback-Leibler divergence functional. + + Notes + ----- + The functional :math:`F^*` with prior :math:`g>0` is given by: + + .. math:: + F^*(x) + = + \\begin{cases} + \\sum_{i} \left( -g_i \ln(1 - x_i) \\right) + & \\text{if } x_i < 1 \\forall i + \\\\ + +\\infty & \\text{else} + \\end{cases} + + See Also + -------- + KullbackLeibler : convex conjugate functional + """ + + def __init__(self, data, background): + self.data = data + self.background = background + + def __call__(self, x): + y = self.data + r = self.background + + tmp = numpy.sum(- x * r - y * numpy.log(1 - x)) + + if numpy.isnan(tmp): + # In this case, some element was larger than or equal to one + return numpy.inf + else: + return tmp + + + def prox(self, x, tau, out=None): + # Let y = data, r = background, z = x + tau * r + # Compute 0.5 * (z + 1 - sqrt((z - 1)**2 + 4 * tau * y)) + # Currently it needs 3 extra copies of memory. + + if out is None: + out = x.copy() + + # define short variable names + try: # this should be standard SIRF/CIL mode + y = self.data.as_array() + r = self.background.as_array() + x = x.as_array() + + try: + taua = tau.as_array() + except: + taua = tau + + z = x + tau * r + + out.fill(0.5 * (z + 1 - numpy.sqrt((z - 1) ** 2 + 4 * taua * y))) + + return out + + except: # e.g. for NumPy + y = self.data + r = self.background + + try: + taua = tau.as_array() + except: + taua = tau + + z = x + tau * r + + out[:] = 0.5 * (z + 1 - numpy.sqrt((z - 1) ** 2 + 4 * taua * y)) + + return out + + @property + def convex_conj(self): + return KullbackLeibler(self.data, self.background) + + +def mult(x, y): + try: + xa = x.as_array() + except: + xa = x + + out = y.clone() + out.fill(xa * y.as_array()) + + return out diff --git a/Wrappers/Python/build/lib/ccpi/processors.py b/Wrappers/Python/build/lib/ccpi/processors.py new file mode 100644 index 0000000..ccef410 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/processors.py @@ -0,0 +1,514 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +from ccpi.framework import DataProcessor, DataContainer, AcquisitionData,\ + AcquisitionGeometry, ImageGeometry, ImageData +from ccpi.reconstruction.parallelbeam import alg as pbalg +import numpy +from scipy import ndimage + +import matplotlib.pyplot as plt + + +class Normalizer(DataProcessor): + '''Normalization based on flat and dark + + This processor read in a AcquisitionData and normalises it based on + the instrument reading with and without incident photons or neutrons. + + Input: AcquisitionData + Parameter: 2D projection with flat field (or stack) + 2D projection with dark field (or stack) + Output: AcquisitionDataSetn + ''' + + def __init__(self, flat_field = None, dark_field = None, tolerance = 1e-5): + kwargs = { + 'flat_field' : flat_field, + 'dark_field' : dark_field, + # very small number. Used when there is a division by zero + 'tolerance' : tolerance + } + + #DataProcessor.__init__(self, **kwargs) + super(Normalizer, self).__init__(**kwargs) + if not flat_field is None: + self.set_flat_field(flat_field) + if not dark_field is None: + self.set_dark_field(dark_field) + + def check_input(self, dataset): + if dataset.number_of_dimensions == 3 or\ + dataset.number_of_dimensions == 2: + return True + else: + raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ + .format(dataset.number_of_dimensions)) + + def set_dark_field(self, df): + if type(df) is numpy.ndarray: + if len(numpy.shape(df)) == 3: + raise ValueError('Dark Field should be 2D') + elif len(numpy.shape(df)) == 2: + self.dark_field = df + elif issubclass(type(df), DataContainer): + self.dark_field = self.set_dark_field(df.as_array()) + + def set_flat_field(self, df): + if type(df) is numpy.ndarray: + if len(numpy.shape(df)) == 3: + raise ValueError('Flat Field should be 2D') + elif len(numpy.shape(df)) == 2: + self.flat_field = df + elif issubclass(type(df), DataContainer): + self.flat_field = self.set_flat_field(df.as_array()) + + @staticmethod + def normalize_projection(projection, flat, dark, tolerance): + a = (projection - dark) + b = (flat-dark) + with numpy.errstate(divide='ignore', invalid='ignore'): + c = numpy.true_divide( a, b ) + c[ ~ numpy.isfinite( c )] = tolerance # set to not zero if 0/0 + return c + + @staticmethod + def estimate_normalised_error(projection, flat, dark, delta_flat, delta_dark): + '''returns the estimated relative error of the normalised projection + + n = (projection - dark) / (flat - dark) + Dn/n = (flat-dark + projection-dark)/((flat-dark)*(projection-dark))*(Df/f + Dd/d) + ''' + a = (projection - dark) + b = (flat-dark) + df = delta_flat / flat + dd = delta_dark / dark + rel_norm_error = (b + a) / (b * a) * (df + dd) + return rel_norm_error + + def process(self, out=None): + + projections = self.get_input() + dark = self.dark_field + flat = self.flat_field + + if projections.number_of_dimensions == 3: + if not (projections.shape[1:] == dark.shape and \ + projections.shape[1:] == flat.shape): + raise ValueError('Flats/Dark and projections size do not match.') + + + a = numpy.asarray( + [ Normalizer.normalize_projection( + projection, flat, dark, self.tolerance) \ + for projection in projections.as_array() ] + ) + elif projections.number_of_dimensions == 2: + a = Normalizer.normalize_projection(projections.as_array(), + flat, dark, self.tolerance) + y = type(projections)( a , True, + dimension_labels=projections.dimension_labels, + geometry=projections.geometry) + return y + + +class CenterOfRotationFinder(DataProcessor): + '''Processor to find the center of rotation in a parallel beam experiment + + This processor read in a AcquisitionDataSet and finds the center of rotation + based on Nghia Vo's method. https://doi.org/10.1364/OE.22.019078 + + Input: AcquisitionDataSet + + Output: float. center of rotation in pixel coordinate + ''' + + def __init__(self): + kwargs = { + + } + + #DataProcessor.__init__(self, **kwargs) + super(CenterOfRotationFinder, self).__init__(**kwargs) + + def check_input(self, dataset): + if dataset.number_of_dimensions == 3: + if dataset.geometry.geom_type == 'parallel': + return True + else: + raise ValueError('{0} is suitable only for parallel beam geometry'\ + .format(self.__class__.__name__)) + else: + raise ValueError("Expected input dimensions is 3, got {0}"\ + .format(dataset.number_of_dimensions)) + + + # ######################################################################### + # Copyright (c) 2015, UChicago Argonne, LLC. All rights reserved. # + # # + # Copyright 2015. UChicago Argonne, LLC. This software was produced # + # under U.S. Government contract DE-AC02-06CH11357 for Argonne National # + # Laboratory (ANL), which is operated by UChicago Argonne, LLC for the # + # U.S. Department of Energy. The U.S. Government has rights to use, # + # reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR # + # UChicago Argonne, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR # + # ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is # + # modified to produce derivative works, such modified software should # + # be clearly marked, so as not to confuse it with the version available # + # from ANL. # + # # + # Additionally, redistribution and use in source and binary forms, with # + # or without modification, are permitted provided that the following # + # conditions are met: # + # # + # * Redistributions of source code must retain the above copyright # + # notice, this list of conditions and the following disclaimer. # + # # + # * Redistributions in binary form must reproduce the above copyright # + # notice, this list of conditions and the following disclaimer in # + # the documentation and/or other materials provided with the # + # distribution. # + # # + # * Neither the name of UChicago Argonne, LLC, Argonne National # + # Laboratory, ANL, the U.S. Government, nor the names of its # + # contributors may be used to endorse or promote products derived # + # from this software without specific prior written permission. # + # # + # THIS SOFTWARE IS PROVIDED BY UChicago Argonne, LLC AND CONTRIBUTORS # + # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # + # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # + # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UChicago # + # Argonne, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # + # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # + # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # + # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # + # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # + # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # + # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # + # POSSIBILITY OF SUCH DAMAGE. # + # ######################################################################### + + @staticmethod + def as_ndarray(arr, dtype=None, copy=False): + if not isinstance(arr, numpy.ndarray): + arr = numpy.array(arr, dtype=dtype, copy=copy) + return arr + + @staticmethod + def as_dtype(arr, dtype, copy=False): + if not arr.dtype == dtype: + arr = numpy.array(arr, dtype=dtype, copy=copy) + return arr + + @staticmethod + def as_float32(arr): + arr = CenterOfRotationFinder.as_ndarray(arr, numpy.float32) + return CenterOfRotationFinder.as_dtype(arr, numpy.float32) + + + + + @staticmethod + def find_center_vo(tomo, ind=None, smin=-40, smax=40, srad=10, step=0.5, + ratio=2., drop=20): + """ + Find rotation axis location using Nghia Vo's method. :cite:`Vo:14`. + + Parameters + ---------- + tomo : ndarray + 3D tomographic data. + ind : int, optional + Index of the slice to be used for reconstruction. + smin, smax : int, optional + Reference to the horizontal center of the sinogram. + srad : float, optional + Fine search radius. + step : float, optional + Step of fine searching. + ratio : float, optional + The ratio between the FOV of the camera and the size of object. + It's used to generate the mask. + drop : int, optional + Drop lines around vertical center of the mask. + + Returns + ------- + float + Rotation axis location. + + Notes + ----- + The function may not yield a correct estimate, if: + + - the sample size is bigger than the field of view of the camera. + In this case the ``ratio`` argument need to be set larger + than the default of 2.0. + + - there is distortion in the imaging hardware. If there's + no correction applied, the center of the projection image may + yield a better estimate. + + - the sample contrast is weak. Paganin's filter need to be applied + to overcome this. + + - the sample was changed during the scan. + """ + tomo = CenterOfRotationFinder.as_float32(tomo) + + if ind is None: + ind = tomo.shape[1] // 2 + _tomo = tomo[:, ind, :] + + + + # Reduce noise by smooth filters. Use different filters for coarse and fine search + _tomo_cs = ndimage.filters.gaussian_filter(_tomo, (3, 1)) + _tomo_fs = ndimage.filters.median_filter(_tomo, (2, 2)) + + # Coarse and fine searches for finding the rotation center. + if _tomo.shape[0] * _tomo.shape[1] > 4e6: # If data is large (>2kx2k) + #_tomo_coarse = downsample(numpy.expand_dims(_tomo_cs,1), level=2)[:, 0, :] + #init_cen = _search_coarse(_tomo_coarse, smin, smax, ratio, drop) + #fine_cen = _search_fine(_tomo_fs, srad, step, init_cen*4, ratio, drop) + init_cen = CenterOfRotationFinder._search_coarse(_tomo_cs, smin, + smax, ratio, drop) + fine_cen = CenterOfRotationFinder._search_fine(_tomo_fs, srad, + step, init_cen, + ratio, drop) + else: + init_cen = CenterOfRotationFinder._search_coarse(_tomo_cs, + smin, smax, + ratio, drop) + fine_cen = CenterOfRotationFinder._search_fine(_tomo_fs, srad, + step, init_cen, + ratio, drop) + + #logger.debug('Rotation center search finished: %i', fine_cen) + return fine_cen + + + @staticmethod + def _search_coarse(sino, smin, smax, ratio, drop): + """ + Coarse search for finding the rotation center. + """ + (Nrow, Ncol) = sino.shape + centerfliplr = (Ncol - 1.0) / 2.0 + + # Copy the sinogram and flip left right, the purpose is to + # make a full [0;2Pi] sinogram + _copy_sino = numpy.fliplr(sino[1:]) + + # This image is used for compensating the shift of sinogram 2 + temp_img = numpy.zeros((Nrow - 1, Ncol), dtype='float32') + temp_img[:] = sino[-1] + + # Start coarse search in which the shift step is 1 + listshift = numpy.arange(smin, smax + 1) + listmetric = numpy.zeros(len(listshift), dtype='float32') + mask = CenterOfRotationFinder._create_mask(2 * Nrow - 1, Ncol, + 0.5 * ratio * Ncol, drop) + for i in listshift: + _sino = numpy.roll(_copy_sino, i, axis=1) + if i >= 0: + _sino[:, 0:i] = temp_img[:, 0:i] + else: + _sino[:, i:] = temp_img[:, i:] + listmetric[i - smin] = numpy.sum(numpy.abs(numpy.fft.fftshift( + #pyfftw.interfaces.numpy_fft.fft2( + # numpy.vstack((sino, _sino))) + numpy.fft.fft2(numpy.vstack((sino, _sino))) + )) * mask) + minpos = numpy.argmin(listmetric) + return centerfliplr + listshift[minpos] / 2.0 + + @staticmethod + def _search_fine(sino, srad, step, init_cen, ratio, drop): + """ + Fine search for finding the rotation center. + """ + Nrow, Ncol = sino.shape + centerfliplr = (Ncol + 1.0) / 2.0 - 1.0 + # Use to shift the sinogram 2 to the raw CoR. + shiftsino = numpy.int16(2 * (init_cen - centerfliplr)) + _copy_sino = numpy.roll(numpy.fliplr(sino[1:]), shiftsino, axis=1) + if init_cen <= centerfliplr: + lefttake = numpy.int16(numpy.ceil(srad + 1)) + righttake = numpy.int16(numpy.floor(2 * init_cen - srad - 1)) + else: + lefttake = numpy.int16(numpy.ceil( + init_cen - (Ncol - 1 - init_cen) + srad + 1)) + righttake = numpy.int16(numpy.floor(Ncol - 1 - srad - 1)) + Ncol1 = righttake - lefttake + 1 + mask = CenterOfRotationFinder._create_mask(2 * Nrow - 1, Ncol1, + 0.5 * ratio * Ncol, drop) + numshift = numpy.int16((2 * srad) / step) + 1 + listshift = numpy.linspace(-srad, srad, num=numshift) + listmetric = numpy.zeros(len(listshift), dtype='float32') + factor1 = numpy.mean(sino[-1, lefttake:righttake]) + num1 = 0 + for i in listshift: + _sino = ndimage.interpolation.shift( + _copy_sino, (0, i), prefilter=False) + factor2 = numpy.mean(_sino[0,lefttake:righttake]) + _sino = _sino * factor1 / factor2 + sinojoin = numpy.vstack((sino, _sino)) + listmetric[num1] = numpy.sum(numpy.abs(numpy.fft.fftshift( + #pyfftw.interfaces.numpy_fft.fft2( + # sinojoin[:, lefttake:righttake + 1]) + numpy.fft.fft2(sinojoin[:, lefttake:righttake + 1]) + )) * mask) + num1 = num1 + 1 + minpos = numpy.argmin(listmetric) + return init_cen + listshift[minpos] / 2.0 + + @staticmethod + def _create_mask(nrow, ncol, radius, drop): + du = 1.0 / ncol + dv = (nrow - 1.0) / (nrow * 2.0 * numpy.pi) + centerrow = numpy.ceil(nrow / 2) - 1 + centercol = numpy.ceil(ncol / 2) - 1 + # added by Edoardo Pasca + centerrow = int(centerrow) + centercol = int(centercol) + mask = numpy.zeros((nrow, ncol), dtype='float32') + for i in range(nrow): + num1 = numpy.round(((i - centerrow) * dv / radius) / du) + (p1, p2) = numpy.int16(numpy.clip(numpy.sort( + (-num1 + centercol, num1 + centercol)), 0, ncol - 1)) + mask[i, p1:p2 + 1] = numpy.ones(p2 - p1 + 1, dtype='float32') + if drop < centerrow: + mask[centerrow - drop:centerrow + drop + 1, + :] = numpy.zeros((2 * drop + 1, ncol), dtype='float32') + mask[:,centercol-1:centercol+2] = numpy.zeros((nrow, 3), dtype='float32') + return mask + + def process(self, out=None): + + projections = self.get_input() + + cor = CenterOfRotationFinder.find_center_vo(projections.as_array()) + + return cor + + +class AcquisitionDataPadder(DataProcessor): + '''Normalization based on flat and dark + + This processor read in a AcquisitionData and normalises it based on + the instrument reading with and without incident photons or neutrons. + + Input: AcquisitionData + Parameter: 2D projection with flat field (or stack) + 2D projection with dark field (or stack) + Output: AcquisitionDataSetn + ''' + + def __init__(self, + center_of_rotation = None, + acquisition_geometry = None, + pad_value = 1e-5): + kwargs = { + 'acquisition_geometry' : acquisition_geometry, + 'center_of_rotation' : center_of_rotation, + 'pad_value' : pad_value + } + + super(AcquisitionDataPadder, self).__init__(**kwargs) + + def check_input(self, dataset): + if self.acquisition_geometry is None: + self.acquisition_geometry = dataset.geometry + if dataset.number_of_dimensions == 3: + return True + else: + raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ + .format(dataset.number_of_dimensions)) + + def process(self, out=None): + projections = self.get_input() + w = projections.get_dimension_size('horizontal') + delta = w - 2 * self.center_of_rotation + + padded_width = int ( + numpy.ceil(abs(delta)) + w + ) + delta_pix = padded_width - w + + voxel_per_pixel = 1 + geom = pbalg.pb_setup_geometry_from_acquisition(projections.as_array(), + self.acquisition_geometry.angles, + self.center_of_rotation, + voxel_per_pixel ) + + padded_geometry = self.acquisition_geometry.clone() + + padded_geometry.pixel_num_h = geom['n_h'] + padded_geometry.pixel_num_v = geom['n_v'] + + delta_pix_h = padded_geometry.pixel_num_h - self.acquisition_geometry.pixel_num_h + delta_pix_v = padded_geometry.pixel_num_v - self.acquisition_geometry.pixel_num_v + + if delta_pix_h == 0: + delta_pix_h = delta_pix + padded_geometry.pixel_num_h = padded_width + #initialize a new AcquisitionData with values close to 0 + out = AcquisitionData(geometry=padded_geometry) + out = out + self.pad_value + + + #pad in the horizontal-vertical plane -> slice on angles + if delta > 0: + #pad left of middle + command = "out.array[" + for i in range(out.number_of_dimensions): + if out.dimension_labels[i] == 'horizontal': + value = '{0}:{1}'.format(delta_pix_h, delta_pix_h+w) + command = command + str(value) + else: + if out.dimension_labels[i] == 'vertical' : + value = '{0}:'.format(delta_pix_v) + command = command + str(value) + else: + command = command + ":" + if i < out.number_of_dimensions -1: + command = command + ',' + command = command + '] = projections.array' + #print (command) + else: + #pad right of middle + command = "out.array[" + for i in range(out.number_of_dimensions): + if out.dimension_labels[i] == 'horizontal': + value = '{0}:{1}'.format(0, w) + command = command + str(value) + else: + if out.dimension_labels[i] == 'vertical' : + value = '{0}:'.format(delta_pix_v) + command = command + str(value) + else: + command = command + ":" + if i < out.number_of_dimensions -1: + command = command + ',' + command = command + '] = projections.array' + #print (command) + #cleaned = eval(command) + exec(command) + return out \ No newline at end of file diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py index bc080f8..a41bd48 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py @@ -166,10 +166,10 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): y_old.fill(y) -# if i%10==0: -## -# p1 = f(operator.direct(x)) + g(x) -## print(p1) + if i%10==0: +# + p1 = f(operator.direct(x)) + g(x) + print(p1) # d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) # primal.append(p1) # dual.append(d1) @@ -196,10 +196,10 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): x_old.fill(x) y_old.fill(y) -# if i%10==0: -## -# p1 = f(operator.direct(x)) + g(x) -## print(p1) + if i%10==0: +# + p1 = f(operator.direct(x)) + g(x) + print(p1) # d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) # primal.append(p1) # dual.append(d1) diff --git a/Wrappers/Python/wip/pdhg_TV_tomography2D.py b/Wrappers/Python/wip/pdhg_TV_tomography2D.py index 419b098..d0ef097 100644 --- a/Wrappers/Python/wip/pdhg_TV_tomography2D.py +++ b/Wrappers/Python/wip/pdhg_TV_tomography2D.py @@ -43,7 +43,7 @@ from timeit import default_timer as timer #ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) #data = ImageData(phantom_2D, geometry=ig) -N = 150 +N = 75 x = np.zeros((N,N)) x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 diff --git a/Wrappers/Python/wip/test_pdhg_profile/profile_pdhg_TV_denoising.py b/Wrappers/Python/wip/test_pdhg_profile/profile_pdhg_TV_denoising.py deleted file mode 100644 index e142d94..0000000 --- a/Wrappers/Python/wip/test_pdhg_profile/profile_pdhg_TV_denoising.py +++ /dev/null @@ -1,273 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 22 14:53:03 2019 - -@author: evangelos -""" - -from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer - -import numpy as np -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, PDHG_old - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, FunctionOperatorComposition, BlockFunction, ScaledFunction - -from skimage.util import random_noise - -from timeit import default_timer as timer -def dt(steps): - return steps[-1] - steps[-2] - -#%% - -# Create phantom for TV denoising - -N = 500 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Add Gaussian noise -n1 = random_noise(data, mode = 'gaussian', mean=0, var = 0.05, seed=10) -noisy_data = ImageData(n1) - -plt.imshow(noisy_data.as_array()) -plt.show() - -#%% - -# Regularisation Parameter -alpha = 2 - -#method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Form Composite Operator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - #### Create functions - - f1 = alpha * MixedL21Norm() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - ########################################################################### - # No Composite # - ########################################################################### - operator = Gradient(ig) - f = alpha * FunctionOperatorComposition(operator, MixedL21Norm()) - g = L2NormSquared(b = noisy_data) - - ########################################################################### -#%% - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -opt = {'niter':2000} -opt1 = {'niter':2000, 'memopt': True} - -t1 = timer() -res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) -t2 = timer() - - -t3 = timer() -res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) -t4 = timer() -# -print ("No memopt in {}s, memopt in {}s ".format(t2-t1, t4 -t3)) - -# -#%% -#pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -#pdhg.max_iteration = 2000 -#pdhg.update_objective_interval = 100 -## -#pdhgo = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -#pdhgo.max_iteration = 2000 -#pdhgo.update_objective_interval = 100 -## -#steps = [timer()] -#pdhgo.run(2000) -#steps.append(timer()) -#t1 = dt(steps) -## -#pdhg.run(2000) -#steps.append(timer()) -#t2 = dt(steps) -# -#print ("Time difference {}s {}s {}s Speedup {:.2f}".format(t1,t2,t2-t1, t2/t1)) -#res = pdhg.get_output() -#res1 = pdhgo.get_output() - -#%% -#plt.figure(figsize=(15,15)) -#plt.subplot(3,1,1) -#plt.imshow(res.as_array()) -#plt.title('no memopt') -#plt.colorbar() -#plt.subplot(3,1,2) -#plt.imshow(res1.as_array()) -#plt.title('memopt') -#plt.colorbar() -#plt.subplot(3,1,3) -#plt.imshow((res1 - res).abs().as_array()) -#plt.title('diff') -#plt.colorbar() -#plt.show() - - -#plt.figure(figsize=(15,15)) -#plt.subplot(3,1,1) -#plt.imshow(pdhg.get_output().as_array()) -#plt.title('no memopt class') -#plt.colorbar() -#plt.subplot(3,1,2) -#plt.imshow(res.as_array()) -#plt.title('no memopt') -#plt.colorbar() -#plt.subplot(3,1,3) -#plt.imshow((pdhg.get_output() - res).abs().as_array()) -#plt.title('diff') -#plt.colorbar() -#plt.show() -# -# -# -#plt.figure(figsize=(15,15)) -#plt.subplot(3,1,1) -#plt.imshow(pdhgo.get_output().as_array()) -#plt.title('memopt class') -#plt.colorbar() -#plt.subplot(3,1,2) -#plt.imshow(res1.as_array()) -#plt.title('no memopt') -#plt.colorbar() -#plt.subplot(3,1,3) -#plt.imshow((pdhgo.get_output() - res1).abs().as_array()) -#plt.title('diff') -#plt.colorbar() -#plt.show() - - - - - -# print ("Time difference {}s {}s {}s Speedup {:.2f}".format(t1,t2,t2-t1, t2/t1)) -# res = pdhg.get_output() -# res1 = pdhgo.get_output() -# -# diff = (res-res1) -# print ("diff norm {} max {}".format(diff.norm(), diff.abs().as_array().max())) -# print ("Sum ( abs(diff) ) {}".format(diff.abs().sum())) -# -# -# plt.figure(figsize=(5,5)) -# plt.subplot(1,3,1) -# plt.imshow(res.as_array()) -# plt.colorbar() -# #plt.show() -# -# #plt.figure(figsize=(5,5)) -# plt.subplot(1,3,2) -# plt.imshow(res1.as_array()) -# plt.colorbar() - -#plt.show() - - - -#======= -## opt = {'niter':2000, 'memopt': True} -# -## res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) -# -#>>>>>>> origin/pdhg_fix -# -# -## opt = {'niter':2000, 'memopt': False} -## res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) -# -## plt.figure(figsize=(5,5)) -## plt.subplot(1,3,1) -## plt.imshow(res.as_array()) -## plt.title('memopt') -## plt.colorbar() -## plt.subplot(1,3,2) -## plt.imshow(res1.as_array()) -## plt.title('no memopt') -## plt.colorbar() -## plt.subplot(1,3,3) -## plt.imshow((res1 - res).abs().as_array()) -## plt.title('diff') -## plt.colorbar() -## plt.show() -#pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -#pdhg.max_iteration = 2000 -#pdhg.update_objective_interval = 100 -# -# -#pdhgo = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -#pdhgo.max_iteration = 2000 -#pdhgo.update_objective_interval = 100 -# -#steps = [timer()] -#pdhgo.run(200) -#steps.append(timer()) -#t1 = dt(steps) -# -#pdhg.run(200) -#steps.append(timer()) -#t2 = dt(steps) -# -#print ("Time difference {} {} {}".format(t1,t2,t2-t1)) -#sol = pdhg.get_output().as_array() -##sol = result.as_array() -## -#fig = plt.figure() -#plt.subplot(1,3,1) -#plt.imshow(noisy_data.as_array()) -#plt.colorbar() -#plt.subplot(1,3,2) -#plt.imshow(sol) -#plt.colorbar() -#plt.subplot(1,3,3) -#plt.imshow(pdhgo.get_output().as_array()) -#plt.colorbar() -# -#plt.show() -### -## -#### -##plt.plot(np.linspace(0,N,N), data[int(N/2),:], label = 'GTruth') -##plt.plot(np.linspace(0,N,N), sol[int(N/2),:], label = 'Recon') -##plt.legend() -##plt.show() -# -# -##%% -## -- cgit v1.2.3 From 0bc7e54e090abbf75c75312d8787d922abde1f13 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 17 Apr 2019 16:12:03 +0100 Subject: fix pdhg tv poisson --- .../Python/ccpi/optimisation/algorithms/PDHG.py | 19 ++-- .../ccpi/optimisation/functions/KullbackLeibler.py | 104 ++++++++------------- Wrappers/Python/wip/pdhg_tv_denoising_poisson.py | 98 ++++--------------- 3 files changed, 62 insertions(+), 159 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py index a41bd48..c0c2c10 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py @@ -169,12 +169,10 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): if i%10==0: # p1 = f(operator.direct(x)) + g(x) - print(p1) -# d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) -# primal.append(p1) -# dual.append(d1) -# pdgap.append(p1-d1) -# print(p1, d1, p1-d1) + d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) + primal.append(p1) + dual.append(d1) + pdgap.append(p1-d1) else: @@ -199,11 +197,10 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): if i%10==0: # p1 = f(operator.direct(x)) + g(x) - print(p1) -# d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) -# primal.append(p1) -# dual.append(d1) -# pdgap.append(p1-d1) + d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) + primal.append(p1) + dual.append(d1) + pdgap.append(p1-d1) # print(p1, d1, p1-d1) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index 09e39bf..1a64b13 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -38,32 +38,15 @@ class KullbackLeibler(Function): def __call__(self, x): - - # TODO check -<<<<<<< HEAD - tmp = x + self.bnoise - ind = tmp.as_array()>0 - - res = x.as_array()[ind] - self.b.as_array()[ind] * numpy.log(tmp.as_array()[ind]) - - return sum(res) - -======= + res_tmp = numpy.zeros(x.shape) - self.sum_value = x + self.bnoise - if (self.sum_value.as_array()<0).any(): - self.sum_value = numpy.inf + tmp = x + self.bnoise + ind = x.as_array()>0 - if self.sum_value==numpy.inf: - return numpy.inf - else: - tmp = self.sum_value.copy() - #tmp.fill( numpy.log(tmp.as_array()) ) - self.log(tmp) - return (x - self.b * tmp ).sum() - -# return numpy.sum( x.as_array() - self.b.as_array() * numpy.log(self.sum_value.as_array())) + res_tmp[ind] = x.as_array()[ind] - self.b.as_array()[ind] * numpy.log(tmp.as_array()[ind]) + + return res_tmp.sum() def log(self, datacontainer): '''calculates the in-place log of the datacontainer''' @@ -71,7 +54,7 @@ class KullbackLeibler(Function): datacontainer.as_array().ravel(), True): raise ValueError('KullbackLeibler. Cannot calculate log of negative number') datacontainer.fill( numpy.log(datacontainer.as_array()) ) ->>>>>>> origin/composite_operator_datacontainer + def gradient(self, x, out=None): @@ -79,19 +62,19 @@ class KullbackLeibler(Function): if out is None: return 1 - self.b/(x + self.bnoise) else: -<<<<<<< HEAD - self.b.divide(x+self.bnoise, out=out) - out.subtract(1, out=out) - - def convex_conjugate(self, x): - - tmp = self.b/( 1 - x ) - ind = tmp.as_array()>0 - - sel - - return (self.b * ( ImageData( numpy.log(tmp) ) - 1 ) - self.bnoise * (x - 1)).sum() -======= +#<<<<<<< HEAD +# self.b.divide(x+self.bnoise, out=out) +# out.subtract(1, out=out) +# +# def convex_conjugate(self, x): +# +# tmp = self.b/( 1 - x ) +# ind = tmp.as_array()>0 +# +# sel +# +# return (self.b * ( ImageData( numpy.log(tmp) ) - 1 ) - self.bnoise * (x - 1)).sum() +#======= x.add(self.bnoise, out=out) self.b.divide(out, out=out) out.subtract(1, out=out) @@ -99,21 +82,17 @@ class KullbackLeibler(Function): def convex_conjugate(self, x): - tmp = self.b/( 1 - x ) - self.log(tmp) - return (self.b * ( tmp - 1 ) - self.bnoise * (x - 1)).sum() -# return self.b * ( ImageData(numpy.log(self.b/(1-x)) - 1 )) - self.bnoise * (x - 1) ->>>>>>> origin/composite_operator_datacontainer + tmp = self.b/(1-x) + ind = tmp.as_array()>0 + + return (self.b.as_array()[ind] * (numpy.log(tmp.as_array()[ind])-1)).sum() + def proximal(self, x, tau, out=None): if out is None: return 0.5 *( (x - self.bnoise - tau) + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() ) else: -<<<<<<< HEAD - tmp = 0.5 *( (x - self.bnoise - tau) + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() ) - out.fill(tmp) -======= tmp = 0.5 *( (x - self.bnoise - tau) + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() ) @@ -130,36 +109,29 @@ class KullbackLeibler(Function): out += tmp out *= 0.5 - ->>>>>>> origin/composite_operator_datacontainer - - + def proximal_conjugate(self, x, tau, out=None): if out is None: z = x + tau * self.bnoise -<<<<<<< HEAD + return 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) else: z = x + tau * self.bnoise res = 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) out.fill(res) - -======= - return (z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt() - else: - z_m = x + tau * self.bnoise - 1 - self.b.multiply(4*tau, out=out) - z_m.multiply(z_m, out=z_m) - out += z_m - out.sqrt(out=out) - # z = z_m + 2 - z_m.sqrt(out=z_m) - z_m += 2 - out *= -1 - out += z_m ->>>>>>> origin/composite_operator_datacontainer +# else: +# z_m = x + tau * self.bnoise -1 +# self.b.multiply(4*tau, out=out) +# z_m.multiply(z_m, out=z_m) +# out += z_m +# out.sqrt(out=out) +# z = z_m + 2 +# z_m.sqrt(out=z_m) +# z_m += 2 +# out *= -1 +# out += z_m def __rmul__(self, scalar): diff --git a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py index 16e6b43..ec745a2 100644 --- a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py +++ b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py @@ -27,7 +27,7 @@ from timeit import default_timer as timer # ############################################################################ # Create phantom for TV Poisson denoising -N = 100 +N = 200 data = np.zeros((N,N)) data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 @@ -36,7 +36,7 @@ ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) ag = ig # Create noisy data. Add Gaussian noise -n1 = random_noise(data, mode = 'poisson') +n1 = random_noise(data, mode = 'poisson', seed = 10) noisy_data = ImageData(n1) plt.imshow(noisy_data.as_array()) @@ -59,7 +59,7 @@ if method == '0': operator = BlockOperator(op1, op2, shape=(2,1) ) f1 = alpha * MixedL21Norm() - f2 = 0.5 * KullbackLeibler(noisy_data) + f2 = KullbackLeibler(noisy_data) f = BlockFunction(f1, f2 ) g = ZeroFunction() @@ -71,7 +71,7 @@ else: ########################################################################### operator = Gradient(ig) f = alpha * MixedL21Norm() - g = 0.5 * KullbackLeibler(noisy_data) + g = KullbackLeibler(noisy_data) ########################################################################### # Compute operator Norm @@ -133,22 +133,25 @@ except ImportError: if cvx_not_installable: ##Construct problem - u = Variable(ig.shape) + u1 = Variable(ig.shape) + q = Variable() DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) - - solver = SCS - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) + fidelity = sum( u1 - multiply(noisy_data.as_array(), log(u1)) ) + constraints = [q>= fidelity, u1>=0] + + solver = ECOS + obj = Minimize( regulariser + q) + prob = Problem(obj, constraints) result = prob.solve(verbose = True, solver = solver) + - diff_cvx = numpy.abs( res.as_array() - u.value ) + diff_cvx = numpy.abs( res.as_array() - u1.value ) # Show result plt.figure(figsize=(15,15)) @@ -158,7 +161,7 @@ if cvx_not_installable: plt.colorbar() plt.subplot(3,1,2) - plt.imshow(u.value) + plt.imshow(u1.value) plt.title('CVX solution') plt.colorbar() @@ -172,72 +175,3 @@ if cvx_not_installable: -#pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -#pdhg.max_iteration = 2000 -#pdhg.update_objective_interval = 10 -# -#pdhg.run(2000) - - - -#sol = pdhg.get_output().as_array() -##sol = result.as_array() -## -#fig = plt.figure() -#plt.subplot(1,2,1) -#plt.imshow(noisy_data.as_array()) -##plt.colorbar() -#plt.subplot(1,2,2) -#plt.imshow(sol) -##plt.colorbar() -#plt.show() -## - -## -#plt.plot(np.linspace(0,N,N), data[int(N/2),:], label = 'GTruth') -#plt.plot(np.linspace(0,N,N), sol[int(N/2),:], label = 'Recon') -#plt.legend() -#plt.show() - - -#%% Compare with cvx - -#try_cvx = input("Do you want CVX comparison (0/1)") -# -#if try_cvx=='0': -# -# from cvxpy import * -# import sys -# sys.path.insert(0,'/Users/evangelos/Desktop/Projects/CCPi/CCPi-Framework/Wrappers/Python/ccpi/optimisation/cvx_scripts') -# from cvx_functions import TV_cvx -# -# u = Variable((N, N)) -# fidelity = pnorm( u - noisy_data.as_array(),1) -# regulariser = alpha * TV_cvx(u) -# solver = MOSEK -# obj = Minimize( regulariser + fidelity) -# constraints = [] -# prob = Problem(obj, constraints) -# -# # Choose solver (SCS is fast but less accurate than MOSEK) -# result = prob.solve(verbose = True, solver = solver) -# -# print('Objective value is {} '.format(obj.value)) -# -# diff_pdhg_cvx = np.abs(u.value - res.as_array()) -# plt.imshow(diff_pdhg_cvx) -# plt.colorbar() -# plt.title('|CVX-PDHG|') -# plt.show() -# -# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') -# plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'PDHG') -# plt.legend() -# plt.show() -# -#else: -# print('No CVX solution available') - - - - -- cgit v1.2.3 From cc9e5d510378e1810d0a1a6b0fe70279389255be Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Wed, 17 Apr 2019 16:33:13 +0100 Subject: fixed conflict --- Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index cc3356a..c277482 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -20,12 +20,8 @@ import numpy from ccpi.optimisation.functions import Function from ccpi.optimisation.functions.ScaledFunction import ScaledFunction -<<<<<<< HEAD -from ccpi.framework import ImageData -======= from ccpi.framework import ImageData, ImageGeometry import functools ->>>>>>> composite_operator_datacontainer class KullbackLeibler(Function): -- cgit v1.2.3 From df538e0d55513274ba27285764783359ef7b5c6c Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 17 Apr 2019 17:53:31 +0100 Subject: fix with cvxpy tomo2D recon --- .../Python/ccpi/optimisation/algorithms/PDHG.py | 2 +- .../ccpi/optimisation/functions/L2NormSquared.py | 12 +-- Wrappers/Python/wip/pdhg_TV_denoising.py | 17 +++- .../Python/wip/pdhg_TV_denoising_salt_pepper.py | 9 +- Wrappers/Python/wip/pdhg_TV_tomography2D.py | 102 +++++++++++++++++---- Wrappers/Python/wip/pdhg_tv_denoising_poisson.py | 8 ++ 6 files changed, 116 insertions(+), 34 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py index c0c2c10..6360ac1 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py @@ -182,7 +182,7 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): f.proximal_conjugate(y_tmp, sigma, out=y) operator.adjoint(y, out = x_tmp) - x_tmp *= -tau + x_tmp *= -1*tau x_tmp += x_old g.proximal(x_tmp, tau, out = x) diff --git a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py index 8a16c28..ca6e7a7 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py +++ b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py @@ -94,12 +94,12 @@ class L2NormSquared(Function): return x/(1+2*tau) else: - tmp = x.subtract(self.b) - #tmp -= self.b - tmp /= (1+2*tau) - tmp += self.b - return tmp -# return (x-self.b)/(1+2*tau) + self.b +# tmp = x.subtract(self.b) +# tmp -= self.b +# tmp /= (1+2*tau) +# tmp += self.b +# return tmp + return (x-self.b)/(1+2*tau) + self.b # if self.b is not None: # out=x diff --git a/Wrappers/Python/wip/pdhg_TV_denoising.py b/Wrappers/Python/wip/pdhg_TV_denoising.py index 0e7f628..f3980c4 100755 --- a/Wrappers/Python/wip/pdhg_TV_denoising.py +++ b/Wrappers/Python/wip/pdhg_TV_denoising.py @@ -45,7 +45,7 @@ plt.title('Noisy data') plt.show() # Regularisation Parameter -alpha = 2 +alpha = 0.5 #method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") method = '1' @@ -83,8 +83,8 @@ normK = operator.norm() sigma = 1 tau = 1/(sigma*normK**2) -opt = {'niter':2000} -opt1 = {'niter':2000, 'memopt': True} +opt = {'niter':5000} +opt1 = {'niter':5000, 'memopt': True} t1 = timer() res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) @@ -149,7 +149,8 @@ if cvx_not_installable: if 'MOSEK' in installed_solvers(): solver = MOSEK else: - solver = SCS + solver = SCS + obj = Minimize( regulariser + fidelity) prob = Problem(obj) result = prob.solve(verbose = True, solver = solver) @@ -172,6 +173,14 @@ if cvx_not_installable: plt.imshow(diff_cvx) plt.title('Difference') plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + + + print('Primal Objective (CVX) {} '.format(obj.value)) print('Primal Objective (PDHG) {} '.format(primal[-1])) diff --git a/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py b/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py index cec9770..364d879 100644 --- a/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py +++ b/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py @@ -54,13 +54,8 @@ if method == '0': op1 = Gradient(ig) op2 = Identity(ig, ag) - # Form Composite Operator operator = BlockOperator(op1, op2, shape=(2,1) ) - #### Create functions -# f = FunctionComposition_new(operator, mixed_L12Norm(alpha), \ -# L2NormSq(0.5, b = noisy_data) ) - f1 = alpha * MixedL21Norm() f2 = L1Norm(b = noisy_data) @@ -73,12 +68,12 @@ else: # No Composite # ########################################################################### operator = Gradient(ig) - f = alpha * FunctionOperatorComposition(operator, MixedL21Norm()) + f = alpha * MixedL21Norm() g = L1Norm(b = noisy_data) ########################################################################### #%% -diag_precon = True +diag_precon = False if diag_precon: diff --git a/Wrappers/Python/wip/pdhg_TV_tomography2D.py b/Wrappers/Python/wip/pdhg_TV_tomography2D.py index d0ef097..1d4023b 100644 --- a/Wrappers/Python/wip/pdhg_TV_tomography2D.py +++ b/Wrappers/Python/wip/pdhg_TV_tomography2D.py @@ -43,7 +43,7 @@ from timeit import default_timer as timer #ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) #data = ImageData(phantom_2D, geometry=ig) -N = 75 +N = 50 x = np.zeros((N,N)) x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 @@ -52,8 +52,8 @@ data = ImageData(x) ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -detectors = 150 -angles = np.linspace(0,np.pi,100) +detectors = 50 +angles = np.linspace(0,np.pi,50) ag = AcquisitionGeometry('parallel','2D',angles, detectors) Aop = AstraProjectorSimple(ig, ag, 'cpu') @@ -75,11 +75,6 @@ plt.title('Noisy Sinogram') plt.colorbar() plt.show() - -#%% Works only with Composite Operator Structure of PDHG - -#ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - # Create operators op1 = Gradient(ig) op2 = Aop @@ -109,19 +104,18 @@ if diag_precon: tau, sigma = tau_sigma_precond(operator) -else: +else: + normK = operator.norm() sigma = 1 tau = 1/(sigma*normK**2) # Compute operator Norm -normK = operator.norm() + # Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -opt = {'niter':2000} -opt1 = {'niter':2000, 'memopt': True} +opt = {'niter':5000} +opt1 = {'niter':5000, 'memopt': True} t1 = timer() res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) @@ -132,9 +126,7 @@ t3 = timer() res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) t4 = timer() # -print ("No memopt in {}s, memopt in {}s ".format(t2-t1, t4 -t3)) -#%% plt.figure(figsize=(15,15)) plt.subplot(3,1,1) plt.imshow(res.as_array()) @@ -160,3 +152,81 @@ diff = (res1 - res).abs().as_array().max() # print(" Max of abs difference is {}".format(diff)) + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff +import astra +import numpy + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + + u = Variable( N*N) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + + # create matrix representation for Astra operator + + vol_geom = astra.create_vol_geom(N, N) + proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + + proj_id = astra.create_projector('strip', proj_geom, vol_geom) + + matrix_id = astra.projector.matrix(proj_id) + + ProjMat = astra.matrix.get(matrix_id) + + fidelity = 0.5 * sum_squares(ProjMat * u - noisy_data.as_array().ravel()) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + +#%% + + u_rs = np.reshape(u.value, (N,N)) + + diff_cvx = numpy.abs( res.as_array() - u_rs ) + + # Show result + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(res.as_array()) + plt.title('PDHG solution') + plt.colorbar() + + plt.subplot(3,1,2) + plt.imshow(u_rs) + plt.title('CVX solution') + plt.colorbar() + + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u_rs[int(N/2),:], label = 'CVX') + plt.legend() + + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(primal[-1])) \ No newline at end of file diff --git a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py index ec745a2..82384ef 100644 --- a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py +++ b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py @@ -169,9 +169,17 @@ if cvx_not_installable: plt.imshow(diff_cvx) plt.title('Difference') plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') + plt.legend() + print('Primal Objective (CVX) {} '.format(obj.value)) print('Primal Objective (PDHG) {} '.format(primal[-1])) + + -- cgit v1.2.3 From b200f96782c3756110bfbfc3c6cf5093c7d2a12b Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Sat, 20 Apr 2019 18:45:39 +0100 Subject: fix KL & L2 --- Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py | 2 ++ Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index 1a64b13..22d21fd 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -93,6 +93,7 @@ class KullbackLeibler(Function): if out is None: return 0.5 *( (x - self.bnoise - tau) + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() ) else: + tmp = 0.5 *( (x - self.bnoise - tau) + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() ) @@ -152,6 +153,7 @@ if __name__ == '__main__': from ccpi.framework import ImageGeometry import numpy + N, M = 2,3 ig = ImageGeometry(N, M) data = ImageData(numpy.random.randint(-10, 10, size=(M, N))) diff --git a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py index ca6e7a7..5490782 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py +++ b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py @@ -71,7 +71,7 @@ class L2NormSquared(Function): def convex_conjugate(self, x): ''' Evaluate convex conjugate of L2NormSquared at x: f^{*}(x)''' - + tmp = 0 if self.b is not None: -- cgit v1.2.3 From 1ef1cd2c55ee4dc132f89ccef62961cb9f11d800 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Sat, 20 Apr 2019 18:47:02 +0100 Subject: pdhg examples --- Wrappers/Python/wip/pdhg_TV_denoising.py | 162 +++++++++++--------- .../Python/wip/pdhg_TV_denoising_salt_pepper.py | 167 ++++++++++++++++----- Wrappers/Python/wip/pdhg_TV_tomography2D.py | 162 ++++++++++---------- Wrappers/Python/wip/pdhg_tv_denoising_poisson.py | 8 +- 4 files changed, 304 insertions(+), 195 deletions(-) diff --git a/Wrappers/Python/wip/pdhg_TV_denoising.py b/Wrappers/Python/wip/pdhg_TV_denoising.py index f3980c4..24fe216 100755 --- a/Wrappers/Python/wip/pdhg_TV_denoising.py +++ b/Wrappers/Python/wip/pdhg_TV_denoising.py @@ -26,7 +26,7 @@ from timeit import default_timer as timer # Create phantom for TV Gaussian denoising -N = 100 +N = 500 data = np.zeros((N,N)) data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 @@ -48,7 +48,7 @@ plt.show() alpha = 0.5 #method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") -method = '1' +method = '0' if method == '0': @@ -73,29 +73,43 @@ else: # No Composite # ########################################################################### operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = 0.5 * L2NormSquared(b = noisy_data) + f = alpha * MixedL21Norm() + g = 0.5 * L2NormSquared(b = noisy_data) -# Compute operator Norm -normK = operator.norm() + +diag_precon = False + +if diag_precon: + + def tau_sigma_precond(operator): + + tau = 1/operator.sum_abs_row() + sigma = 1/ operator.sum_abs_col() + + return tau, sigma -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) + tau, sigma = tau_sigma_precond(operator) + +else: + # Compute operator Norm + normK = operator.norm() + + # Primal & dual stepsizes + sigma = 1 + tau = 1/(sigma*normK**2) -opt = {'niter':5000} -opt1 = {'niter':5000, 'memopt': True} + +opt = {'niter':2000} +opt1 = {'niter':2000, 'memopt': True} t1 = timer() res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) t2 = timer() - t3 = timer() res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) t4 = timer() -# -##%% + plt.figure(figsize=(15,15)) plt.subplot(3,1,1) plt.imshow(res.as_array()) @@ -124,66 +138,66 @@ print(" Max of abs difference is {}".format(diff)) #%% Check with CVX solution -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( res.as_array() - u.value ) - - # Show result - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(res.as_array()) - plt.title('PDHG solution') - plt.colorbar() - - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - - - - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(primal[-1])) +#from ccpi.optimisation.operators import SparseFiniteDiff +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +# +#if cvx_not_installable: +# +# ##Construct problem +# u = Variable(ig.shape) +# +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# # Define Total Variation as a regulariser +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) +# fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) +# +# # choose solver +# if 'MOSEK' in installed_solvers(): +# solver = MOSEK +# else: +# solver = SCS +# +# obj = Minimize( regulariser + fidelity) +# prob = Problem(obj) +# result = prob.solve(verbose = True, solver = solver) +# +# diff_cvx = numpy.abs( res.as_array() - u.value ) +# +# # Show result +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(res.as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# +# plt.subplot(3,1,2) +# plt.imshow(u.value) +# plt.title('CVX solution') +# plt.colorbar() +# +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() +# +# plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') +# plt.legend() +# +# +# +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(primal[-1])) diff --git a/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py b/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py index 364d879..bd330fc 100644 --- a/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py +++ b/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py @@ -8,18 +8,20 @@ Created on Fri Feb 22 14:53:03 2019 from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer -import numpy as np +import numpy as np +import numpy import matplotlib.pyplot as plt from ccpi.optimisation.algorithms import PDHG, PDHG_old from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFun, L1Norm, \ - MixedL21Norm, FunctionOperatorComposition, BlockFunction - +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, FunctionOperatorComposition, BlockFunction, ScaledFunction from skimage.util import random_noise +from timeit import default_timer as timer + # ############################################################################ @@ -60,7 +62,7 @@ if method == '0': f2 = L1Norm(b = noisy_data) f = BlockFunction(f1, f2 ) - g = ZeroFun() + g = ZeroFunction() else: @@ -89,20 +91,48 @@ if diag_precon: else: # Compute operator Norm normK = operator.norm() - print ("normK", normK) + # Primal & dual stepsizes - sigma = 1/normK - tau = 1/normK -# tau = 1/(sigma*normK**2) + sigma = 1 + tau = 1/(sigma*normK**2) + -opt = {'niter':2000} +opt = {'niter':5000} +opt1 = {'niter':5000, 'memopt': True} +t1 = timer() res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) - -plt.figure(figsize=(5,5)) +t2 = timer() + + +t3 = timer() +res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) +t4 = timer() + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) plt.imshow(res.as_array()) +plt.title('no memopt') plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(res1.as_array()) +plt.title('memopt') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow((res1 - res).abs().as_array()) +plt.title('diff') +plt.colorbar() +plt.show() +# +plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'memopt') +plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'no memopt') +plt.legend() plt.show() +# +print ("Time: No memopt in {}s, \n Time: Memopt in {}s ".format(t2-t1, t4 -t3)) +diff = (res1 - res).abs().as_array().max() +# +print(" Max of abs difference is {}".format(diff)) #pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) #pdhg.max_iteration = 2000 @@ -132,43 +162,106 @@ plt.show() #plt.show() -#%% Compare with cvx - -try_cvx = input("Do you want CVX comparison (0/1)") +#%% Check with CVX solution -if try_cvx=='0': +from ccpi.optimisation.operators import SparseFiniteDiff +try: from cvxpy import * - import sys - sys.path.insert(0,'/Users/evangelos/Desktop/Projects/CCPi/CCPi-Framework/Wrappers/Python/ccpi/optimisation/cvx_scripts') - from cvx_functions import TV_cvx + cvx_not_installable = True +except ImportError: + cvx_not_installable = False - u = Variable((N, N)) + +if cvx_not_installable: + + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = pnorm( u - noisy_data.as_array(),1) - regulariser = alpha * TV_cvx(u) - solver = MOSEK + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + obj = Minimize( regulariser + fidelity) - constraints = [] - prob = Problem(obj, constraints) - - # Choose solver (SCS is fast but less accurate than MOSEK) + prob = Problem(obj) result = prob.solve(verbose = True, solver = solver) - - print('Objective value is {} '.format(obj.value)) - - diff_pdhg_cvx = np.abs(u.value - res.as_array()) - plt.imshow(diff_pdhg_cvx) + + diff_cvx = numpy.abs( res.as_array() - u.value ) + +# Show result + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(res.as_array()) + plt.title('PDHG solution') + plt.colorbar() + + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') plt.colorbar() - plt.title('|CVX-PDHG|') plt.show() - + + plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'PDHG') plt.legend() - plt.show() + -else: - print('No CVX solution available') + + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(primal[-1])) + + + +#try_cvx = input("Do you want CVX comparison (0/1)") +# +#if try_cvx=='0': +# +# from cvxpy import * +# import sys +# sys.path.insert(0,'/Users/evangelos/Desktop/Projects/CCPi/CCPi-Framework/Wrappers/Python/ccpi/optimisation/cvx_scripts') +# from cvx_functions import TV_cvx +# +# u = Variable((N, N)) +# fidelity = pnorm( u - noisy_data.as_array(),1) +# regulariser = alpha * TV_cvx(u) +# solver = MOSEK +# obj = Minimize( regulariser + fidelity) +# constraints = [] +# prob = Problem(obj, constraints) +# +# # Choose solver (SCS is fast but less accurate than MOSEK) +# result = prob.solve(verbose = True, solver = solver) +# +# print('Objective value is {} '.format(obj.value)) +# +# diff_pdhg_cvx = np.abs(u.value - res.as_array()) +# plt.imshow(diff_pdhg_cvx) +# plt.colorbar() +# plt.title('|CVX-PDHG|') +# plt.show() +# +# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') +# plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'PDHG') +# plt.legend() +# plt.show() +# +#else: +# print('No CVX solution available') diff --git a/Wrappers/Python/wip/pdhg_TV_tomography2D.py b/Wrappers/Python/wip/pdhg_TV_tomography2D.py index 1d4023b..2713cfd 100644 --- a/Wrappers/Python/wip/pdhg_TV_tomography2D.py +++ b/Wrappers/Python/wip/pdhg_TV_tomography2D.py @@ -43,7 +43,7 @@ from timeit import default_timer as timer #ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) #data = ImageData(phantom_2D, geometry=ig) -N = 50 +N = 75 x = np.zeros((N,N)) x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 @@ -52,8 +52,8 @@ data = ImageData(x) ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -detectors = 50 -angles = np.linspace(0,np.pi,50) +detectors = N +angles = np.linspace(0,np.pi,N) ag = AcquisitionGeometry('parallel','2D',angles, detectors) Aop = AstraProjectorSimple(ig, ag, 'cpu') @@ -82,7 +82,7 @@ op2 = Aop # Form Composite Operator operator = BlockOperator(op1, op2, shape=(2,1) ) -alpha = 50 +alpha = 10 f = BlockFunction( alpha * MixedL21Norm(), \ 0.5 * L2NormSquared(b = noisy_data) ) g = ZeroFunction() @@ -114,8 +114,8 @@ else: # Primal & dual stepsizes -opt = {'niter':5000} -opt1 = {'niter':5000, 'memopt': True} +opt = {'niter':2000} +opt1 = {'niter':2000, 'memopt': True} t1 = timer() res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) @@ -155,78 +155,78 @@ print(" Max of abs difference is {}".format(diff)) #%% Check with CVX solution -from ccpi.optimisation.operators import SparseFiniteDiff -import astra -import numpy - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - - u = Variable( N*N) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - - # create matrix representation for Astra operator - - vol_geom = astra.create_vol_geom(N, N) - proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) - - proj_id = astra.create_projector('strip', proj_geom, vol_geom) - - matrix_id = astra.projector.matrix(proj_id) - - ProjMat = astra.matrix.get(matrix_id) - - fidelity = 0.5 * sum_squares(ProjMat * u - noisy_data.as_array().ravel()) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - -#%% - - u_rs = np.reshape(u.value, (N,N)) - - diff_cvx = numpy.abs( res.as_array() - u_rs ) - - # Show result - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(res.as_array()) - plt.title('PDHG solution') - plt.colorbar() - - plt.subplot(3,1,2) - plt.imshow(u_rs) - plt.title('CVX solution') - plt.colorbar() - - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u_rs[int(N/2),:], label = 'CVX') - plt.legend() - - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(primal[-1])) \ No newline at end of file +#from ccpi.optimisation.operators import SparseFiniteDiff +#import astra +#import numpy +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +# +#if cvx_not_installable: +# +# +# u = Variable( N*N) +# +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) +# +# # create matrix representation for Astra operator +# +# vol_geom = astra.create_vol_geom(N, N) +# proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) +# +# proj_id = astra.create_projector('strip', proj_geom, vol_geom) +# +# matrix_id = astra.projector.matrix(proj_id) +# +# ProjMat = astra.matrix.get(matrix_id) +# +# fidelity = 0.5 * sum_squares(ProjMat * u - noisy_data.as_array().ravel()) +# +# # choose solver +# if 'MOSEK' in installed_solvers(): +# solver = MOSEK +# else: +# solver = SCS +# +# obj = Minimize( regulariser + fidelity) +# prob = Problem(obj) +# result = prob.solve(verbose = True, solver = solver) +# +##%% +# +# u_rs = np.reshape(u.value, (N,N)) +# +# diff_cvx = numpy.abs( res.as_array() - u_rs ) +# +# # Show result +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(res.as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# +# plt.subplot(3,1,2) +# plt.imshow(u_rs) +# plt.title('CVX solution') +# plt.colorbar() +# +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() +# +# plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), u_rs[int(N/2),:], label = 'CVX') +# plt.legend() +# +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(primal[-1])) \ No newline at end of file diff --git a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py index 82384ef..2c7e145 100644 --- a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py +++ b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py @@ -48,7 +48,7 @@ alpha = 2 #method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") -method = '0' +method = '1' if method == '0': # Create operators @@ -81,8 +81,8 @@ normK = operator.norm() sigma = 1 tau = 1/(sigma*normK**2) -opt = {'niter':2000} -opt1 = {'niter':2000, 'memopt': True} +opt = {'niter':5000} +opt1 = {'niter':5000, 'memopt': True} t1 = timer() res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) @@ -92,6 +92,8 @@ t3 = timer() res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) t4 = timer() +print(pdgap[-1]) + plt.figure(figsize=(15,15)) plt.subplot(3,1,1) -- cgit v1.2.3 From 55691dadd3a8b3755e25f398236e9d9a46e5f8c3 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Sat, 20 Apr 2019 18:47:39 +0100 Subject: fixes for TGV --- .../operators/FiniteDifferenceOperator.py | 2 +- .../operators/SymmetrizedGradientOperator.py | 262 +++++++++++++-------- 2 files changed, 159 insertions(+), 105 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/operators/FiniteDifferenceOperator.py b/Wrappers/Python/ccpi/optimisation/operators/FiniteDifferenceOperator.py index 2c42532..f459334 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/FiniteDifferenceOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/FiniteDifferenceOperator.py @@ -41,7 +41,7 @@ class FiniteDiff(LinearOperator): #self.voxel_size = kwargs.get('voxel_size',1) # this wrongly assumes a homogeneous voxel size - self.voxel_size = self.gm_domain.voxel_size_x +# self.voxel_size = self.gm_domain.voxel_size_x def direct(self, x, out=None): diff --git a/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py b/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py index 3fc43b8..25af156 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py @@ -21,94 +21,105 @@ class SymmetrizedGradient(Gradient): ''' Domain of SymGrad is the Range of Gradient ''' + self.gm_domain = self.gm_range self.bnd_cond = bnd_cond self.channels = self.gm_range.get_item(0).channels - if self.correlation=='Space': - if self.channels>1: - pass - else: -# # 2D image ---> Dx v1, Dyv2, Dx - tmp = self.gm_domain.geometries + (self.gm_domain.get_item(0),) - self.gm_range = BlockGeometry(*tmp ) - self.ind1 = range(self.gm_domain.get_item(0).length) - self.ind2 = range(self.gm_domain.get_item(0).length-1, -1, -1) -# self.order = myorder = [0,1,2 3] - - elif self.correlation=='SpaceChannels': - if self.channels>1: - pass - else: - raise ValueError('No channels to correlate') + tmp_gm = len(self.gm_domain.geometries)*self.gm_domain.geometries + + self.gm_range = BlockGeometry(*tmp_gm) + + self.FD = FiniteDiff(self.gm_domain, direction = 0, bnd_cond = self.bnd_cond) + + if self.gm_domain.shape[0]==2: + self.order_ind = [0,2,1,3] + else: + self.order_ind = [0,3,6,1,4,7,2,5,8] + + +# if self.correlation=='Space': +# if self.channels>1: +# pass +# else: +## # 2D image ---> Dx v1, Dyv2, Dx +# tmp = self.gm_domain.geometries + (self.gm_domain.get_item(0),) +# self.gm_range = BlockGeometry(*tmp ) +# self.ind1 = range(self.gm_domain.get_item(0).length) +# self.ind2 = range(self.gm_domain.get_item(0).length-1, -1, -1) +## self.order = myorder = [0,1,2 3] +# +# elif self.correlation=='SpaceChannels': +# if self.channels>1: +# pass +# else: +# raise ValueError('No channels to correlate') def direct(self, x, out=None): -# tmp = numpy.zeros(self.gm_range) -# tmp[0] = FiniteDiff(self.gm_domain[1:], direction = 1, bnd_cond = self.bnd_cond).adjoint(x.as_array()[0]) -# tmp[1] = FiniteDiff(self.gm_domain[1:], direction = 0, bnd_cond = self.bnd_cond).adjoint(x.as_array()[1]) -# tmp[2] = 0.5 * (FiniteDiff(self.gm_domain[1:], direction = 0, bnd_cond = self.bnd_cond).adjoint(x.as_array()[0]) + -# FiniteDiff(self.gm_domain[1:], direction = 1, bnd_cond = self.bnd_cond).adjoint(x.as_array()[1]) ) -# -# return type(x)(tmp) - - tmp = [[None]*2]*2 - for i in range(2): - for j in range(2): - tmp[i][j]=FiniteDiff(self.gm_domain.get_item(0), direction = i, bnd_cond = self.bnd_cond).adjoint(x.get_item(j)) - tmp = numpy.array(tmp) - z = 0.5 * (tmp.T + tmp) - z = z.to - - return BlockDataContainer(*z.tolist()) + if out is None: + + tmp = [] + for i in range(self.gm_domain.shape[0]): + for j in range(x.shape[0]): + self.FD.direction = i + tmp.append(self.FD.adjoint(x.get_item(j))) + + tmp1 = [tmp[i] for i in self.order_ind] + + res = [0.5 * sum(x) for x in zip(tmp, tmp1)] + + return BlockDataContainer(*res) + def adjoint(self, x, out=None): - pass - - res = [] - for i in range(2): - tmp = ImageData(np.zeros(x.get_item(0))) - for j in range(2): - tmp += FiniteDiff(self.gm_domain.get_item(0), direction = i, bnd_cond = self.bnd_cond).direct(x.get_item(j)) - res.append(tmp) - return res - - -# for - -# tmp = numpy.zeros(self.gm_domain) -# -# tmp[0] = FiniteDiff(self.gm_domain[1:], direction = 1, bnd_cond = self.bnd_cond).direct(x.as_array()[0]) + \ -# FiniteDiff(self.gm_domain[1:], direction = 0, bnd_cond = self.bnd_cond).direct(x.as_array()[2]) -# -# tmp[1] = FiniteDiff(self.gm_domain[1:], direction = 1, bnd_cond = self.bnd_cond).direct(x.as_array()[2]) + \ -# FiniteDiff(self.gm_domain[1:], direction = 0, bnd_cond = self.bnd_cond).direct(x.as_array()[1]) -# -# return type(x)(tmp) + if out is None: - def alloc_domain_dim(self): - return ImageData(numpy.zeros(self.gm_domain)) - - def alloc_range_dim(self): - return ImageData(numpy.zeros(self.range_dim)) - - def domain_dim(self): + tmp = [None]*self.gm_domain.shape[0] + i = 0 + + for k in range(self.gm_domain.shape[0]): + tmp1 = 0 + for j in range(self.gm_domain.shape[0]): + self.FD.direction = j + tmp1 += self.FD.direct(x[i]) + i+=1 + tmp[k] = tmp1 + return BlockDataContainer(*tmp) + +# i = 0 +# +# tmp = self.gm_domain.allocate() +# for k in range(tmp.shape[0]): +# tmp1 = 0 +# for j in range(tmp.shape[0]): +# self.FD.direction = j +# tmp1 += self.FD.direct(x[i]) +# i+=1 +# tmp.get_item(k).fill(tmp1) +# return tmp + + else: + pass + + + def domain_geometry(self): return self.gm_domain - def range_dim(self): + def range_geometry(self): return self.gm_range def norm(self): -# return np.sqrt(4*len(self.domainDim())) - #TODO this takes time for big ImageData - # for 2D ||grad|| = sqrt(8), 3D ||grad|| = sqrt(12) - x0 = ImageData(np.random.random_sample(self.domain_dim())) - self.s1, sall, svec = LinearOperator.PowerMethod(self, 25, x0) - return self.s1 + + #TODO need dot method for BlockDataContainer + return numpy.sqrt(4*self.gm_domain.shape[0]) +# x0 = self.gm_domain.allocate('random_int') +# self.s1, sall, svec = LinearOperator.PowerMethod(self, 10, x0) +# return self.s1 @@ -124,57 +135,100 @@ if __name__ == '__main__': K = 2 ig1 = ImageGeometry(N, M) - ig2 = ImageGeometry(N, M, channels=K) + ig2 = ImageGeometry(N, M, K) E1 = SymmetrizedGradient(ig1, correlation = 'Space', bnd_cond='Neumann') - E2 = SymmetrizedGradient(ig2, correlation = 'SpaceChannels', bnd_cond='Periodic') + E2 = SymmetrizedGradient(ig2, correlation = 'Space', bnd_cond='Periodic') - print(E1.domain_geometry().shape) - print(E2.domain_geometry().shape) + print(E1.domain_geometry().shape, E1.range_geometry().shape) + print(E2.domain_geometry().shape, E2.range_geometry().shape) - u1 = E1.gm_domain.allocate('random_int') - u2 = E2.gm_domain.allocate('random_int') + u1 = E1.gm_domain.allocate('random_int') + a1 = ig1.allocate('random_int') + a2 = ig1.allocate('random_int') + a3 = ig1.allocate('random_int') + w1 = BlockDataContainer(*[a1, a2, a3]) + w11 = BlockDataContainer(*[a1, a2, a2, a3]) + + sym_direct = [None]*3 + + sym_direct[0] = FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').adjoint(u1[0]) + sym_direct[1] = FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').adjoint(u1[1]) + sym_direct[2] = 0.5 * (FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').adjoint(u1[0]) + \ + FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').adjoint(u1[1])) + + sym_direct1 = [None]*4 + + sym_direct1[0] = FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').adjoint(u1[0]) + + sym_direct1[1] = 0.5 * (FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').adjoint(u1[1]) + \ + FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').adjoint(u1[0])) + + sym_direct1[2] = 0.5 * (FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').adjoint(u1[1]) + \ + FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').adjoint(u1[0])) + + sym_direct1[3] = FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').adjoint(u1[1]) + + + sym_adjoint = [None]*2 + + sym_adjoint[0] = FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').direct(w1[0]) + \ + FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').direct(w1[2]) + sym_adjoint[1] = FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').direct(w1[2]) + \ + FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').direct(w1[1]) + + sym_adjoint1 = [None]*2 + + sym_adjoint1[0] = FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').direct(w11[0]) + \ + FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').direct(w11[1]) + sym_adjoint1[1] = FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').direct(w11[2]) + \ + FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').direct(w11[3]) + - res = E1.direct(u1) + LHS = (sym_direct[0] * w1[0] + \ + sym_direct[1] * w1[1] + \ + 2*sym_direct[2] * w1[2]).sum() - res1 = E1.adjoint(res) + RHS = (u1[0]*sym_adjoint[0] + u1[1]*sym_adjoint[1]).sum() -# Dx = FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann') -# Dy = FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann') -# -# B = BlockOperator(Dy, Dx) -# V = BlockDataContainer(u1,u2) -# -# res = B.adjoint(V) + print(LHS, RHS) -# ig = (N,M) -# ig2 = (2,) + ig -# ig3 = (3,) + ig -# u1 = ig.allocate('random_int') -# w1 = E.gm_range.allocate('random_int') -# DataContainer(np.random.randint(10, size=ig3)) + LHS = (sym_direct1[0] * w11[0] + \ + sym_direct1[1] * w11[1] + \ + sym_direct1[2] * w11[2] + \ + sym_direct1[3] * w11[3] ).sum() + RHS = (u1[0]*sym_adjoint1[0] + u1[1]*sym_adjoint1[1]).sum() + print(LHS, RHS) -# d1 = E.direct(u1) -# d2 = E.adjoint(w1) -# LHS = (d1.as_array()[0]*w1.as_array()[0] + \ -# d1.as_array()[1]*w1.as_array()[1] + \ -# 2*d1.as_array()[2]*w1.as_array()[2]).sum() -# -# RHS = (u1.as_array()[0]*d2.as_array()[0] + \ -# u1.as_array()[1]*d2.as_array()[1]).sum() -# -# -# print(LHS, RHS, E.norm()) -# -# + a1 = (E1.direct(u1) * w11).sum() + b1 = (u1 * E1.adjoint(w11)).sum() + print(a1, b1) + + + u2 = E2.gm_domain.allocate('random_int') + + aa1 = ig2.allocate('random_int') + aa2 = ig2.allocate('random_int') + aa3 = ig2.allocate('random_int') + aa4 = ig2.allocate('random_int') + aa5 = ig2.allocate('random_int') + aa6 = ig2.allocate('random_int') + w2 = BlockDataContainer(*[aa1, aa2, aa3, \ + aa2, aa4, aa5, \ + aa3, aa5, aa6]) + tmp1 = E2.direct(u2) + tmp2 = E2.adjoint(w2) + c1 = (tmp1 * w2).sum() + d1 = (u2 * tmp2).sum() + print(c1, d1) @@ -182,4 +236,4 @@ if __name__ == '__main__': - \ No newline at end of file + \ No newline at end of file -- cgit v1.2.3 From cc774263646d61bbc224911903e4e2e8f5e323dc Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Sat, 20 Apr 2019 18:48:00 +0100 Subject: change to ZeroOperator --- .../ccpi/optimisation/operators/ZeroOperator.py | 26 +++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/operators/ZeroOperator.py b/Wrappers/Python/ccpi/optimisation/operators/ZeroOperator.py index a7c5f09..d5fd8ae 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/ZeroOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/ZeroOperator.py @@ -8,32 +8,36 @@ Created on Wed Mar 6 19:25:53 2019 import numpy as np from ccpi.framework import ImageData -from ccpi.optimisation.operators import Operator +from ccpi.optimisation.operators import LinearOperator -class ZeroOp(Operator): +class ZeroOperator(LinearOperator): - def __init__(self, gm_domain, gm_range): + def __init__(self, gm_domain, gm_range=None): + self.gm_domain = gm_domain - self.gm_range = gm_range - super(ZeroOp, self).__init__() + self.gm_range = gm_range + if self.gm_range is None: + self.gm_range = self.gm_domain + + super(ZeroOperator, self).__init__() def direct(self,x,out=None): if out is None: - return ImageData(np.zeros(self.gm_range)) + return self.gm_range.allocate() else: - return ImageData(np.zeros(self.gm_range)) + return self.gm_range.allocate() def adjoint(self,x, out=None): if out is None: - return ImageData(np.zeros(self.gm_domain)) + return self.gm_domain.allocate() else: - return ImageData(np.zeros(self.gm_domain)) + return self.gm_domain.allocate() def norm(self): return 0 - def domain_dim(self): + def domain_geometry(self): return self.gm_domain - def range_dim(self): + def range_geometry(self): return self.gm_range \ No newline at end of file -- cgit v1.2.3 From cfe16a4d31f4c6d1748edadcfc0706bd6f9ee7cf Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Sat, 20 Apr 2019 18:48:39 +0100 Subject: changes for SymmetrizedGradient --- .../ccpi/optimisation/functions/MixedL21Norm.py | 60 ++++++++-------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/MixedL21Norm.py b/Wrappers/Python/ccpi/optimisation/functions/MixedL21Norm.py index 2004e5f..e8f6da4 100755 --- a/Wrappers/Python/ccpi/optimisation/functions/MixedL21Norm.py +++ b/Wrappers/Python/ccpi/optimisation/functions/MixedL21Norm.py @@ -43,19 +43,9 @@ class MixedL21Norm(Function): ''' if not isinstance(x, BlockDataContainer): raise ValueError('__call__ expected BlockDataContainer, got {}'.format(type(x))) - - if self.SymTensor: - - #TODO fix this case - param = [1]*x.shape[0] - param[-1] = 2 - tmp = [param[i]*(x[i] ** 2) for i in range(x.shape[0])] - res = sum(tmp).sqrt().sum() - - else: - - tmp = [ el**2 for el in x.containers ] - res = sum(tmp).sqrt().sum() + + tmp = [ el**2 for el in x.containers ] + res = sum(tmp).sqrt().sum() return res @@ -67,7 +57,12 @@ class MixedL21Norm(Function): ''' This is the Indicator function of ||\cdot||_{2, \infty} which is either 0 if ||x||_{2, \infty} or \infty ''' + return 0.0 + + #tmp = [ el**2 for el in x.containers ] + #print(sum(tmp).sqrt().as_array().max()) + #return sum(tmp).sqrt().as_array().max() def proximal(self, x, tau, out=None): @@ -80,35 +75,24 @@ class MixedL21Norm(Function): def proximal_conjugate(self, x, tau, out=None): - if self.SymTensor: - - param = [1]*x.shape[0] - param[-1] = 2 - tmp = [param[i]*(x[i] ** 2) for i in range(x.shape[0])] - frac = [x[i]/(sum(tmp).sqrt()).maximum(1.0) for i in range(x.shape[0])] - res = BlockDataContainer(*frac) - - return res - - else: - if out is None: - tmp = [ el*el for el in x.containers] - res = sum(tmp).sqrt().maximum(1.0) - frac = [el/res for el in x.containers] - return BlockDataContainer(*frac) + if out is None: + tmp = [ el*el for el in x.containers] + res = sum(tmp).sqrt().maximum(1.0) + frac = [el/res for el in x.containers] + return BlockDataContainer(*frac) + - - #TODO this is slow, why??? + #TODO this is slow, why??? # return x.divide(x.pnorm().maximum(1.0)) - else: - - res1 = functools.reduce(lambda a,b: a + b*b, x.containers, x.get_item(0) * 0 ) - res = res1.sqrt().maximum(1.0) - x.divide(res, out=out) - + else: + + res1 = functools.reduce(lambda a,b: a + b*b, x.containers, x.get_item(0) * 0 ) + res = res1.sqrt().maximum(1.0) + x.divide(res, out=out) + # x.divide(sum([el*el for el in x.containers]).sqrt().maximum(1.0), out=out) - #TODO this is slow, why ??? + #TODO this is slow, why ??? # x.divide(x.pnorm().maximum(1.0), out=out) -- cgit v1.2.3 From 0ff28f2d9fa35d64bbc1079770c0d35fef17b4e5 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Sat, 20 Apr 2019 18:49:35 +0100 Subject: fix domain/range geometry for (N, M) BlockOperator --- .../ccpi/optimisation/operators/BlockOperator.py | 65 +++++++++++++++++++--- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/operators/BlockOperator.py b/Wrappers/Python/ccpi/optimisation/operators/BlockOperator.py index 1d77510..c8bd546 100755 --- a/Wrappers/Python/ccpi/optimisation/operators/BlockOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/BlockOperator.py @@ -266,15 +266,30 @@ class BlockOperator(Operator): # column BlockOperator return self.get_item(0,0).domain_geometry() else: - shape = (self.shape[0], 1) - return BlockGeometry(*[el.domain_geometry() for el in self.operators], - shape=shape) + # get the geometries column wise + # we need only the geometries from the first row + # since it is compatible from __init__ + tmp = [] + for i in range(self.shape[1]): + tmp.append(self.get_item(0,i).domain_geometry()) + return BlockGeometry(*tmp) + + #shape = (self.shape[0], 1) + #return BlockGeometry(*[el.domain_geometry() for el in self.operators], + # shape=self.shape) def range_geometry(self): '''returns the range of the BlockOperator''' - shape = (self.shape[1], 1) - return BlockGeometry(*[el.range_geometry() for el in self.operators], - shape=shape) + + tmp = [] + for i in range(self.shape[0]): + tmp.append(self.get_item(i,0).range_geometry()) + return BlockGeometry(*tmp) + + + #shape = (self.shape[1], 1) + #return BlockGeometry(*[el.range_geometry() for el in self.operators], + # shape=shape) def sum_abs_row(self): @@ -312,7 +327,8 @@ class BlockOperator(Operator): if __name__ == '__main__': from ccpi.framework import ImageGeometry - from ccpi.optimisation.operators import Gradient, Identity, SparseFiniteDiff + from ccpi.optimisation.operators import Gradient, Identity, \ + SparseFiniteDiff, SymmetrizedGradient, ZeroOperator M, N = 4, 3 @@ -363,4 +379,39 @@ if __name__ == '__main__': + ########################################################################### + # Block Operator for TGV reconstruction + + M, N = 2,3 + ig = ImageGeometry(M, N) + ag = ig + + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + + op22 = SymmetrizedGradient(op11.domain_geometry()) + + op21 = ZeroOperator(ig, op22.range_geometry()) + + + op31 = Identity(ig, ag) + op32 = ZeroOperator(op22.domain_geometry(), ag) + + operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + + z1 = operator.domain_geometry() + z2 = operator.range_geometry() + + print(z1.shape) + print(z2.shape) + + + + + + + + + + \ No newline at end of file -- cgit v1.2.3 From 9d5b84a8ca28cf87fe5d8772b277183f919a9bbc Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Sat, 20 Apr 2019 18:50:54 +0100 Subject: change ZeroOperator --- Wrappers/Python/ccpi/optimisation/operators/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/optimisation/operators/__init__.py b/Wrappers/Python/ccpi/optimisation/operators/__init__.py index 811adf6..23222d4 100755 --- a/Wrappers/Python/ccpi/optimisation/operators/__init__.py +++ b/Wrappers/Python/ccpi/optimisation/operators/__init__.py @@ -18,6 +18,6 @@ from .FiniteDifferenceOperator import FiniteDiff from .GradientOperator import Gradient from .SymmetrizedGradientOperator import SymmetrizedGradient from .IdentityOperator import Identity -from .ZeroOperator import ZeroOp +from .ZeroOperator import ZeroOperator from .LinearOperatorMatrix import LinearOperatorMatrix -- cgit v1.2.3 From ac8fd7c768aef99003b1da8618edee9949411573 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 23 Apr 2019 09:34:31 +0100 Subject: test add dot method --- Wrappers/Python/ccpi/framework/BlockDataContainer.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/BlockDataContainer.py b/Wrappers/Python/ccpi/framework/BlockDataContainer.py index 386cd87..823a1bd 100755 --- a/Wrappers/Python/ccpi/framework/BlockDataContainer.py +++ b/Wrappers/Python/ccpi/framework/BlockDataContainer.py @@ -270,6 +270,7 @@ class BlockDataContainer(object): def squared_norm(self): y = numpy.asarray([el.squared_norm() for el in self.containers]) return y.sum() + def norm(self): return numpy.sqrt(self.squared_norm()) @@ -442,9 +443,12 @@ class BlockDataContainer(object): '''Inline truedivision''' return self.__idiv__(other) +# def dot(self, other): +# +# tmp = [ self.containers[i].dot(other.containers[i]) for i in range(self.shape[0])] +# return sum(tmp) - - + if __name__ == '__main__': @@ -456,6 +460,7 @@ if __name__ == '__main__': BG = BlockGeometry(ig, ig) U = BG.allocate('random_int') + V = BG.allocate('random_int') print ("test sum BDC " ) @@ -468,10 +473,10 @@ if __name__ == '__main__': z1 = sum(U**2).sqrt().as_array() numpy.testing.assert_array_equal(z, z1) - - z2 = U.pnorm(2) + zzz = U.dot(V) + -- cgit v1.2.3 From c789b7fcb83d5c693194b46fc7347503f34ac359 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 23 Apr 2019 09:34:57 +0100 Subject: allocate symmetric option --- Wrappers/Python/ccpi/framework/BlockGeometry.py | 46 ++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/framework/BlockGeometry.py b/Wrappers/Python/ccpi/framework/BlockGeometry.py index 5dd6750..7fc5cb8 100755 --- a/Wrappers/Python/ccpi/framework/BlockGeometry.py +++ b/Wrappers/Python/ccpi/framework/BlockGeometry.py @@ -31,7 +31,51 @@ class BlockGeometry(object): '''returns the Geometry in the BlockGeometry located at position index''' return self.geometries[index] - def allocate(self, value=0, dimension_labels=None): + def allocate(self, value=0, dimension_labels=None, **kwargs): + + symmetry = kwargs.get('symmetry',False) containers = [geom.allocate(value) for geom in self.geometries] + + if symmetry == True: + + # TODO works but needs better coding + + # for 2x2 + # [ ig11, ig12\ + # ig21, ig22] + # Row-wise Order + + if len(containers)==4: + containers[1] = containers[2] + + # for 3x3 + # [ ig11, ig12, ig13\ + # ig21, ig22, ig23\ + # ig31, ig32, ig33] + + elif len(containers)==9: + containers[1]=containers[3] + containers[2]=containers[6] + containers[5]=containers[7] + + # for 4x4 + # [ ig11, ig12, ig13, ig14\ + # ig21, ig22, ig23, ig24\ + # ig31, ig32, ig33, ig34 + # ig41, ig42, ig43, ig44] + + elif len(containers) == 16: + containers[1]=containers[4] + containers[2]=containers[8] + containers[3]=containers[12] + containers[6]=containers[9] + containers[7]=containers[10] + containers[11]=containers[15] + + + + return BlockDataContainer(*containers) + + -- cgit v1.2.3 From e81478d344f48a9eed344c70a356492257a8c9a8 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 23 Apr 2019 09:40:40 +0100 Subject: fix prox conj --- Wrappers/Python/ccpi/optimisation/functions/ZeroFunction.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/ZeroFunction.py b/Wrappers/Python/ccpi/optimisation/functions/ZeroFunction.py index cce519a..a019815 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/ZeroFunction.py +++ b/Wrappers/Python/ccpi/optimisation/functions/ZeroFunction.py @@ -39,14 +39,8 @@ class ZeroFunction(Function): indicator function for the set = {0} So 0 if x=0, or inf if x neq 0 ''' + return x.maximum(0).sum() - if x.shape[0]==1: - return x.maximum(0).sum() - else: - if isinstance(x, BlockDataContainer): - return x.get_item(0).maximum(0).sum() + x.get_item(1).maximum(0).sum() - else: - return x.maximum(0).sum() + x.maximum(0).sum() def proximal(self, x, tau, out=None): if out is None: -- cgit v1.2.3 From 6e5acc587ad08d1c6fe3de1c259f505e51b4c90f Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 23 Apr 2019 09:41:19 +0100 Subject: fix __init__ --- Wrappers/Python/ccpi/optimisation/operators/ZeroOperator.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/operators/ZeroOperator.py b/Wrappers/Python/ccpi/optimisation/operators/ZeroOperator.py index d5fd8ae..8168f0b 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/ZeroOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/ZeroOperator.py @@ -13,25 +13,26 @@ from ccpi.optimisation.operators import LinearOperator class ZeroOperator(LinearOperator): def __init__(self, gm_domain, gm_range=None): + + super(ZeroOperator, self).__init__() self.gm_domain = gm_domain self.gm_range = gm_range if self.gm_range is None: self.gm_range = self.gm_domain - - super(ZeroOperator, self).__init__() + def direct(self,x,out=None): if out is None: return self.gm_range.allocate() else: - return self.gm_range.allocate() + out.fill(self.gm_range.allocate()) def adjoint(self,x, out=None): if out is None: return self.gm_domain.allocate() else: - return self.gm_domain.allocate() + out.fill(self.gm_domain.allocate()) def norm(self): return 0 -- cgit v1.2.3 From 27774e3f370db3366dfaa0a4653df8b41349e62c Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 23 Apr 2019 09:41:53 +0100 Subject: tests for TV --- Wrappers/Python/wip/pdhg_TV_denoising.py | 126 +++++++++++------------ Wrappers/Python/wip/pdhg_tv_denoising_poisson.py | 2 +- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/Wrappers/Python/wip/pdhg_TV_denoising.py b/Wrappers/Python/wip/pdhg_TV_denoising.py index 24fe216..872d832 100755 --- a/Wrappers/Python/wip/pdhg_TV_denoising.py +++ b/Wrappers/Python/wip/pdhg_TV_denoising.py @@ -16,7 +16,7 @@ from ccpi.optimisation.algorithms import PDHG, PDHG_old from ccpi.optimisation.operators import BlockOperator, Identity, Gradient from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, FunctionOperatorComposition, BlockFunction, ScaledFunction + MixedL21Norm, BlockFunction from skimage.util import random_noise @@ -26,7 +26,7 @@ from timeit import default_timer as timer # Create phantom for TV Gaussian denoising -N = 500 +N = 100 data = np.zeros((N,N)) data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 @@ -48,7 +48,7 @@ plt.show() alpha = 0.5 #method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") -method = '0' +method = '1' if method == '0': @@ -138,66 +138,66 @@ print(" Max of abs difference is {}".format(diff)) #%% Check with CVX solution -#from ccpi.optimisation.operators import SparseFiniteDiff -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -# -#if cvx_not_installable: -# -# ##Construct problem -# u = Variable(ig.shape) -# -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# # Define Total Variation as a regulariser -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) -# fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) -# -# # choose solver -# if 'MOSEK' in installed_solvers(): -# solver = MOSEK -# else: -# solver = SCS -# -# obj = Minimize( regulariser + fidelity) -# prob = Problem(obj) -# result = prob.solve(verbose = True, solver = solver) -# -# diff_cvx = numpy.abs( res.as_array() - u.value ) -# -# # Show result -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(res.as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# -# plt.subplot(3,1,2) -# plt.imshow(u.value) -# plt.title('CVX solution') -# plt.colorbar() -# -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') -# plt.legend() -# -# -# -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(primal[-1])) +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( res.as_array() - u.value ) + + # Show result + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(res.as_array()) + plt.title('PDHG solution') + plt.colorbar() + + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + + + + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(primal[-1])) diff --git a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py index 2c7e145..70bb4cc 100644 --- a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py +++ b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py @@ -48,7 +48,7 @@ alpha = 2 #method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") -method = '1' +method = '0' if method == '0': # Create operators -- cgit v1.2.3 From ad31397888520977a21a8cd05578aba2f10c832d Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 23 Apr 2019 09:43:00 +0100 Subject: PDGAP for TV --- .../Python/ccpi/optimisation/algorithms/PDHG.py | 25 +++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py index 6360ac1..7631e29 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py @@ -13,6 +13,7 @@ import time from ccpi.optimisation.operators import BlockOperator from ccpi.framework import BlockDataContainer from ccpi.optimisation.functions import FunctionOperatorComposition +import matplotlib.pyplot as plt class PDHG(Algorithm): '''Primal Dual Hybrid Gradient''' @@ -104,7 +105,7 @@ class PDHG(Algorithm): def update_objective(self): p1 = self.f(self.operator.direct(self.x)) + self.g(self.x) - d1 = -(self.f.convex_conjugate(self.y) + self.g(-1*self.operator.adjoint(self.y))) + d1 = -(self.f.convex_conjugate(self.y) + self.g.convex_conjugate(-1*self.operator.adjoint(self.y))) self.loss.append([p1,d1,p1-d1]) @@ -152,7 +153,7 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): if not memopt: - y_tmp = y_old + sigma * operator.direct(xbar) + y_tmp = y_old + sigma * operator.direct(xbar) y = f.proximal_conjugate(y_tmp, sigma) x_tmp = x_old - tau*operator.adjoint(y) @@ -164,15 +165,15 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): x_old.fill(x) y_old.fill(y) - - + if i%10==0: -# + p1 = f(operator.direct(x)) + g(x) - d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) + d1 = - ( f.convex_conjugate(y) + g.convex_conjugate(-1*operator.adjoint(y)) ) primal.append(p1) dual.append(d1) - pdgap.append(p1-d1) + pdgap.append(p1-d1) + print(p1, d1, p1-d1) else: @@ -190,18 +191,18 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): x.subtract(x_old, out=xbar) xbar *= theta xbar += x - + x_old.fill(x) y_old.fill(y) if i%10==0: -# + p1 = f(operator.direct(x)) + g(x) - d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) + d1 = - ( f.convex_conjugate(y) + g.convex_conjugate(-1*operator.adjoint(y)) ) primal.append(p1) dual.append(d1) - pdgap.append(p1-d1) -# print(p1, d1, p1-d1) + pdgap.append(p1-d1) + print(p1, d1, p1-d1) t_end = time.time() -- cgit v1.2.3 From 336a22bca672b55fd9ca999f777dd53796d2e7ea Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 23 Apr 2019 09:43:21 +0100 Subject: add sym grad --- .../operators/SymmetrizedGradientOperator.py | 212 ++++++++++----------- 1 file changed, 105 insertions(+), 107 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py b/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py index 25af156..3c15fa1 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py @@ -14,6 +14,12 @@ from ccpi.optimisation.operators import FiniteDiff, SparseFiniteDiff class SymmetrizedGradient(Gradient): + ''' Symmetrized Gradient, denoted by E: V --> W + where V is the Range of the Gradient Operator + and W is the Range of the Symmetrized Gradient. + ''' + + def __init__(self, gm_domain, bnd_cond = 'Neumann', **kwargs): super(SymmetrizedGradient, self).__init__(gm_domain, bnd_cond, **kwargs) @@ -37,25 +43,7 @@ class SymmetrizedGradient(Gradient): self.order_ind = [0,2,1,3] else: self.order_ind = [0,3,6,1,4,7,2,5,8] - - -# if self.correlation=='Space': -# if self.channels>1: -# pass -# else: -## # 2D image ---> Dx v1, Dyv2, Dx -# tmp = self.gm_domain.geometries + (self.gm_domain.get_item(0),) -# self.gm_range = BlockGeometry(*tmp ) -# self.ind1 = range(self.gm_domain.get_item(0).length) -# self.ind2 = range(self.gm_domain.get_item(0).length-1, -1, -1) -## self.order = myorder = [0,1,2 3] -# -# elif self.correlation=='SpaceChannels': -# if self.channels>1: -# pass -# else: -# raise ValueError('No channels to correlate') - + def direct(self, x, out=None): @@ -71,10 +59,21 @@ class SymmetrizedGradient(Gradient): res = [0.5 * sum(x) for x in zip(tmp, tmp1)] - return BlockDataContainer(*res) - - + return BlockDataContainer(*res) + else: + + ind = 0 + for i in range(self.gm_domain.shape[0]): + for j in range(x.shape[0]): + self.FD.direction = i + self.FD.adjoint(x.get_item(j), out=out[ind]) + ind+=1 + out1 = [out[i] for i in self.order_ind] + res = [0.5 * sum(x) for x in zip(out, out1)] + out.fill(BlockDataContainer(*res)) + + def adjoint(self, x, out=None): if out is None: @@ -91,20 +90,20 @@ class SymmetrizedGradient(Gradient): tmp[k] = tmp1 return BlockDataContainer(*tmp) -# i = 0 -# -# tmp = self.gm_domain.allocate() -# for k in range(tmp.shape[0]): -# tmp1 = 0 -# for j in range(tmp.shape[0]): -# self.FD.direction = j -# tmp1 += self.FD.direct(x[i]) -# i+=1 -# tmp.get_item(k).fill(tmp1) -# return tmp - + else: - pass + + tmp = self.gm_domain.allocate() + i = 0 + for k in range(self.gm_domain.shape[0]): + tmp1 = 0 + for j in range(self.gm_domain.shape[0]): + self.FD.direction = j + self.FD.direct(x[i], out=tmp[j]) + i+=1 + tmp1+=tmp[j] + out[k].fill(tmp1) + def domain_geometry(self): @@ -117,6 +116,7 @@ class SymmetrizedGradient(Gradient): #TODO need dot method for BlockDataContainer return numpy.sqrt(4*self.gm_domain.shape[0]) + # x0 = self.gm_domain.allocate('random_int') # self.s1, sall, svec = LinearOperator.PowerMethod(self, 10, x0) # return self.s1 @@ -126,114 +126,112 @@ class SymmetrizedGradient(Gradient): if __name__ == '__main__': ########################################################################### - ## Symmetrized Gradient + ## Symmetrized Gradient Tests from ccpi.framework import DataContainer from ccpi.optimisation.operators import Gradient, BlockOperator, FiniteDiff import numpy as np N, M = 2, 3 K = 2 + C = 2 ig1 = ImageGeometry(N, M) - ig2 = ImageGeometry(N, M, K) + ig2 = ImageGeometry(N, M, channels=C) E1 = SymmetrizedGradient(ig1, correlation = 'Space', bnd_cond='Neumann') - E2 = SymmetrizedGradient(ig2, correlation = 'Space', bnd_cond='Periodic') - print(E1.domain_geometry().shape, E1.range_geometry().shape) - print(E2.domain_geometry().shape, E2.range_geometry().shape) - - u1 = E1.gm_domain.allocate('random_int') - a1 = ig1.allocate('random_int') - a2 = ig1.allocate('random_int') - a3 = ig1.allocate('random_int') - w1 = BlockDataContainer(*[a1, a2, a3]) - w11 = BlockDataContainer(*[a1, a2, a2, a3]) + try: + E1 = SymmetrizedGradient(ig1, correlation = 'SpaceChannels', bnd_cond='Neumann') + except: + print("No Channels to correlate") - sym_direct = [None]*3 - - sym_direct[0] = FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').adjoint(u1[0]) - sym_direct[1] = FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').adjoint(u1[1]) - sym_direct[2] = 0.5 * (FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').adjoint(u1[0]) + \ - FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').adjoint(u1[1])) - - sym_direct1 = [None]*4 - - sym_direct1[0] = FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').adjoint(u1[0]) + E2 = SymmetrizedGradient(ig2, correlation = 'SpaceChannels', bnd_cond='Neumann') + + print(E1.domain_geometry().shape, E1.range_geometry().shape) + print(E2.domain_geometry().shape, E2.range_geometry().shape) - sym_direct1[1] = 0.5 * (FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').adjoint(u1[1]) + \ - FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').adjoint(u1[0])) + #check Linear operator property - sym_direct1[2] = 0.5 * (FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').adjoint(u1[1]) + \ - FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').adjoint(u1[0])) - - sym_direct1[3] = FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').adjoint(u1[1]) + u1 = E1.domain_geometry().allocate('random_int') + u2 = E2.domain_geometry().allocate('random_int') - - - sym_adjoint = [None]*2 + # Need to allocate random_int at the Range of SymGradient - sym_adjoint[0] = FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').direct(w1[0]) + \ - FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').direct(w1[2]) - sym_adjoint[1] = FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').direct(w1[2]) + \ - FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').direct(w1[1]) - - sym_adjoint1 = [None]*2 + #a1 = ig1.allocate('random_int') + #a2 = ig1.allocate('random_int') + #a3 = ig1.allocate('random_int') - sym_adjoint1[0] = FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').direct(w11[0]) + \ - FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').direct(w11[1]) - sym_adjoint1[1] = FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann').direct(w11[2]) + \ - FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann').direct(w11[3]) - + #a4 = ig1.allocate('random_int') + #a5 = ig1.allocate('random_int') + #a6 = ig1.allocate('random_int') - LHS = (sym_direct[0] * w1[0] + \ - sym_direct[1] * w1[1] + \ - 2*sym_direct[2] * w1[2]).sum() + # TODO allocate has to create this symmetry by default!!!!! + #w1 = BlockDataContainer(*[a1, a2, \ + # a2, a3]) + w1 = E1.range_geometry().allocate('random_int',symmetry=True) - RHS = (u1[0]*sym_adjoint[0] + u1[1]*sym_adjoint[1]).sum() + LHS = (E1.direct(u1) * w1).sum() + RHS = (u1 * E1.adjoint(w1)).sum() - print(LHS, RHS) + numpy.testing.assert_equal(LHS, RHS) - LHS = (sym_direct1[0] * w11[0] + \ - sym_direct1[1] * w11[1] + \ - sym_direct1[2] * w11[2] + \ - sym_direct1[3] * w11[3] ).sum() + u2 = E2.gm_domain.allocate('random_int') - RHS = (u1[0]*sym_adjoint1[0] + u1[1]*sym_adjoint1[1]).sum() + #aa1 = ig2.allocate('random_int') + #aa2 = ig2.allocate('random_int') + #aa3 = ig2.allocate('random_int') + #aa4 = ig2.allocate('random_int') + #aa5 = ig2.allocate('random_int') + #aa6 = ig2.allocate('random_int') - print(LHS, RHS) + #w2 = BlockDataContainer(*[aa1, aa2, aa3, \ + # aa2, aa4, aa5, \ + # aa3, aa5, aa6]) + w2 = E2.range_geometry().allocate('random_int',symmetry=True) + + LHS1 = (E2.direct(u2) * w2).sum() + RHS1 = (u2 * E2.adjoint(w2)).sum() + numpy.testing.assert_equal(LHS1, RHS1) - a1 = (E1.direct(u1) * w11).sum() - b1 = (u1 * E1.adjoint(w11)).sum() - print(a1, b1) + out = E1.range_geometry().allocate() + E1.direct(u1, out=out) + a1 = E1.direct(u1) + numpy.testing.assert_array_equal(a1[0].as_array(), out[0].as_array()) + numpy.testing.assert_array_equal(a1[1].as_array(), out[1].as_array()) + numpy.testing.assert_array_equal(a1[2].as_array(), out[2].as_array()) + numpy.testing.assert_array_equal(a1[3].as_array(), out[3].as_array()) - u2 = E2.gm_domain.allocate('random_int') + out1 = E1.domain_geometry().allocate() + E1.adjoint(w1, out=out1) + b1 = E1.adjoint(w1) - aa1 = ig2.allocate('random_int') - aa2 = ig2.allocate('random_int') - aa3 = ig2.allocate('random_int') - aa4 = ig2.allocate('random_int') - aa5 = ig2.allocate('random_int') - aa6 = ig2.allocate('random_int') + LHS_out = (out * w1).sum() + RHS_out = (u1 * out1).sum() + print(LHS_out, RHS_out) - w2 = BlockDataContainer(*[aa1, aa2, aa3, \ - aa2, aa4, aa5, \ - aa3, aa5, aa6]) - tmp1 = E2.direct(u2) - tmp2 = E2.adjoint(w2) - c1 = (tmp1 * w2).sum() - d1 = (u2 * tmp2).sum() + out2 = E2.range_geometry().allocate() + E2.direct(u2, out=out2) + a2 = E2.direct(u2) - print(c1, d1) + out21 = E2.domain_geometry().allocate() + E2.adjoint(w2, out=out21) + b2 = E2.adjoint(w2) + LHS_out = (out2 * w2).sum() + RHS_out = (u2 * out21).sum() + print(LHS_out, RHS_out) - \ No newline at end of file + +# +# +# +# \ No newline at end of file -- cgit v1.2.3 From cbdd0dabf0bfd98e957d31465d48d16f1ae2b14b Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 23 Apr 2019 09:43:53 +0100 Subject: add TGV test --- Wrappers/Python/wip/pdhg_TGV_denoising.py | 211 ++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 Wrappers/Python/wip/pdhg_TGV_denoising.py diff --git a/Wrappers/Python/wip/pdhg_TGV_denoising.py b/Wrappers/Python/wip/pdhg_TGV_denoising.py new file mode 100644 index 0000000..54b7131 --- /dev/null +++ b/Wrappers/Python/wip/pdhg_TGV_denoising.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Feb 22 14:53:03 2019 + +@author: evangelos +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, PDHG_old + +from ccpi.optimisation.operators import BlockOperator, Identity, \ + Gradient, SymmetrizedGradient, ZeroOperator +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +from timeit import default_timer as timer +#def dt(steps): +# return steps[-1] - steps[-2] + +# Create phantom for TGV Gaussian denoising + +N = 100 + +data = np.zeros((N,N)) + +x1 = np.linspace(0, int(N/2), N) +x2 = np.linspace(int(N/2), 0., N) +xv, yv = np.meshgrid(x1, x2) + +xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T + +data = xv +data = data/data.max() + +plt.imshow(data) +plt.show() + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Add Gaussian noise +n1 = random_noise(data, mode = 'gaussian', mean=0, var = 0.005, seed=10) +noisy_data = ImageData(n1) + + +plt.imshow(noisy_data.as_array()) +plt.title('Noisy data') +plt.show() + +alpha, beta = 0.2, 1 + +#method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") +method = '0' + +if method == '0': + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + + op22 = SymmetrizedGradient(op11.domain_geometry()) + + op21 = ZeroOperator(ig, op22.range_geometry()) + + + op31 = Identity(ig, ag) + op32 = ZeroOperator(op22.domain_geometry(), ag) + + operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + f3 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2, f3) + g = ZeroFunction() + + +else: + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + operator = BlockOperator(op11, -1*op12, \ + op21, op22, \ + shape=(2,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + f = BlockFunction(f1, f2) + + g = 0.5 * L2NormSquared(b = noisy_data) + + + + +## Compute operator Norm +normK = operator.norm() +# +## Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +## +opt = {'niter':2000} +opt1 = {'niter':2000, 'memopt': True} +# +t1 = timer() +res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) +t2 = timer() +# +plt.imshow(res[0].as_array()) +plt.show() + + +#t3 = timer() +#res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) +#t4 = timer() +# +#plt.figure(figsize=(15,15)) +#plt.subplot(3,1,1) +#plt.imshow(res[0].as_array()) +#plt.title('no memopt') +#plt.colorbar() +#plt.subplot(3,1,2) +#plt.imshow(res1[0].as_array()) +#plt.title('memopt') +#plt.colorbar() +#plt.subplot(3,1,3) +#plt.imshow((res1[0] - res[0]).abs().as_array()) +#plt.title('diff') +#plt.colorbar() +#plt.show() +# +#print("NoMemopt/Memopt is {}/{}".format(t2-t1, t4-t3)) + + +#%% Check with CVX solution + +#from ccpi.optimisation.operators import SparseFiniteDiff +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +#if cvx_not_installable: +# +# u = Variable(ig.shape) +# w1 = Variable((N, N)) +# w2 = Variable((N, N)) +# +# # create TGV regulariser +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ +# DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ +# beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ +# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ +# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) +# +# constraints = [] +# fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) +# solver = MOSEK +# +# obj = Minimize( regulariser + fidelity) +# prob = Problem(obj) +# result = prob.solve(verbose = True, solver = solver) +# +# diff_cvx = numpy.abs( res[0].as_array() - u.value ) +# +# # Show result +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(res[0].as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# +# plt.subplot(3,1,2) +# plt.imshow(u.value) +# plt.title('CVX solution') +# plt.colorbar() +# +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() +# +# plt.plot(np.linspace(0,N,N), res[0].as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') +# plt.legend() +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(primal[-1])) +# print('Min/Max of absolute difference {}/{}'.format(diff_cvx.min(), diff_cvx.max())) + + + -- cgit v1.2.3 From cde94fb76d7d3d25801b68663b3a6dc1a066f986 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 23 Apr 2019 09:44:23 +0100 Subject: add symGrad test --- Wrappers/Python/wip/test_symGrad_method1.py | 178 ++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 Wrappers/Python/wip/test_symGrad_method1.py diff --git a/Wrappers/Python/wip/test_symGrad_method1.py b/Wrappers/Python/wip/test_symGrad_method1.py new file mode 100644 index 0000000..36adee1 --- /dev/null +++ b/Wrappers/Python/wip/test_symGrad_method1.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Feb 22 14:53:03 2019 + +@author: evangelos +""" + +from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, PDHG_old + +from ccpi.optimisation.operators import BlockOperator, Identity, \ + Gradient, SymmetrizedGradient, ZeroOperator +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +from timeit import default_timer as timer +#def dt(steps): +# return steps[-1] - steps[-2] + +# Create phantom for TGV Gaussian denoising + +N = 3 + +data = np.zeros((N,N)) + +x1 = np.linspace(0, int(N/2), N) +x2 = np.linspace(int(N/2), 0., N) +xv, yv = np.meshgrid(x1, x2) + +xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T + +data = xv +data = data/data.max() + +plt.imshow(data) +plt.show() + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Add Gaussian noise +n1 = random_noise(data, mode = 'gaussian', mean=0, var = 0.005, seed=10) +noisy_data = ImageData(n1) + + +plt.imshow(noisy_data.as_array()) +plt.title('Noisy data') +plt.show() + +alpha, beta = 0.2, 1 + +#method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") +method = '1' + + +# Create operators +op11 = Gradient(ig) +op12 = Identity(op11.range_geometry()) + +op22 = SymmetrizedGradient(op11.domain_geometry()) + +op21 = ZeroOperator(ig, op22.range_geometry()) + + +op31 = Identity(ig, ag) +op32 = ZeroOperator(op22.domain_geometry(), ag) + +operator1 = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + + +f1 = alpha * MixedL21Norm() +f2 = beta * MixedL21Norm() +f3 = ZeroFunction() +f_B3 = BlockFunction(f1, f2, f3) +g_B3 = ZeroFunction() + + + +# Create operators +op11 = Gradient(ig) +op12 = Identity(op11.range_geometry()) + +op22 = SymmetrizedGradient(op11.domain_geometry()) + +op21 = ZeroOperator(ig, op22.range_geometry()) + +operator2 = BlockOperator(op11, -1*op12, \ + op21, op22, \ + shape=(2,2) ) + +#f1 = alpha * MixedL21Norm() +#f2 = beta * MixedL21Norm() +f_B2 = BlockFunction(f1, f2) +g_B2 = 0.5 * L2NormSquared(b = noisy_data) + + +#%% + +x_old1 = operator1.domain_geometry().allocate('random_int') +y_old1 = operator1.range_geometry().allocate() + +xbar1 = x_old1.copy() +x_tmp1 = x_old1.copy() +x1 = x_old1.copy() + +y_tmp1 = y_old1.copy() +y1 = y_tmp1.copy() + +x_old2 = x_old1.copy() +y_old2 = operator2.range_geometry().allocate() + +xbar2 = x_old2.copy() +x_tmp2 = x_old2.copy() +x2 = x_old2.copy() + +y_tmp2 = y_old2.copy() +y2 = y_tmp2.copy() + +sigma = 0.4 +tau = 0.4 + +y_tmp1 = y_old1 + sigma * operator1.direct(xbar1) +y_tmp2 = y_old2 + sigma * operator2.direct(xbar2) + +numpy.testing.assert_array_equal(y_tmp1[0][0].as_array(), y_tmp2[0][0].as_array()) +numpy.testing.assert_array_equal(y_tmp1[0][1].as_array(), y_tmp2[0][1].as_array()) +numpy.testing.assert_array_equal(y_tmp1[1][0].as_array(), y_tmp2[1][0].as_array()) +numpy.testing.assert_array_equal(y_tmp1[1][1].as_array(), y_tmp2[1][1].as_array()) + + +y1 = f_B3.proximal_conjugate(y_tmp1, sigma) +y2 = f_B2.proximal_conjugate(y_tmp2, sigma) + +numpy.testing.assert_array_equal(y1[0][0].as_array(), y2[0][0].as_array()) +numpy.testing.assert_array_equal(y1[0][1].as_array(), y2[0][1].as_array()) +numpy.testing.assert_array_equal(y1[1][0].as_array(), y2[1][0].as_array()) +numpy.testing.assert_array_equal(y1[1][1].as_array(), y2[1][1].as_array()) + + +x_tmp1 = x_old1 - tau * operator1.adjoint(y1) +x_tmp2 = x_old2 - tau * operator2.adjoint(y2) + +numpy.testing.assert_array_equal(x_tmp1[0].as_array(), x_tmp2[0].as_array()) + + + + + + + + + + + +############################################################################## +#x_1 = operator1.domain_geometry().allocate('random_int') +# +#x_2 = BlockDataContainer(x_1[0], x_1[1]) +# +#res1 = operator1.direct(x_1) +#res2 = operator2.direct(x_2) +# +#print(res1[0][0].as_array(), res2[0][0].as_array()) +#print(res1[0][1].as_array(), res2[0][1].as_array()) +# +#print(res1[1][0].as_array(), res2[1][0].as_array()) +#print(res1[1][1].as_array(), res2[1][1].as_array()) +# +##res1 = op11.direct(x1[0]) - op12.direct(x1[1]) +##res2 = op21.direct(x1[0]) - op22.direct(x1[1]) -- cgit v1.2.3 From 1b377a4b2588cc83dce64a8988eeef862946c9eb Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 23 Apr 2019 10:31:14 +0100 Subject: fix second case for TGV --- Wrappers/Python/wip/pdhg_TGV_denoising.py | 141 ++++++++++++++---------------- 1 file changed, 67 insertions(+), 74 deletions(-) diff --git a/Wrappers/Python/wip/pdhg_TGV_denoising.py b/Wrappers/Python/wip/pdhg_TGV_denoising.py index 54b7131..0b6e594 100644 --- a/Wrappers/Python/wip/pdhg_TGV_denoising.py +++ b/Wrappers/Python/wip/pdhg_TGV_denoising.py @@ -55,10 +55,10 @@ plt.imshow(noisy_data.as_array()) plt.title('Noisy data') plt.show() -alpha, beta = 0.2, 1 +alpha, beta = 0.1, 0.5 #method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") -method = '0' +method = '1' if method == '0': @@ -66,11 +66,9 @@ if method == '0': op11 = Gradient(ig) op12 = Identity(op11.range_geometry()) - op22 = SymmetrizedGradient(op11.domain_geometry()) - + op22 = SymmetrizedGradient(op11.domain_geometry()) op21 = ZeroOperator(ig, op22.range_geometry()) - - + op31 = Identity(ig, ag) op32 = ZeroOperator(op22.domain_geometry(), ag) @@ -90,21 +88,16 @@ else: op11 = Gradient(ig) op12 = Identity(op11.range_geometry()) op22 = SymmetrizedGradient(op11.domain_geometry()) - op21 = ZeroOperator(ig, op22.range_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) - operator = BlockOperator(op11, -1*op12, \ - op21, op22, \ - shape=(2,2) ) + operator = BlockOperator(op11, -1*op12, op21, op22, shape=(2,2) ) f1 = alpha * MixedL21Norm() f2 = beta * MixedL21Norm() - f = BlockFunction(f1, f2) - g = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + g = BlockFunction(0.5 * L2NormSquared(b = noisy_data), ZeroFunction()) - - - ## Compute operator Norm normK = operator.norm() # @@ -147,65 +140,65 @@ plt.show() #%% Check with CVX solution -#from ccpi.optimisation.operators import SparseFiniteDiff -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -#if cvx_not_installable: -# -# u = Variable(ig.shape) -# w1 = Variable((N, N)) -# w2 = Variable((N, N)) -# -# # create TGV regulariser -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ -# DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ -# beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ -# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ -# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) -# -# constraints = [] -# fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) -# solver = MOSEK -# -# obj = Minimize( regulariser + fidelity) -# prob = Problem(obj) -# result = prob.solve(verbose = True, solver = solver) -# -# diff_cvx = numpy.abs( res[0].as_array() - u.value ) -# -# # Show result -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(res[0].as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# -# plt.subplot(3,1,2) -# plt.imshow(u.value) -# plt.title('CVX solution') -# plt.colorbar() -# -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), res[0].as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') -# plt.legend() -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(primal[-1])) -# print('Min/Max of absolute difference {}/{}'.format(diff_cvx.min(), diff_cvx.max())) +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + +if cvx_not_installable: + + u = Variable(ig.shape) + w1 = Variable((N, N)) + w2 = Variable((N, N)) + + # create TGV regulariser + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ + DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ + beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) + + constraints = [] + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + solver = MOSEK + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( res[0].as_array() - u.value ) + + # Show result + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(res[0].as_array()) + plt.title('PDHG solution') + plt.colorbar() + + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), res[0].as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(primal[-1])) + print('Min/Max of absolute difference {}/{}'.format(diff_cvx.min(), diff_cvx.max())) -- cgit v1.2.3 From 9734788e8bc5088040ca8958db3ce9811a845758 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 23 Apr 2019 12:36:30 +0100 Subject: pass current solution to callback, fix PDHG setup --- Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py | 2 +- Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py index ed95c3f..7604c8a 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py @@ -151,7 +151,7 @@ class Algorithm(object): self.max_iteration, self.get_last_objective()) ) else: if callback is not None: - callback(self.iteration, self.get_last_objective()) + callback(self.iteration, self.get_last_objective(), self.x) i += 1 if i == iterations: break diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py index bc080f8..bc3059b 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py @@ -37,7 +37,12 @@ class PDHG(Algorithm): def set_up(self, f, g, operator, tau = None, sigma = None, opt = None, **kwargs): # algorithmic parameters - + self.operator = operator + self.f = f + self.g = g + self.tau = tau + self.sigma = sigma + self.opt = opt if sigma is None and tau is None: raise ValueError('Need sigma*tau||K||^2<1') -- cgit v1.2.3 From 541bbba333d0fd36e0a7a050879d40e6bd279306 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 23 Apr 2019 12:38:45 +0100 Subject: add tomography type of example with ccpi projector --- Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py | 273 ++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py diff --git a/Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py b/Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py new file mode 100644 index 0000000..ea1ad08 --- /dev/null +++ b/Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- + +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Feb 22 14:53:03 2019 + +@author: evangelos +""" + +from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer, \ + AcquisitionGeometry, AcquisitionData + +import numpy as np +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, PDHG_old + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction, ScaledFunction + +#from ccpi.astra.ops import AstraProjectorSimple +from ccpi.plugins.ops import CCPiProjectorSimple +#from skimage.util import random_noise +from timeit import default_timer as timer + +#%% + +#%%############################################################################### +# Create phantom for TV tomography + +#import os +#import tomophantom +#from tomophantom import TomoP2D +#from tomophantom.supp.qualitymetrics import QualityTools + +#model = 1 # select a model number from the library +#N = 150 # set dimension of the phantom +## one can specify an exact path to the parameters file +## path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' +#path = os.path.dirname(tomophantom.__file__) +#path_library2D = os.path.join(path, "Phantom2DLibrary.dat") +##This will generate a N_size x N_size phantom (2D) +#phantom_2D = TomoP2D.Model(model, N, path_library2D) +#ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +#data = ImageData(phantom_2D, geometry=ig) + +N = 150 +#x = np.zeros((N,N)) + +vert = 4 +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, voxel_num_z=vert) +data = ig.allocate() +Phantom = data +# Populate image data by looping over and filling slices +i = 0 +while i < vert: + if vert > 1: + x = Phantom.subset(vertical=i).array + else: + x = Phantom.array + x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 + x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 0.98 + if vert > 1 : + Phantom.fill(x, vertical=i) + i += 1 + + +#%% +#detectors = N +#angles = np.linspace(0,np.pi,100) +angles_num = 100 +det_w = 1.0 +det_num = N + +angles = np.linspace(0,np.pi,angles_num,endpoint=False,dtype=np.float32)*\ + 180/np.pi + +# Inputs: Geometry, 2D or 3D, angles, horz detector pixel count, +# horz detector pixel size, vert detector pixel count, +# vert detector pixel size. +ag = AcquisitionGeometry('parallel', + '3D', + angles, + N, + det_w, + vert, + det_w) + +from ccpi.reconstruction.parallelbeam import alg as pbalg +#from ccpi.plugins.processors import setupCCPiGeometries +def setupCCPiGeometries(ig, ag, counter): + + #vg = ImageGeometry(voxel_num_x=voxel_num_x,voxel_num_y=voxel_num_y, voxel_num_z=voxel_num_z) + #Phantom_ccpi = ImageData(geometry=vg, + # dimension_labels=['horizontal_x','horizontal_y','vertical']) + ##.subset(['horizontal_x','horizontal_y','vertical']) + ## ask the ccpi code what dimensions it would like + Phantom_ccpi = ig.allocate(dimension_labels=[ImageGeometry.HORIZONTAL_X, + ImageGeometry.HORIZONTAL_Y, + ImageGeometry.VERTICAL]) + + voxel_per_pixel = 1 + angles = ag.angles + geoms = pbalg.pb_setup_geometry_from_image(Phantom_ccpi.as_array(), + angles, + voxel_per_pixel ) + + pg = AcquisitionGeometry('parallel', + '3D', + angles, + geoms['n_h'], 1.0, + geoms['n_v'], 1.0 #2D in 3D is a slice 1 pixel thick + ) + + center_of_rotation = Phantom_ccpi.get_dimension_size('horizontal_x') / 2 + #ad = AcquisitionData(geometry=pg,dimension_labels=['angle','vertical','horizontal']) + ad = pg.allocate(dimension_labels=[AcquisitionGeometry.ANGLE, + AcquisitionGeometry.VERTICAL, + AcquisitionGeometry.HORIZONTAL]) + geoms_i = pbalg.pb_setup_geometry_from_acquisition(ad.as_array(), + angles, + center_of_rotation, + voxel_per_pixel ) + + counter+=1 + + if counter < 4: + print (geoms, geoms_i) + if (not ( geoms_i == geoms )): + print ("not equal and {} {} {}".format(counter, geoms['output_volume_z'], geoms_i['output_volume_z'])) + X = max(geoms['output_volume_x'], geoms_i['output_volume_x']) + Y = max(geoms['output_volume_y'], geoms_i['output_volume_y']) + Z = max(geoms['output_volume_z'], geoms_i['output_volume_z']) + return setupCCPiGeometries(X,Y,Z,angles, counter) + else: + print ("happy now {} {} {}".format(counter, geoms['output_volume_z'], geoms_i['output_volume_z'])) + + return geoms + else: + return geoms_i + + + +#voxel_num_x, voxel_num_y, voxel_num_z, angles, counter +print ("###############################################") +print (ig) +print (ag) +g = setupCCPiGeometries(ig, ag, 0) +print (g) +print ("###############################################") +print ("###############################################") +#ag = AcquisitionGeometry('parallel','2D',angles, detectors) +#Aop = AstraProjectorSimple(ig, ag, 'cpu') +Aop = CCPiProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +plt.imshow(sin.subset(vertical=0).as_array()) +plt.title('Sinogram') +plt.colorbar() +plt.show() + + +#%% +# Add Gaussian noise to the sinogram data +np.random.seed(10) +n1 = np.random.random(sin.shape) + +noisy_data = sin + ImageData(5*n1) + +plt.imshow(noisy_data.subset(vertical=0).as_array()) +plt.title('Noisy Sinogram') +plt.colorbar() +plt.show() + + +#%% Works only with Composite Operator Structure of PDHG + +#ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Form Composite Operator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +alpha = 50 +f = BlockFunction( alpha * MixedL21Norm(), \ + 0.5 * L2NormSquared(b = noisy_data) ) +g = ZeroFunction() + +normK = Aop.norm() + +# Compute operator Norm +normK = operator.norm() + +## Primal & dual stepsizes +diag_precon = False + +if diag_precon: + + def tau_sigma_precond(operator): + + tau = 1/operator.sum_abs_row() + sigma = 1/ operator.sum_abs_col() + + return tau, sigma + + tau, sigma = tau_sigma_precond(operator) + +else: + sigma = 1 + tau = 1/(sigma*normK**2) + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + +opt = {'niter':2000} +opt1 = {'niter':2000, 'memopt': True} + + +pdhg1 = PDHG(f=f,g=g, operator=operator, tau=tau, sigma=sigma) +pdhg1.max_iteration = 2000 +pdhg1.update_objective_interval = 200 +pdhg2 = PDHG(f=f,g=g, operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg2.max_iteration = 2000 +pdhg2.update_objective_interval = 200 +t1 = timer() +#res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) +pdhg1.run(200) +res = pdhg1.get_output().subset(vertical=0) +t2 = timer() + +t3 = timer() +#res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) +pdhg2.run(200) +res1 = pdhg2.get_output().subset(vertical=0) +t4 = timer() +# +print ("No memopt in {}s, memopt in {}s ".format(t2-t1, t4 -t3)) + +#%% +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(res.as_array()) +plt.title('no memopt') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(res1.as_array()) +plt.title('memopt') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow((res1 - res).abs().as_array()) +plt.title('diff') +plt.colorbar() +plt.show() +# +plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'memopt') +plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'no memopt') +plt.legend() +plt.show() +# +print ("Time: No memopt in {}s, \n Time: Memopt in {}s ".format(t2-t1, t4 -t3)) +diff = (res1 - res).abs().as_array().max() +# +print(" Max of abs difference is {}".format(diff)) + -- cgit v1.2.3 From f12e27ba088462858f35315193cf6bf624325d1e Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 24 Apr 2019 10:49:06 +0100 Subject: fix memopt proximal --- .../ccpi/optimisation/functions/BlockFunction.py | 28 +++++++++++++++------- Wrappers/Python/wip/pdhg_TV_denoising.py | 2 +- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py b/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py index bf627a5..0836324 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py +++ b/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py @@ -93,16 +93,28 @@ class BlockFunction(Function): ''' + + if out is None: - out = [None]*self.length - if isinstance(tau, Number): - for i in range(self.length): - out[i] = self.functions[i].proximal(x.get_item(i), tau) + out = [None]*self.length + if isinstance(tau, Number): + for i in range(self.length): + out[i] = self.functions[i].proximal(x.get_item(i), tau) + else: + for i in range(self.length): + out[i] = self.functions[i].proximal(x.get_item(i), tau.get_item(i)) + + return BlockDataContainer(*out) + else: - for i in range(self.length): - out[i] = self.functions[i].proximal(x.get_item(i), tau.get_item(i)) - - return BlockDataContainer(*out) + if isinstance(tau, Number): + for i in range(self.length): + self.functions[i].proximal(x.get_item(i), tau, out[i]) + else: + for i in range(self.length): + self.functions[i].proximal(x.get_item(i), tau.get_item(i), out[i]) + + def gradient(self,x, out=None): diff --git a/Wrappers/Python/wip/pdhg_TV_denoising.py b/Wrappers/Python/wip/pdhg_TV_denoising.py index 872d832..a8e25d2 100755 --- a/Wrappers/Python/wip/pdhg_TV_denoising.py +++ b/Wrappers/Python/wip/pdhg_TV_denoising.py @@ -6,7 +6,7 @@ Created on Fri Feb 22 14:53:03 2019 @author: evangelos """ -from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer +from ccpi.framework import ImageData, ImageGeometry import numpy as np import numpy -- cgit v1.2.3 From b0a2d779602df9f8abfcb68083f9de0df43ed5b0 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 24 Apr 2019 10:49:41 +0100 Subject: fix TGV example --- .../Python/ccpi/optimisation/algorithms/PDHG.py | 7 +- .../operators/SymmetrizedGradientOperator.py | 29 ++-- Wrappers/Python/wip/pdhg_TGV_denoising.py | 184 ++++++++++++--------- Wrappers/Python/wip/pdhg_TV_tomography2D.py | 6 +- 4 files changed, 130 insertions(+), 96 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py index 7631e29..110f998 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py @@ -104,6 +104,7 @@ class PDHG(Algorithm): self.y_old = self.y def update_objective(self): + p1 = self.f(self.operator.direct(self.x)) + self.g(self.x) d1 = -(self.f.convex_conjugate(self.y) + self.g.convex_conjugate(-1*self.operator.adjoint(self.y))) @@ -166,7 +167,7 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): x_old.fill(x) y_old.fill(y) - if i%10==0: + if i%50==0: p1 = f(operator.direct(x)) + g(x) d1 = - ( f.convex_conjugate(y) + g.convex_conjugate(-1*operator.adjoint(y)) ) @@ -194,8 +195,8 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): x_old.fill(x) y_old.fill(y) - - if i%10==0: + + if i%50==0: p1 = f(operator.direct(x)) + g(x) d1 = - ( f.convex_conjugate(y) + g.convex_conjugate(-1*operator.adjoint(y)) ) diff --git a/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py b/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py index 3c15fa1..243809b 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py @@ -69,9 +69,8 @@ class SymmetrizedGradient(Gradient): self.FD.direction = i self.FD.adjoint(x.get_item(j), out=out[ind]) ind+=1 - out1 = [out[i] for i in self.order_ind] - res = [0.5 * sum(x) for x in zip(out, out1)] - out.fill(BlockDataContainer(*res)) + out1 = BlockDataContainer(*[out[i] for i in self.order_ind]) + out.fill( 0.5 * (out + out1) ) def adjoint(self, x, out=None): @@ -102,10 +101,11 @@ class SymmetrizedGradient(Gradient): self.FD.direct(x[i], out=tmp[j]) i+=1 tmp1+=tmp[j] - out[k].fill(tmp1) + out[k].fill(tmp1) +# tmp = self.adjoint(x) +# out.fill(tmp) - - + def domain_geometry(self): return self.gm_domain @@ -114,12 +114,12 @@ class SymmetrizedGradient(Gradient): def norm(self): - #TODO need dot method for BlockDataContainer - return numpy.sqrt(4*self.gm_domain.shape[0]) +# TODO need dot method for BlockDataContainer +# return numpy.sqrt(4*self.gm_domain.shape[0]) -# x0 = self.gm_domain.allocate('random_int') -# self.s1, sall, svec = LinearOperator.PowerMethod(self, 10, x0) -# return self.s1 +# x0 = self.gm_domain.allocate('random') + self.s1, sall, svec = LinearOperator.PowerMethod(self, 50) + return self.s1 @@ -226,6 +226,13 @@ if __name__ == '__main__': print(LHS_out, RHS_out) + out = E1.range_geometry().allocate() + E1.direct(u1, out=out) + E1.adjoint(out, out=out1) + + print(E1.norm()) + print(E2.norm()) + diff --git a/Wrappers/Python/wip/pdhg_TGV_denoising.py b/Wrappers/Python/wip/pdhg_TGV_denoising.py index 0b6e594..1c570cb 100644 --- a/Wrappers/Python/wip/pdhg_TGV_denoising.py +++ b/Wrappers/Python/wip/pdhg_TGV_denoising.py @@ -105,100 +105,126 @@ normK = operator.norm() sigma = 1 tau = 1/(sigma*normK**2) ## -opt = {'niter':2000} -opt1 = {'niter':2000, 'memopt': True} +opt = {'niter':500} +opt1 = {'niter':500, 'memopt': True} # t1 = timer() res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) t2 = timer() # +t3 = timer() +res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) +t4 = timer() +# +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) plt.imshow(res[0].as_array()) +plt.title('no memopt') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(res1[0].as_array()) +plt.title('memopt') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow((res1[0] - res[0]).abs().as_array()) +plt.title('diff') +plt.colorbar() plt.show() +print("NoMemopt/Memopt is {}/{}".format(t2-t1, t4-t3)) + + +###### + +#%% -#t3 = timer() -#res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) -#t4 = timer() +#def update_plot(it_update, objective, x): +# +## sol = pdhg.get_output() +# plt.figure() +# plt.imshow(x[0].as_array()) +# plt.show() +# +# +##def stop_criterion(x,) # -#plt.figure(figsize=(15,15)) -#plt.subplot(3,1,1) -#plt.imshow(res[0].as_array()) -#plt.title('no memopt') -#plt.colorbar() -#plt.subplot(3,1,2) -#plt.imshow(res1[0].as_array()) -#plt.title('memopt') -#plt.colorbar() -#plt.subplot(3,1,3) -#plt.imshow((res1[0] - res[0]).abs().as_array()) -#plt.title('diff') -#plt.colorbar() -#plt.show() +#pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +#pdhg.max_iteration = 2000 +#pdhg.update_objective_interval = 100 # -#print("NoMemopt/Memopt is {}/{}".format(t2-t1, t4-t3)) - +#pdhg.run(4000, verbose=False, callback=update_plot) -#%% Check with CVX solution -from ccpi.optimisation.operators import SparseFiniteDiff +#%% -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False -if cvx_not_installable: - - u = Variable(ig.shape) - w1 = Variable((N, N)) - w2 = Variable((N, N)) - - # create TGV regulariser - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ - DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ - beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ - 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ - 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) - - constraints = [] - fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) - solver = MOSEK - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( res[0].as_array() - u.value ) - - # Show result - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(res[0].as_array()) - plt.title('PDHG solution') - plt.colorbar() - - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), res[0].as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(primal[-1])) - print('Min/Max of absolute difference {}/{}'.format(diff_cvx.min(), diff_cvx.max())) - + + + + + +#%% Check with CVX solution + +#from ccpi.optimisation.operators import SparseFiniteDiff +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +#if cvx_not_installable: +# +# u = Variable(ig.shape) +# w1 = Variable((N, N)) +# w2 = Variable((N, N)) +# +# # create TGV regulariser +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ +# DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ +# beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ +# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ +# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) +# +# constraints = [] +# fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) +# solver = MOSEK +# +# obj = Minimize( regulariser + fidelity) +# prob = Problem(obj) +# result = prob.solve(verbose = True, solver = solver) +# +# diff_cvx = numpy.abs( res[0].as_array() - u.value ) +# +# # Show result +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(res[0].as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# +# plt.subplot(3,1,2) +# plt.imshow(u.value) +# plt.title('CVX solution') +# plt.colorbar() +# +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() +# +# plt.plot(np.linspace(0,N,N), res[0].as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') +# plt.legend() +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(primal[-1])) +# print('Min/Max of absolute difference {}/{}'.format(diff_cvx.min(), diff_cvx.max())) +# diff --git a/Wrappers/Python/wip/pdhg_TV_tomography2D.py b/Wrappers/Python/wip/pdhg_TV_tomography2D.py index 2713cfd..91c48c7 100644 --- a/Wrappers/Python/wip/pdhg_TV_tomography2D.py +++ b/Wrappers/Python/wip/pdhg_TV_tomography2D.py @@ -8,16 +8,16 @@ Created on Fri Feb 22 14:53:03 2019 @author: evangelos """ -from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer, AcquisitionGeometry, AcquisitionData +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData import numpy as np import matplotlib.pyplot as plt from ccpi.optimisation.algorithms import PDHG, PDHG_old -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.operators import BlockOperator, Gradient from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction, ScaledFunction + MixedL21Norm, BlockFunction from ccpi.astra.ops import AstraProjectorSimple from skimage.util import random_noise -- cgit v1.2.3 From 0604ebff2fccfe1fa48defcecfba6dd75ef3249b Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 24 Apr 2019 10:49:59 +0100 Subject: fix TGV tomo2D example --- Wrappers/Python/wip/pdhg_TGV_tomography2D.py | 200 +++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 Wrappers/Python/wip/pdhg_TGV_tomography2D.py diff --git a/Wrappers/Python/wip/pdhg_TGV_tomography2D.py b/Wrappers/Python/wip/pdhg_TGV_tomography2D.py new file mode 100644 index 0000000..ee3b089 --- /dev/null +++ b/Wrappers/Python/wip/pdhg_TGV_tomography2D.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Feb 22 14:53:03 2019 + +@author: evangelos +""" + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, PDHG_old + +from ccpi.optimisation.operators import BlockOperator, Identity, \ + Gradient, SymmetrizedGradient, ZeroOperator +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +from timeit import default_timer as timer +from ccpi.astra.ops import AstraProjectorSimple + +#def dt(steps): +# return steps[-1] - steps[-2] + +# Create phantom for TGV Gaussian denoising + +N = 100 + +data = np.zeros((N,N)) + +x1 = np.linspace(0, int(N/2), N) +x2 = np.linspace(int(N/2), 0., N) +xv, yv = np.meshgrid(x1, x2) + +xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T + +data = xv +data = ImageData(data/data.max()) + +plt.imshow(data.as_array()) +plt.show() + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0,np.pi,N) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +plt.imshow(sin.as_array()) +plt.title('Sinogram') +plt.colorbar() +plt.show() + +# Add Gaussian noise to the sinogram data +np.random.seed(10) +n1 = np.random.random(sin.shape) + +noisy_data = sin + ImageData(5*n1) + +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Sinogram') +plt.colorbar() +plt.show() + +#%% + +alpha, beta = 20, 50 + + +# Create operators +op11 = Gradient(ig) +op12 = Identity(op11.range_geometry()) + +op22 = SymmetrizedGradient(op11.domain_geometry()) +op21 = ZeroOperator(ig, op22.range_geometry()) + +op31 = Aop +op32 = ZeroOperator(op22.domain_geometry(), ag) + +operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + + +f1 = alpha * MixedL21Norm() +f2 = beta * MixedL21Norm() +f3 = 0.5 * L2NormSquared(b = noisy_data) +f = BlockFunction(f1, f2, f3) +g = ZeroFunction() + +## Compute operator Norm +normK = operator.norm() +# +## Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +## +opt = {'niter':5000} +opt1 = {'niter':5000, 'memopt': True} +# +t1 = timer() +res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) +t2 = timer() +# +plt.imshow(res[0].as_array()) +plt.show() + + +#t3 = timer() +#res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) +#t4 = timer() +# +#plt.figure(figsize=(15,15)) +#plt.subplot(3,1,1) +#plt.imshow(res[0].as_array()) +#plt.title('no memopt') +#plt.colorbar() +#plt.subplot(3,1,2) +#plt.imshow(res1[0].as_array()) +#plt.title('memopt') +#plt.colorbar() +#plt.subplot(3,1,3) +#plt.imshow((res1[0] - res[0]).abs().as_array()) +#plt.title('diff') +#plt.colorbar() +#plt.show() +# +#print("NoMemopt/Memopt is {}/{}".format(t2-t1, t4-t3)) + + +#%% Check with CVX solution + +#from ccpi.optimisation.operators import SparseFiniteDiff +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +#if cvx_not_installable: +# +# u = Variable(ig.shape) +# w1 = Variable((N, N)) +# w2 = Variable((N, N)) +# +# # create TGV regulariser +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ +# DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ +# beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ +# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ +# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) +# +# constraints = [] +# fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) +# solver = MOSEK +# +# obj = Minimize( regulariser + fidelity) +# prob = Problem(obj) +# result = prob.solve(verbose = True, solver = solver) +# +# diff_cvx = numpy.abs( res[0].as_array() - u.value ) +# +# # Show result +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(res[0].as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# +# plt.subplot(3,1,2) +# plt.imshow(u.value) +# plt.title('CVX solution') +# plt.colorbar() +# +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() +# +# plt.plot(np.linspace(0,N,N), res[0].as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') +# plt.legend() +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(primal[-1])) +# print('Min/Max of absolute difference {}/{}'.format(diff_cvx.min(), diff_cvx.max())) + + + -- cgit v1.2.3 From 2e8b2def18c2d57920ad063056540873cb30d292 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 24 Apr 2019 13:49:21 +0100 Subject: test pdhg tomo TV 2D --- .../Python/ccpi/optimisation/algorithms/PDHG.py | 78 ++++++++++++++-------- Wrappers/Python/wip/pdhg_TV_tomography2D.py | 2 +- 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py index 110f998..0f5e8ef 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py @@ -47,34 +47,43 @@ class PDHG(Algorithm): self.y_old = self.operator.range_geometry().allocate() self.xbar = self.x_old.copy() - + self.x_tmp = self.x_old.copy() self.x = self.x_old.copy() - self.y = self.y_old.copy() - if self.memopt: - self.y_tmp = self.y_old.copy() - self.x_tmp = self.x_old.copy() - #y = y_tmp + + self.y_tmp = self.y_old.copy() + self.y = self.y_tmp.copy() + + + + #self.y = self.y_old.copy() + + + #if self.memopt: + # self.y_tmp = self.y_old.copy() + # self.x_tmp = self.x_old.copy() + # relaxation parameter self.theta = 1 def update(self): + if self.memopt: # Gradient descent, Dual problem solution # self.y_old += self.sigma * self.operator.direct(self.xbar) self.operator.direct(self.xbar, out=self.y_tmp) self.y_tmp *= self.sigma - self.y_old += self.y_tmp + self.y_tmp += self.y_old #self.y = self.f.proximal_conjugate(self.y_old, self.sigma) - self.f.proximal_conjugate(self.y_old, self.sigma, out=self.y) + self.f.proximal_conjugate(self.y_tmp, self.sigma, out=self.y) # Gradient ascent, Primal problem solution self.operator.adjoint(self.y, out=self.x_tmp) - self.x_tmp *= self.tau - self.x_old -= self.x_tmp + self.x_tmp *= -1*self.tau + self.x_tmp += self.x_old - self.g.proximal(self.x_old, self.tau, out=self.x) + self.g.proximal(self.x_tmp, self.tau, out=self.x) #Update self.x.subtract(self.x_old, out=self.xbar) @@ -83,7 +92,8 @@ class PDHG(Algorithm): self.x_old.fill(self.x) self.y_old.fill(self.y) - + + else: # Gradient descent, Dual problem solution self.y_old += self.sigma * self.operator.direct(self.xbar) @@ -94,14 +104,23 @@ class PDHG(Algorithm): self.x = self.g.proximal(self.x_old, self.tau) #Update - #xbar = x + theta * (x - x_old) - self.xbar.fill(self.x) - self.xbar -= self.x_old + self.x.subtract(self.x_old, out=self.xbar) self.xbar *= self.theta self.xbar += self.x - self.x_old = self.x - self.y_old = self.y + self.x_old.fill(self.x) + self.y_old.fill(self.y) + + #xbar = x + theta * (x - x_old) +# self.xbar.fill(self.x) +# self.xbar -= self.x_old +# self.xbar *= self.theta +# self.xbar += self.x + +# self.x_old.fill(self.x) +# self.y_old.fill(self.y) + + def update_objective(self): @@ -153,7 +172,7 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): if not memopt: - + y_tmp = y_old + sigma * operator.direct(xbar) y = f.proximal_conjugate(y_tmp, sigma) @@ -163,10 +182,7 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): x.subtract(x_old, out=xbar) xbar *= theta xbar += x - - x_old.fill(x) - y_old.fill(y) - + if i%50==0: p1 = f(operator.direct(x)) + g(x) @@ -174,7 +190,11 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): primal.append(p1) dual.append(d1) pdgap.append(p1-d1) - print(p1, d1, p1-d1) + print(p1, d1, p1-d1) + + x_old.fill(x) + y_old.fill(y) + else: @@ -192,10 +212,7 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): x.subtract(x_old, out=xbar) xbar *= theta xbar += x - - x_old.fill(x) - y_old.fill(y) - + if i%50==0: p1 = f(operator.direct(x)) + g(x) @@ -203,7 +220,12 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): primal.append(p1) dual.append(d1) pdgap.append(p1-d1) - print(p1, d1, p1-d1) + print(p1, d1, p1-d1) + + x_old.fill(x) + y_old.fill(y) + + t_end = time.time() diff --git a/Wrappers/Python/wip/pdhg_TV_tomography2D.py b/Wrappers/Python/wip/pdhg_TV_tomography2D.py index 91c48c7..0e167e3 100644 --- a/Wrappers/Python/wip/pdhg_TV_tomography2D.py +++ b/Wrappers/Python/wip/pdhg_TV_tomography2D.py @@ -126,7 +126,7 @@ t3 = timer() res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) t4 = timer() # - +# plt.figure(figsize=(15,15)) plt.subplot(3,1,1) plt.imshow(res.as_array()) -- cgit v1.2.3 From 90431756d8c385e51ae4c4de0c7a280e466cf915 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 24 Apr 2019 15:44:52 +0100 Subject: add complete Demos PDHG --- .../wip/Demos/PDHG_TGV_Denoising_SaltPepper.py | 196 +++++++++++++++++++++ .../Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py | 177 +++++++++++++++++++ .../Python/wip/Demos/PDHG_TV_Denoising_Poisson.py | 177 +++++++++++++++++++ .../wip/Demos/PDHG_TV_Denoising_SaltPepper.py | 177 +++++++++++++++++++ .../wip/Demos/sinogram_demo_tomography2D.npy | Bin 0 -> 60128 bytes Wrappers/Python/wip/Demos/untitled4.py | 87 +++++++++ 6 files changed, 814 insertions(+) create mode 100644 Wrappers/Python/wip/Demos/PDHG_TGV_Denoising_SaltPepper.py create mode 100644 Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py create mode 100644 Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py create mode 100644 Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py create mode 100644 Wrappers/Python/wip/Demos/sinogram_demo_tomography2D.npy create mode 100644 Wrappers/Python/wip/Demos/untitled4.py diff --git a/Wrappers/Python/wip/Demos/PDHG_TGV_Denoising_SaltPepper.py b/Wrappers/Python/wip/Demos/PDHG_TGV_Denoising_SaltPepper.py new file mode 100644 index 0000000..dffb6d8 --- /dev/null +++ b/Wrappers/Python/wip/Demos/PDHG_TGV_Denoising_SaltPepper.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Feb 22 14:53:03 2019 + +@author: evangelos +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, \ + Gradient, SymmetrizedGradient, ZeroOperator +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TGV SaltPepper denoising + +N = 300 + +data = np.zeros((N,N)) + +x1 = np.linspace(0, int(N/2), N) +x2 = np.linspace(int(N/2), 0., N) +xv, yv = np.meshgrid(x1, x2) + +xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T + +data = xv +data = ImageData(data/data.max()) + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Add Gaussian noise +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Regularisation Parameters +alpha = 0.8 +beta = numpy.sqrt(2)* alpha + +method = '0' + +if method == '0': + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + op31 = Identity(ig, ag) + op32 = ZeroOperator(op22.domain_geometry(), ag) + + operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + f3 = L1Norm(b=noisy_data) + f = BlockFunction(f1, f2, f3) + g = ZeroFunction() + +else: + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + operator = BlockOperator(op11, -1*op12, op21, op22, shape=(2,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + + f = BlockFunction(f1, f2) + g = BlockFunction(0.5 * L1Norm(b=noisy_data), ZeroFunction()) + +## Compute operator Norm +normK = operator.norm() +# +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + +#%% +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output()[0].as_array()) +plt.title('TGV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + + + + + + + + + +#%% Check with CVX solution + +#from ccpi.optimisation.operators import SparseFiniteDiff +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +#if cvx_not_installable: +# +# u = Variable(ig.shape) +# w1 = Variable((N, N)) +# w2 = Variable((N, N)) +# +# # create TGV regulariser +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ +# DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ +# beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ +# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ +# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) +# +# constraints = [] +# fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) +# solver = MOSEK +# +# obj = Minimize( regulariser + fidelity) +# prob = Problem(obj) +# result = prob.solve(verbose = True, solver = solver) +# +# diff_cvx = numpy.abs( res[0].as_array() - u.value ) +# +# # Show result +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(res[0].as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# +# plt.subplot(3,1,2) +# plt.imshow(u.value) +# plt.title('CVX solution') +# plt.colorbar() +# +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() +# +# plt.plot(np.linspace(0,N,N), res[0].as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') +# plt.legend() +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(primal[-1])) +# print('Min/Max of absolute difference {}/{}'.format(diff_cvx.min(), diff_cvx.max())) +# + + diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py new file mode 100644 index 0000000..10e5621 --- /dev/null +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Gaussian denoising +N = 300 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Add Gaussian noise +n1 = random_noise(data.as_array(), mode = 'gaussian', mean=0, var = 0.05, seed=10) +noisy_data = ImageData(n1) + +# Regularisation Parameter +alpha = 0.5 + +method = '1' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = 0.5 * L2NormSquared(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py new file mode 100644 index 0000000..f2c2023 --- /dev/null +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Poisson denoising +N = 300 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Apply Poisson noise +n1 = random_noise(data.as_array(), mode = 'poisson', seed = 10) +noisy_data = ImageData(n1) + +# Regularisation Parameter +alpha = 2 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = KullbackLeibler(noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = KullbackLeibler(noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +opt = {'niter':2000, 'memopt': True} + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +##%% Check with CVX solution +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u1 = Variable(ig.shape) + q = Variable() + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) + + fidelity = sum( u1 - multiply(noisy_data.as_array(), log(u1)) ) + constraints = [q>= fidelity, u1>=0] + + solver = ECOS + obj = Minimize( regulariser + q) + prob = Problem(obj, constraints) + result = prob.solve(verbose = True, solver = solver) + + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u1.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u1.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py new file mode 100644 index 0000000..f96acd5 --- /dev/null +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Salt & Pepper denoising +N = 300 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Apply Salt & Pepper noise +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Regularisation Parameter +alpha = 2 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = L1Norm(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = L1Norm(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +opt = {'niter':2000, 'memopt': True} + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = pnorm( u - noisy_data.as_array(),1) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/wip/Demos/sinogram_demo_tomography2D.npy b/Wrappers/Python/wip/Demos/sinogram_demo_tomography2D.npy new file mode 100644 index 0000000..f37fd4b Binary files /dev/null and b/Wrappers/Python/wip/Demos/sinogram_demo_tomography2D.npy differ diff --git a/Wrappers/Python/wip/Demos/untitled4.py b/Wrappers/Python/wip/Demos/untitled4.py new file mode 100644 index 0000000..0cacbd7 --- /dev/null +++ b/Wrappers/Python/wip/Demos/untitled4.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Apr 24 14:21:08 2019 + +@author: evangelos +""" + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData +import numpy +import numpy as np +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, PDHG_old + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple, AstraProjector3DSimple +from skimage.util import random_noise +from timeit import default_timer as timer + +#N = 75 +#x = np.zeros((N,N)) +#x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +#x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +#data = ImageData(x) + +N = 75 +#x = np.zeros((N,N)) + +vert = 4 +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, voxel_num_z=vert) +data = ig.allocate() +Phantom = data +# Populate image data by looping over and filling slices +i = 0 +while i < vert: + if vert > 1: + x = Phantom.subset(vertical=i).array + else: + x = Phantom.array + x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 + x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 0.98 + if vert > 1 : + Phantom.fill(x, vertical=i) + i += 1 + +angles_num = 100 +det_w = 1.0 +det_num = N + +angles = np.linspace(0,np.pi,angles_num,endpoint=False,dtype=np.float32)*\ + 180/np.pi + +# Inputs: Geometry, 2D or 3D, angles, horz detector pixel count, +# horz detector pixel size, vert detector pixel count, +# vert detector pixel size. +ag = AcquisitionGeometry('parallel', + '3D', + angles, + N, + det_w, + vert, + det_w) + +sino = numpy.load("sinogram_demo_tomography2D.npy", mmap_mode='r') +plt.imshow(sino) +plt.title('Sinogram CCPi') +plt.colorbar() +plt.show() + +#%% +Aop = AstraProjector3DSimple(ig, ag) +sin = Aop.direct(data) + +plt.imshow(sin.as_array()) + +plt.title('Sinogram Astra') +plt.colorbar() +plt.show() + + + +#%% \ No newline at end of file -- cgit v1.2.3 From e05bed848d826d219efba6f78354b4c3f76161cc Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 25 Apr 2019 10:06:59 +0100 Subject: add denoising demos --- .../wip/Demos/PDHG_TGV_Denoising_SaltPepper.py | 132 ++++++++++----------- .../Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py | 2 +- .../Python/wip/Demos/PDHG_TV_Denoising_Poisson.py | 4 +- .../wip/Demos/PDHG_TV_Denoising_SaltPepper.py | 4 +- 4 files changed, 70 insertions(+), 72 deletions(-) diff --git a/Wrappers/Python/wip/Demos/PDHG_TGV_Denoising_SaltPepper.py b/Wrappers/Python/wip/Demos/PDHG_TGV_Denoising_SaltPepper.py index dffb6d8..7b65c31 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TGV_Denoising_SaltPepper.py +++ b/Wrappers/Python/wip/Demos/PDHG_TGV_Denoising_SaltPepper.py @@ -23,7 +23,7 @@ from skimage.util import random_noise # Create phantom for TGV SaltPepper denoising -N = 300 +N = 100 data = np.zeros((N,N)) @@ -47,7 +47,7 @@ noisy_data = ImageData(n1) alpha = 0.8 beta = numpy.sqrt(2)* alpha -method = '0' +method = '1' if method == '0': @@ -83,7 +83,7 @@ else: f2 = beta * MixedL21Norm() f = BlockFunction(f1, f2) - g = BlockFunction(0.5 * L1Norm(b=noisy_data), ZeroFunction()) + g = BlockFunction(L1Norm(b=noisy_data), ZeroFunction()) ## Compute operator Norm normK = operator.norm() @@ -122,75 +122,73 @@ plt.title('Middle Line Profiles') plt.show() +#%% Check with CVX solution +from ccpi.optimisation.operators import SparseFiniteDiff +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False +if cvx_not_installable: + + u = Variable(ig.shape) + w1 = Variable((N, N)) + w2 = Variable((N, N)) + + # create TGV regulariser + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ + DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ + beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) + + constraints = [] + fidelity = pnorm(u - noisy_data.as_array(),1) + solver = MOSEK + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output()[0].as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output()[0].as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) -#%% Check with CVX solution - -#from ccpi.optimisation.operators import SparseFiniteDiff -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -#if cvx_not_installable: -# -# u = Variable(ig.shape) -# w1 = Variable((N, N)) -# w2 = Variable((N, N)) -# -# # create TGV regulariser -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ -# DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ -# beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ -# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ -# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) -# -# constraints = [] -# fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) -# solver = MOSEK -# -# obj = Minimize( regulariser + fidelity) -# prob = Problem(obj) -# result = prob.solve(verbose = True, solver = solver) -# -# diff_cvx = numpy.abs( res[0].as_array() - u.value ) -# -# # Show result -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(res[0].as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# -# plt.subplot(3,1,2) -# plt.imshow(u.value) -# plt.title('CVX solution') -# plt.colorbar() -# -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), res[0].as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') -# plt.legend() -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(primal[-1])) -# print('Min/Max of absolute difference {}/{}'.format(diff_cvx.min(), diff_cvx.max())) -# - - diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py index 10e5621..1a3e0df 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py @@ -48,7 +48,7 @@ noisy_data = ImageData(n1) # Regularisation Parameter alpha = 0.5 -method = '1' +method = '0' if method == '0': diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py index f2c2023..482b3b4 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py @@ -32,7 +32,7 @@ from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ from skimage.util import random_noise # Create phantom for TV Poisson denoising -N = 300 +N = 100 data = np.zeros((N,N)) data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 @@ -48,7 +48,7 @@ noisy_data = ImageData(n1) # Regularisation Parameter alpha = 2 -method = '0' +method = '1' if method == '0': diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py index f96acd5..484fe42 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py @@ -32,7 +32,7 @@ from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ from skimage.util import random_noise # Create phantom for TV Salt & Pepper denoising -N = 300 +N = 100 data = np.zeros((N,N)) data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 @@ -48,7 +48,7 @@ noisy_data = ImageData(n1) # Regularisation Parameter alpha = 2 -method = '0' +method = '1' if method == '0': -- cgit v1.2.3 From f1da1e2bda23a978c92b91a5d533c7d4b331acc3 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 25 Apr 2019 10:08:08 +0100 Subject: change verbose format --- .../Python/ccpi/optimisation/algorithms/Algorithm.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py index ed95c3f..3c97480 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py @@ -145,13 +145,27 @@ class Algorithm(object): if self.should_stop(): print ("Stop cryterion has been reached.") i = 0 + + print("Iteration {:<5} Primal {:<5} Dual {:<5} PDgap".format('','','')) for _ in self: + + if verbose and self.iteration % self.update_objective_interval == 0: - print ("Iteration {}/{}, objective {}".format(self.iteration, - self.max_iteration, self.get_last_objective()) ) + #pass + print( "{}/{} {:<5} {:.4f} {:<5} {:.4f} {:<5} {:.4f}".\ + format(self.iteration, self.max_iteration,'', \ + self.get_last_objective()[0],'',\ + self.get_last_objective()[1],'',\ + self.get_last_objective()[2])) + + + #print ("Iteration {}/{}, Primal, Dual, PDgap = {}".format(self.iteration, + # self.max_iteration, self.get_last_objective()) ) + + else: if callback is not None: - callback(self.iteration, self.get_last_objective()) + callback(self.iteration, self.get_last_objective(), self.x) i += 1 if i == iterations: break -- cgit v1.2.3 From e021cf74ab461bc72ecc42543852d119bfcd4549 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 25 Apr 2019 10:09:23 +0100 Subject: add dot method --- Wrappers/Python/ccpi/framework/BlockDataContainer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/BlockDataContainer.py b/Wrappers/Python/ccpi/framework/BlockDataContainer.py index 823a1bd..166014b 100755 --- a/Wrappers/Python/ccpi/framework/BlockDataContainer.py +++ b/Wrappers/Python/ccpi/framework/BlockDataContainer.py @@ -443,10 +443,10 @@ class BlockDataContainer(object): '''Inline truedivision''' return self.__idiv__(other) -# def dot(self, other): + def dot(self, other): # -# tmp = [ self.containers[i].dot(other.containers[i]) for i in range(self.shape[0])] -# return sum(tmp) + tmp = [ self.containers[i].dot(other.containers[i]) for i in range(self.shape[0])] + return sum(tmp) -- cgit v1.2.3 From f6d46771664363b4448c53c8399c3fe1da425f59 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 25 Apr 2019 10:09:52 +0100 Subject: Symmetric BlockGeometry --- Wrappers/Python/ccpi/framework/BlockGeometry.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/BlockGeometry.py b/Wrappers/Python/ccpi/framework/BlockGeometry.py index 7fc5cb8..ed44d99 100755 --- a/Wrappers/Python/ccpi/framework/BlockGeometry.py +++ b/Wrappers/Python/ccpi/framework/BlockGeometry.py @@ -37,16 +37,15 @@ class BlockGeometry(object): containers = [geom.allocate(value) for geom in self.geometries] if symmetry == True: - - # TODO works but needs better coding - + # for 2x2 # [ ig11, ig12\ # ig21, ig22] + # Row-wise Order if len(containers)==4: - containers[1] = containers[2] + containers[1]=containers[2] # for 3x3 # [ ig11, ig12, ig13\ -- cgit v1.2.3 From e7bfeab8ef7c723022d0f36a3be945f6b8a056c8 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 25 Apr 2019 11:17:50 +0100 Subject: Tikhonov demos --- .../Python/wip/Demos/PDHG_Tikhonov_Denoising.py | 176 +++++++++++++++++++++ Wrappers/Python/wip/Demos/PDHG_Tikhonov_Tomo2D.py | 108 +++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py create mode 100644 Wrappers/Python/wip/Demos/PDHG_Tikhonov_Tomo2D.py diff --git a/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py b/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py new file mode 100644 index 0000000..3f275e2 --- /dev/null +++ b/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Salt & Pepper denoising +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Apply Salt & Pepper noise +n1 = random_noise(data.as_array(), mode = 'gaussian', mean=0, var = 0.05, seed=10) +noisy_data = ImageData(n1) + +# Regularisation Parameter +alpha = 4 + +method = '1' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * L2NormSquared() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * L2NormSquared() + g = 0.5 * L2NormSquared(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +opt = {'niter':2000, 'memopt': True} + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('Tikhonov Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + + regulariser = alpha * sum_squares(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Tomo2D.py b/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Tomo2D.py new file mode 100644 index 0000000..5c03362 --- /dev/null +++ b/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Tomo2D.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction +from skimage.util import random_noise +from ccpi.astra.ops import AstraProjectorSimple + +# Create phantom for TV 2D tomography +N = 75 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Gaussian noise + +np.random.seed(10) +noisy_data = sin + AcquisitionData(np.random.normal(0, 3, sin.shape)) + +# Regularisation Parameter +alpha = 500 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * L2NormSquared() +f2 = 0.5 * L2NormSquared(b=noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 5000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + +#%% +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('Tikhonov Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + -- cgit v1.2.3 From b79143152345bc0e2c49320bc1d7607cedda51cc Mon Sep 17 00:00:00 2001 From: Vaggelis Papoutsellis <22398586+epapoutsellis@users.noreply.github.com> Date: Thu, 25 Apr 2019 11:21:08 +0100 Subject: Delete sinogram_demo_tomography2D.npy --- .../Python/wip/Demos/sinogram_demo_tomography2D.npy | Bin 60128 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Wrappers/Python/wip/Demos/sinogram_demo_tomography2D.npy diff --git a/Wrappers/Python/wip/Demos/sinogram_demo_tomography2D.npy b/Wrappers/Python/wip/Demos/sinogram_demo_tomography2D.npy deleted file mode 100644 index f37fd4b..0000000 Binary files a/Wrappers/Python/wip/Demos/sinogram_demo_tomography2D.npy and /dev/null differ -- cgit v1.2.3 From d8cbae5c0ed33ea0c30f1e0f3518e2e97c0a86ba Mon Sep 17 00:00:00 2001 From: Vaggelis Papoutsellis <22398586+epapoutsellis@users.noreply.github.com> Date: Thu, 25 Apr 2019 11:21:24 +0100 Subject: Delete untitled4.py --- Wrappers/Python/wip/Demos/untitled4.py | 87 ---------------------------------- 1 file changed, 87 deletions(-) delete mode 100644 Wrappers/Python/wip/Demos/untitled4.py diff --git a/Wrappers/Python/wip/Demos/untitled4.py b/Wrappers/Python/wip/Demos/untitled4.py deleted file mode 100644 index 0cacbd7..0000000 --- a/Wrappers/Python/wip/Demos/untitled4.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Wed Apr 24 14:21:08 2019 - -@author: evangelos -""" - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData -import numpy -import numpy as np -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, PDHG_old - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from ccpi.astra.ops import AstraProjectorSimple, AstraProjector3DSimple -from skimage.util import random_noise -from timeit import default_timer as timer - -#N = 75 -#x = np.zeros((N,N)) -#x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -#x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -#data = ImageData(x) - -N = 75 -#x = np.zeros((N,N)) - -vert = 4 -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, voxel_num_z=vert) -data = ig.allocate() -Phantom = data -# Populate image data by looping over and filling slices -i = 0 -while i < vert: - if vert > 1: - x = Phantom.subset(vertical=i).array - else: - x = Phantom.array - x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 - x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 0.98 - if vert > 1 : - Phantom.fill(x, vertical=i) - i += 1 - -angles_num = 100 -det_w = 1.0 -det_num = N - -angles = np.linspace(0,np.pi,angles_num,endpoint=False,dtype=np.float32)*\ - 180/np.pi - -# Inputs: Geometry, 2D or 3D, angles, horz detector pixel count, -# horz detector pixel size, vert detector pixel count, -# vert detector pixel size. -ag = AcquisitionGeometry('parallel', - '3D', - angles, - N, - det_w, - vert, - det_w) - -sino = numpy.load("sinogram_demo_tomography2D.npy", mmap_mode='r') -plt.imshow(sino) -plt.title('Sinogram CCPi') -plt.colorbar() -plt.show() - -#%% -Aop = AstraProjector3DSimple(ig, ag) -sin = Aop.direct(data) - -plt.imshow(sin.as_array()) - -plt.title('Sinogram Astra') -plt.colorbar() -plt.show() - - - -#%% \ No newline at end of file -- cgit v1.2.3 From 3be687f3d78b2edcbfec19bb24c3cd0493e7259a Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 25 Apr 2019 11:22:14 +0100 Subject: TV, TGV Tomo --- Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py | 124 ++++++++++++++++ Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py | 211 +++++++++++++++++++++++++++ 2 files changed, 335 insertions(+) create mode 100644 Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py create mode 100644 Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py diff --git a/Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py b/Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py new file mode 100644 index 0000000..26578bb --- /dev/null +++ b/Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient, Identity, \ + SymmetrizedGradient, ZeroOperator +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple + +# Create phantom for TV 2D tomography +N = 75 + +data = np.zeros((N,N)) + +x1 = np.linspace(0, int(N/2), N) +x2 = np.linspace(int(N/2), 0., N) +xv, yv = np.meshgrid(x1, x2) + +xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T +data = xv +data = ImageData(data/data.max()) + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +scale = 0.1 +np.random.seed(5) +n1 = scale * np.random.poisson(sin.as_array()/scale) +noisy_data = AcquisitionData(n1, ag) + + +plt.imshow(noisy_data.as_array()) +plt.show() +#%% +# Regularisation Parameters +alpha = 0.7 +beta = 2 + +# Create Operators +op11 = Gradient(ig) +op12 = Identity(op11.range_geometry()) + +op22 = SymmetrizedGradient(op11.domain_geometry()) +op21 = ZeroOperator(ig, op22.range_geometry()) + +op31 = Aop +op32 = ZeroOperator(op22.domain_geometry(), ag) + +operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + +f1 = alpha * MixedL21Norm() +f2 = beta * MixedL21Norm() +f3 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2, f3) +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output()[0].as_array()) +plt.title('TGV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TGV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py b/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py new file mode 100644 index 0000000..0711e91 --- /dev/null +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple + +# Create phantom for TV 2D tomography +N = 75 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +scale = 2 +n1 = scale * np.random.poisson(sin.as_array()/scale) +noisy_data = AcquisitionData(n1, ag) + +# Regularisation Parameter +alpha = 5 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff +import astra +import numpy + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + + ##Construct problem + u = Variable(N*N) + #q = Variable() + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + + # create matrix representation for Astra operator + + vol_geom = astra.create_vol_geom(N, N) + proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + + proj_id = astra.create_projector('strip', proj_geom, vol_geom) + + matrix_id = astra.projector.matrix(proj_id) + + ProjMat = astra.matrix.get(matrix_id) + + fidelity = sum( ProjMat * u - noisy_data.as_array().ravel() * log(ProjMat * u)) + #constraints = [q>= fidelity, u>=0] + constraints = [u>=0] + + solver = SCS + obj = Minimize( regulariser + fidelity) + prob = Problem(obj, constraints) + result = prob.solve(verbose = True, solver = solver) + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = pnorm( u - noisy_data.as_array(),1) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(np.reshape(u.value, (N, N))) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file -- cgit v1.2.3 From 36c36aa4395eb7625b28180bdd6bd376ae2017a7 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 25 Apr 2019 13:10:03 +0100 Subject: add 3D TV denoising --- .../wip/Demos/PDHG_TV_Denoising_Gaussian_3D.py | 155 +++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian_3D.py diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian_3D.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian_3D.py new file mode 100644 index 0000000..c86ddc9 --- /dev/null +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian_3D.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Gaussian denoising +import timeit +import os +from tomophantom import TomoP3D +import tomophantom + +print ("Building 3D phantom using TomoPhantom software") +tic=timeit.default_timer() +model = 13 # select a model number from the library +N = 64 # Define phantom dimensions using a scalar value (cubic phantom) +path = os.path.dirname(tomophantom.__file__) +path_library3D = os.path.join(path, "Phantom3DLibrary.dat") + +#This will generate a N x N x N phantom (3D) +phantom_tm = TomoP3D.Model(model, N, path_library3D) + +# Create noisy data. Add Gaussian noise +ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N, voxel_num_z=N) +ag = ig +n1 = random_noise(phantom_tm, mode = 'gaussian', mean=0, var = 0.001, seed=10) +noisy_data = ImageData(n1) + +sliceSel = int(0.5*N) +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.title('Axial View') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.title('Coronal View') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.title('Sagittal View') +plt.colorbar() +plt.show() + + +# Regularisation Parameter +alpha = 0.05 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = 0.5 * L2NormSquared(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) + +plt.subplot(2,3,1) +plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Axial View') + +plt.subplot(2,3,2) +plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Coronal View') + +plt.subplot(2,3,3) +plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.axis('off') +plt.title('Sagittal View') + + +plt.subplot(2,3,4) +plt.imshow(pdhg.get_output().as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.axis('off') +plt.subplot(2,3,5) +plt.imshow(pdhg.get_output().as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.axis('off') +plt.subplot(2,3,6) +plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.axis('off') +im = plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + -- cgit v1.2.3 From d34028a672b22f7c4a02464736d74c70fb354362 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 25 Apr 2019 16:23:57 +0100 Subject: memopt fix prox conjugate --- .../ccpi/optimisation/functions/KullbackLeibler.py | 39 ++++++---------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index 22d21fd..14b5ea0 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -62,19 +62,6 @@ class KullbackLeibler(Function): if out is None: return 1 - self.b/(x + self.bnoise) else: -#<<<<<<< HEAD -# self.b.divide(x+self.bnoise, out=out) -# out.subtract(1, out=out) -# -# def convex_conjugate(self, x): -# -# tmp = self.b/( 1 - x ) -# ind = tmp.as_array()>0 -# -# sel -# -# return (self.b * ( ImageData( numpy.log(tmp) ) - 1 ) - self.bnoise * (x - 1)).sum() -#======= x.add(self.bnoise, out=out) self.b.divide(out, out=out) out.subtract(1, out=out) @@ -116,23 +103,19 @@ class KullbackLeibler(Function): if out is None: z = x + tau * self.bnoise - return 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) else: - z = x + tau * self.bnoise - res = 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) - out.fill(res) -# else: -# z_m = x + tau * self.bnoise -1 -# self.b.multiply(4*tau, out=out) -# z_m.multiply(z_m, out=z_m) -# out += z_m -# out.sqrt(out=out) -# z = z_m + 2 -# z_m.sqrt(out=z_m) -# z_m += 2 -# out *= -1 -# out += z_m + z_m = x + tau * self.bnoise -1 + self.b.multiply(4*tau, out=out) + z_m.multiply(z_m, out=z_m) + out += z_m + out.sqrt(out=out) + z_m.sqrt(out=z_m) + z_m += 2 + out *= -1 + out += z_m + out *= 0.5 + def __rmul__(self, scalar): -- cgit v1.2.3 From b726c09f16c256daf92544668d2ff51bed3cf06f Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 25 Apr 2019 16:30:33 +0100 Subject: L2Norm fix memopt --- Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py index 5490782..20e754e 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py +++ b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py @@ -94,12 +94,12 @@ class L2NormSquared(Function): return x/(1+2*tau) else: -# tmp = x.subtract(self.b) + tmp = x.subtract(self.b) # tmp -= self.b -# tmp /= (1+2*tau) -# tmp += self.b -# return tmp - return (x-self.b)/(1+2*tau) + self.b + tmp /= (1+2*tau) + tmp += self.b + return tmp +# return (x-self.b)/(1+2*tau) + self.b # if self.b is not None: # out=x -- cgit v1.2.3 From fb6f44dff00c194e4936f39e4be8d0f22e8fe4da Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 25 Apr 2019 17:43:16 +0100 Subject: TV dynamic tomo --- Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D_time.py | 169 +++++++++++++++++++++++ Wrappers/Python/wip/pdhg_TV_tomography2D_time.py | 4 +- 2 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D_time.py diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D_time.py b/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D_time.py new file mode 100644 index 0000000..045458a --- /dev/null +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D_time.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorMC + +import os +import tomophantom +from tomophantom import TomoP2D + +# Create phantom for TV 2D dynamic tomography + +model = 102 # note that the selected model is temporal (2D + time) +N = 50 # set dimension of the phantom +# one can specify an exact path to the parameters file +# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' +path = os.path.dirname(tomophantom.__file__) +path_library2D = os.path.join(path, "Phantom2DLibrary.dat") +#This will generate a N_size x N_size x Time frames phantom (2D + time) +phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) + +plt.close('all') +plt.figure(1) +plt.rcParams.update({'font.size': 21}) +plt.title('{}''{}'.format('2D+t phantom using model no.',model)) +for sl in range(0,np.shape(phantom_2Dt)[0]): + im = phantom_2Dt[sl,:,:] + plt.imshow(im, vmin=0, vmax=1) + plt.pause(.1) + plt.draw + + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) +data = ImageData(phantom_2Dt, geometry=ig) + +detectors = N +angles = np.linspace(0,np.pi,N) + +ag = AcquisitionGeometry('parallel','2D', angles, detectors, channels = np.shape(phantom_2Dt)[0]) +Aop = AstraProjectorMC(ig, ag, 'gpu') +sin = Aop.direct(data) + +scale = 2 +n1 = scale * np.random.poisson(sin.as_array()/scale) +noisy_data = AcquisitionData(n1, ag) + +tindex = [3, 6, 10] + +fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) +plt.subplot(1,3,1) +plt.imshow(noisy_data.as_array()[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) +plt.subplot(1,3,2) +plt.imshow(noisy_data.as_array()[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) +plt.subplot(1,3,3) +plt.imshow(noisy_data.as_array()[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +plt.show() + +#%% +# Regularisation Parameter +alpha = 5 + +# Create operators +#op1 = Gradient(ig) +op1 = Gradient(ig, correlation='SpaceChannels') +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + + +#%% +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) + +plt.subplot(2,3,1) +plt.imshow(phantom_2Dt[tindex[0],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) + +plt.subplot(2,3,2) +plt.imshow(phantom_2Dt[tindex[1],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) + +plt.subplot(2,3,3) +plt.imshow(phantom_2Dt[tindex[2],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + + +plt.subplot(2,3,4) +plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(2,3,5) +plt.imshow(pdhg.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(2,3,6) +plt.imshow(pdhg.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') +im = plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + diff --git a/Wrappers/Python/wip/pdhg_TV_tomography2D_time.py b/Wrappers/Python/wip/pdhg_TV_tomography2D_time.py index dea8e5c..5423b22 100644 --- a/Wrappers/Python/wip/pdhg_TV_tomography2D_time.py +++ b/Wrappers/Python/wip/pdhg_TV_tomography2D_time.py @@ -16,7 +16,7 @@ import matplotlib.pyplot as plt from ccpi.optimisation.algorithms import PDHG, PDHG_old from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFun, L2NormSquared, \ +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ MixedL21Norm, BlockFunction, ScaledFunction from ccpi.astra.ops import AstraProjectorSimple, AstraProjectorMC @@ -100,7 +100,7 @@ operator = BlockOperator(op1, op2, shape=(2,1) ) alpha = 50 f = BlockFunction( alpha * MixedL21Norm(), \ 0.5 * L2NormSquared(b = noisy_data) ) -g = ZeroFun() +g = ZeroFunction() # Compute operator Norm normK = operator.norm() -- cgit v1.2.3 From 07e7344926bc8850ef5e2c1580fdff580ca8c9a9 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 25 Apr 2019 17:45:46 +0100 Subject: callback fix --- Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py index 482b3b4..32ab62d 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py @@ -87,7 +87,19 @@ opt = {'niter':2000, 'memopt': True} pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) pdhg.max_iteration = 2000 pdhg.update_objective_interval = 50 -pdhg.run(2000) + +def pdgap_print(niter, objective, solution): + + + print( "{:04}/{:04} {:<5} {:.4f} {:<5} {:.4f} {:<5} {:.4f}".\ + format(niter, pdhg.max_iteration,'', \ + objective[0],'',\ + objective[1],'',\ + objective[2])) + +#pdhg.run(2000) + +pdhg.run(2000, callback = pdgap_print) plt.figure(figsize=(15,15)) -- cgit v1.2.3 From 8ef753231e74b4dad339370661b563a57ffe75cf Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 25 Apr 2019 17:48:03 +0100 Subject: fix L2 and ALgo --- .../ccpi/optimisation/algorithms/Algorithm.py | 25 ++++++++++++++-------- .../ccpi/optimisation/functions/L2NormSquared.py | 8 ++++++- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py index 3c97480..47376a5 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py @@ -149,23 +149,30 @@ class Algorithm(object): print("Iteration {:<5} Primal {:<5} Dual {:<5} PDgap".format('','','')) for _ in self: + + if self.iteration % self.update_objective_interval == 0: + if verbose: - if verbose and self.iteration % self.update_objective_interval == 0: +# if verbose and self.iteration % self.update_objective_interval == 0: #pass - print( "{}/{} {:<5} {:.4f} {:<5} {:.4f} {:<5} {:.4f}".\ - format(self.iteration, self.max_iteration,'', \ - self.get_last_objective()[0],'',\ - self.get_last_objective()[1],'',\ - self.get_last_objective()[2])) + # \t for tab +# print( "{:04}/{:04} {:<5} {:.4f} {:<5} {:.4f} {:<5} {:.4f}".\ +# format(self.iteration, self.max_iteration,'', \ +# self.get_last_objective()[0],'',\ +# self.get_last_objective()[1],'',\ +# self.get_last_objective()[2])) + print ("Iteration {}/{}, {}".format(self.iteration, + self.max_iteration, self.get_last_objective()) ) + #print ("Iteration {}/{}, Primal, Dual, PDgap = {}".format(self.iteration, # self.max_iteration, self.get_last_objective()) ) - else: - if callback is not None: - callback(self.iteration, self.get_last_objective(), self.x) + else: + if callback is not None: + callback(self.iteration, self.get_last_objective(), self.x) i += 1 if i == iterations: break diff --git a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py index 20e754e..6cb3d8a 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py +++ b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py @@ -146,7 +146,13 @@ class L2NormSquared(Function): ''' - return ScaledFunction(self, scalar) + return ScaledFunction(self, scalar) + + + def operator_composition(self, operator): + + return FunctionOperatorComposition + if __name__ == '__main__': -- cgit v1.2.3 From cf4f909599f945c1af34daf00a9928dfeff4d041 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 26 Apr 2019 11:16:49 +0100 Subject: fixed prints --- .../ccpi/optimisation/algorithms/Algorithm.py | 23 +++++++--------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py index 3c97480..bd48e13 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py @@ -34,7 +34,7 @@ class Algorithm(object): method will stop when the stopping cryterion is met. ''' - def __init__(self): + def __init__(self, **kwargs): '''Constructor Set the minimal number of parameters: @@ -48,7 +48,7 @@ class Algorithm(object): when evaluating the objective is computationally expensive. ''' self.iteration = 0 - self.__max_iteration = 0 + self.__max_iteration = kwargs.get('max_iteration', 0) self.__loss = [] self.memopt = False self.timing = [] @@ -91,9 +91,11 @@ class Algorithm(object): if self.iteration % self.update_objective_interval == 0: self.update_objective() self.iteration += 1 + def get_output(self): '''Returns the solution found''' return self.x + def get_last_loss(self): '''Returns the last stored value of the loss function @@ -146,21 +148,10 @@ class Algorithm(object): print ("Stop cryterion has been reached.") i = 0 - print("Iteration {:<5} Primal {:<5} Dual {:<5} PDgap".format('','','')) for _ in self: - - - if verbose and self.iteration % self.update_objective_interval == 0: - #pass - print( "{}/{} {:<5} {:.4f} {:<5} {:.4f} {:<5} {:.4f}".\ - format(self.iteration, self.max_iteration,'', \ - self.get_last_objective()[0],'',\ - self.get_last_objective()[1],'',\ - self.get_last_objective()[2])) - - - #print ("Iteration {}/{}, Primal, Dual, PDgap = {}".format(self.iteration, - # self.max_iteration, self.get_last_objective()) ) + if verbose and (self.iteration -1) % self.update_objective_interval == 0: + print ("Iteration {}/{}, = {}".format(self.iteration-1, + self.max_iteration, self.get_last_objective()) ) else: -- cgit v1.2.3 From a0c532bc1e55574aa5262901614545bdfb006dc7 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 26 Apr 2019 11:17:09 +0100 Subject: removed memopt as not needed --- Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py index 2cbd146..c0b774d 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py @@ -19,14 +19,13 @@ class PDHG(Algorithm): '''Primal Dual Hybrid Gradient''' def __init__(self, **kwargs): - super(PDHG, self).__init__() + super(PDHG, self).__init__(max_iteration=kwargs.get('max_iteration',0)) self.f = kwargs.get('f', None) self.operator = kwargs.get('operator', None) self.g = kwargs.get('g', None) self.tau = kwargs.get('tau', None) self.sigma = kwargs.get('sigma', None) - self.memopt = kwargs.get('memopt', False) - + if self.f is not None and self.operator is not None and \ self.g is not None: print ("Calling from creator") @@ -46,25 +45,22 @@ class PDHG(Algorithm): self.opt = opt if sigma is None and tau is None: raise ValueError('Need sigma*tau||K||^2<1') - - + self.x_old = self.operator.domain_geometry().allocate() self.x_tmp = self.x_old.copy() self.x = self.x_old.copy() - + self.y_old = self.operator.range_geometry().allocate() self.y_tmp = self.y_old.copy() self.y = self.y_old.copy() - + self.xbar = self.x_old.copy() - # relaxation parameter self.theta = 1 def update(self): - # Gradient descent, Dual problem solution self.operator.direct(self.xbar, out=self.y_tmp) self.y_tmp *= self.sigma @@ -90,7 +86,7 @@ class PDHG(Algorithm): self.y_old.fill(self.y) def update_objective(self): - + p1 = self.f(self.operator.direct(self.x)) + self.g(self.x) d1 = -(self.f.convex_conjugate(self.y) + self.g.convex_conjugate(-1*self.operator.adjoint(self.y))) -- cgit v1.2.3 From c39df4e997039c61f8a3bb883bf135d88db2498e Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 26 Apr 2019 11:17:32 +0100 Subject: demo updated --- Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py b/Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py index 70ca57a..9de48a5 100644 --- a/Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py +++ b/Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py @@ -21,7 +21,8 @@ from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ MixedL21Norm, BlockFunction, ScaledFunction #from ccpi.astra.ops import AstraProjectorSimple -from ccpi.plugins.ops import CCPiProjectorSimple +#from ccpi.plugins.ops import CCPiProjectorSimple +from ccpi.plugins.operators import CCPiProjectorSimple #from skimage.util import random_noise from timeit import default_timer as timer @@ -90,8 +91,8 @@ ag = AcquisitionGeometry('parallel', det_w) from ccpi.reconstruction.parallelbeam import alg as pbalg -#from ccpi.plugins.processors import setupCCPiGeometries -def setupCCPiGeometries(ig, ag, counter): +from ccpi.plugins.processors import setupCCPiGeometries +def ssetupCCPiGeometries(ig, ag, counter): #vg = ImageGeometry(voxel_num_x=voxel_num_x,voxel_num_y=voxel_num_y, voxel_num_z=voxel_num_z) #Phantom_ccpi = ImageData(geometry=vg, @@ -221,16 +222,16 @@ normK = operator.norm() # Primal & dual stepsizes sigma = 1 tau = 1/(sigma*normK**2) -niter = 3 +niter = 50 opt = {'niter':niter} opt1 = {'niter':niter, 'memopt': True} -pdhg1 = PDHG(f=f,g=g, operator=operator, tau=tau, sigma=sigma) -pdhg1.max_iteration = 2000 -pdhg1.update_objective_interval = 10 -pdhg2 = PDHG(f=f,g=g, operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg1 = PDHG(f=f,g=g, operator=operator, tau=tau, sigma=sigma, memopt=True, max_iteration=niter) +#pdhg1.max_iteration = 2000 +pdhg1.update_objective_interval = 100 +pdhg2 = PDHG(f=f,g=g, operator=operator, tau=tau, sigma=sigma, memopt=False) pdhg2.max_iteration = 2000 pdhg2.update_objective_interval = 100 @@ -238,12 +239,14 @@ t1_old = timer() resold, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) t2_old = timer() +print ("memopt = False, shouldn't matter") pdhg1.run(niter) print (sum(pdhg1.timing)) res = pdhg1.get_output().subset(vertical=0) - +print (pdhg1.objective) t3 = timer() #res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) +print ("memopt = True, shouldn't matter") pdhg2.run(niter) print (sum(pdhg2.timing)) res1 = pdhg2.get_output().subset(vertical=0) -- cgit v1.2.3 From 3a2fcc7e379f4761bad029dd99edf7d806526a10 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 26 Apr 2019 13:48:00 +0100 Subject: removed matplotlib dependence --- Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py index c0b774d..39b092b 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py @@ -13,7 +13,6 @@ import time from ccpi.optimisation.operators import BlockOperator from ccpi.framework import BlockDataContainer from ccpi.optimisation.functions import FunctionOperatorComposition -import matplotlib.pyplot as plt class PDHG(Algorithm): '''Primal Dual Hybrid Gradient''' -- cgit v1.2.3 From 5ae13b1d55da87f4c3f3908fc91ec2424daaf4b3 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 09:43:18 +0100 Subject: changes to PD aglo --- .../ccpi/optimisation/algorithms/Algorithm.py | 22 ++-- .../Python/wip/Demos/PDHG_TV_Denoising_Poisson.py | 127 ++++++++++----------- 2 files changed, 76 insertions(+), 73 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py index 47376a5..12cbabc 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py @@ -146,12 +146,18 @@ class Algorithm(object): print ("Stop cryterion has been reached.") i = 0 - print("Iteration {:<5} Primal {:<5} Dual {:<5} PDgap".format('','','')) +# print("Iteration {:<5} Primal {:<5} Dual {:<5} PDgap".format('','','')) for _ in self: - + if self.iteration % self.update_objective_interval == 0: - if verbose: + + if callback is not None: + callback(self.iteration, self.get_last_objective(), self.x) + + else: + + if verbose: # if verbose and self.iteration % self.update_objective_interval == 0: #pass @@ -163,16 +169,16 @@ class Algorithm(object): # self.get_last_objective()[2])) - print ("Iteration {}/{}, {}".format(self.iteration, - self.max_iteration, self.get_last_objective()) ) + print ("Iteration {}/{}, {}".format(self.iteration, + self.max_iteration, self.get_last_objective()) ) #print ("Iteration {}/{}, Primal, Dual, PDgap = {}".format(self.iteration, # self.max_iteration, self.get_last_objective()) ) - else: - if callback is not None: - callback(self.iteration, self.get_last_objective(), self.x) +# else: +# if callback is not None: +# callback(self.iteration, self.get_last_objective(), self.x) i += 1 if i == iterations: break diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py index 32ab62d..ccdabb2 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py @@ -88,7 +88,7 @@ pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) pdhg.max_iteration = 2000 pdhg.update_objective_interval = 50 -def pdgap_print(niter, objective, solution): +def pdgap_objectives(niter, objective, solution): print( "{:04}/{:04} {:<5} {:.4f} {:<5} {:.4f} {:<5} {:.4f}".\ @@ -97,9 +97,7 @@ def pdgap_print(niter, objective, solution): objective[1],'',\ objective[2])) -#pdhg.run(2000) - -pdhg.run(2000, callback = pdgap_print) +pdhg.run(2000, callback = pdgap_objectives) plt.figure(figsize=(15,15)) @@ -124,66 +122,65 @@ plt.title('Middle Line Profiles') plt.show() -##%% Check with CVX solution #%% Check with CVX solution -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u1 = Variable(ig.shape) - q = Variable() - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) - - fidelity = sum( u1 - multiply(noisy_data.as_array(), log(u1)) ) - constraints = [q>= fidelity, u1>=0] - - solver = ECOS - obj = Minimize( regulariser + q) - prob = Problem(obj, constraints) - result = prob.solve(verbose = True, solver = solver) - - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u1.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u1.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - +#from ccpi.optimisation.operators import SparseFiniteDiff +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +# +#if cvx_not_installable: +# +# ##Construct problem +# u1 = Variable(ig.shape) +# q = Variable() +# +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# # Define Total Variation as a regulariser +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) +# +# fidelity = sum( u1 - multiply(noisy_data.as_array(), log(u1)) ) +# constraints = [q>= fidelity, u1>=0] +# +# solver = ECOS +# obj = Minimize( regulariser + q) +# prob = Problem(obj, constraints) +# result = prob.solve(verbose = True, solver = solver) +# +# +# diff_cvx = numpy.abs( pdhg.get_output().as_array() - u1.value ) +# +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(pdhg.get_output().as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# plt.subplot(3,1,2) +# plt.imshow(u1.value) +# plt.title('CVX solution') +# plt.colorbar() +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() +# +# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') +# plt.legend() +# plt.title('Middle Line Profiles') +# plt.show() +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) +# +# +# +# +# -- cgit v1.2.3 From 7203efb28376e42e3c346b00c9c266f8c9febaf0 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 16:17:52 +0100 Subject: Imat Demo --- .../TV_WhiteBeam_reconstruction.py | 164 ++++++++++++++++++ .../Demos/IMAT_Reconstruction/golden_angles.txt | 186 +++++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 Wrappers/Python/wip/Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py create mode 100644 Wrappers/Python/wip/Demos/IMAT_Reconstruction/golden_angles.txt diff --git a/Wrappers/Python/wip/Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py b/Wrappers/Python/wip/Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py new file mode 100644 index 0000000..ef7a9ec --- /dev/null +++ b/Wrappers/Python/wip/Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageGeometry, AcquisitionGeometry, AcquisitionData +from astropy.io import fits +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, L2NormSquared,\ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple + + +# load IMAT file +#filename_sino = '/media/newhd/shared/DataProcessed/IMAT_beamtime_Feb_2019/preprocessed_test_flat/sino/rebin_slice_350/sino_log_rebin_141.fits' +filename_sino = '/media/newhd/shared/DataProcessed/IMAT_beamtime_Feb_2019/preprocessed_test_flat/sino/rebin_slice_350/sino_log_rebin_564.fits' + +sino_handler = fits.open(filename_sino) +sino_tmp = numpy.array(sino_handler[0].data, dtype=float) +# reorder sino coordinate: channels, angles, detectors +sinogram = numpy.rollaxis(sino_tmp, 2) +sino_handler.close() +#%% +# white beam data +sinogram_wb = sinogram.sum(axis=0) + +pixh = sinogram_wb.shape[1] # detectors +pixv = sinogram_wb.shape[1] # detectors + +# WhiteBeam Geometry +igWB = ImageGeometry(voxel_num_x = pixh, voxel_num_y = pixv) + +# Load Golden angles +with open("/media/newhd/vaggelis/CCPi/CCPi-Block/CCPi-Framework/Wrappers/Python/wip/Demos/IMAT_Reconstruction/golden_angles.txt") as f: + angles_string = [line.rstrip() for line in f] + angles = numpy.array(angles_string).astype(float) +agWB = AcquisitionGeometry('parallel', '2D', angles * numpy.pi / 180, pixh) +op_WB = AstraProjectorSimple(igWB, agWB, 'gpu') +sinogram_aqdata = AcquisitionData(sinogram_wb, agWB) + +# BackProjection +result_bp = op_WB.adjoint(sinogram_aqdata) + +plt.imshow(result_bp.subset(channel=50).array) +plt.title('BackProjection') +plt.show() + + + +#%% + +# Regularisation Parameter +alpha = 10 + +# Create operators +op1 = Gradient(igWB) +op2 = op_WB + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = KullbackLeibler(sinogram_aqdata) +#f2 = L2NormSquared(b = sinogram_aqdata) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +diag_precon = True + +if diag_precon: + + def tau_sigma_precond(operator): + + tau = 1/operator.sum_abs_row() + sigma = 1/ operator.sum_abs_col() + + return tau, sigma + + tau, sigma = tau_sigma_precond(operator) + +else: + # Compute operator Norm + normK = operator.norm() + print ("normK", normK) + # Primal & dual stepsizes + sigma = 0.1 + tau = 1/(sigma*normK**2) + +#%% + + +## Primal & dual stepsizes +sigma = 0.1 +tau = 1/(sigma*normK**2) +# +# +## Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 10000 +pdhg.update_objective_interval = 500 + +def circ_mask(h, w, center=None, radius=None): + + if center is None: # use the middle of the image + center = [int(w/2), int(h/2)] + if radius is None: # use the smallest distance between the center and image walls + radius = min(center[0], center[1], w-center[0], h-center[1]) + + Y, X = numpy.ogrid[:h, :w] + dist_from_center = numpy.sqrt((X - center[0])**2 + (Y-center[1])**2) + + mask = dist_from_center <= radius + return mask + +def show_result(niter, objective, solution): + + mask = circ_mask(pixh, pixv, center=None, radius = 220) # 55 with 141, + plt.imshow(solution.as_array() * mask) + plt.colorbar() + plt.title("Iter: {}".format(niter)) + plt.show() + + + print( "{:04}/{:04} {:<5} {:.4f} {:<5} {:.4f} {:<5} {:.4f}".\ + format(niter, pdhg.max_iteration,'', \ + objective[0],'',\ + objective[1],'',\ + objective[2])) + +pdhg.run(10000, callback = show_result) + +#%% + +mask = circ_mask(pixh, pixv, center=None, radius = 210) # 55 with 141, +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(pdhg.get_output().as_array() * mask) +plt.title('Ground Truth') +plt.colorbar() +plt.show() diff --git a/Wrappers/Python/wip/Demos/IMAT_Reconstruction/golden_angles.txt b/Wrappers/Python/wip/Demos/IMAT_Reconstruction/golden_angles.txt new file mode 100644 index 0000000..95ce73a --- /dev/null +++ b/Wrappers/Python/wip/Demos/IMAT_Reconstruction/golden_angles.txt @@ -0,0 +1,186 @@ +0 +0.9045 +1.809 +2.368 +3.2725 +4.736 +5.6405 +6.1995 +7.104 +8.5675 +9.472 +10.9356 +11.8401 +12.3991 +13.3036 +14.7671 +15.6716 +16.2306 +17.1351 +18.0396 +18.5986 +19.5031 +20.9666 +21.8711 +22.4301 +23.3346 +24.7981 +25.7026 +27.1661 +28.0706 +28.6297 +29.5342 +30.9977 +31.9022 +32.4612 +33.3657 +34.8292 +35.7337 +37.1972 +38.1017 +38.6607 +39.5652 +41.0287 +41.9332 +42.4922 +43.3967 +44.3012 +44.8602 +45.7647 +47.2283 +48.1328 +48.6918 +49.5963 +51.0598 +51.9643 +53.4278 +54.3323 +54.8913 +55.7958 +57.2593 +58.1638 +58.7228 +59.6273 +60.5318 +61.0908 +61.9953 +63.4588 +64.3633 +64.9224 +65.8269 +67.2904 +68.1949 +69.6584 +70.5629 +71.1219 +72.0264 +73.4899 +74.3944 +74.9534 +75.8579 +77.3214 +78.2259 +79.6894 +80.5939 +81.1529 +82.0574 +83.521 +84.4255 +84.9845 +85.889 +86.7935 +87.3525 +88.257 +89.7205 +90.625 +91.184 +92.0885 +93.552 +94.4565 +95.92 +96.8245 +97.3835 +98.288 +99.7516 +100.656 +101.215 +102.12 +103.583 +104.488 +105.951 +106.856 +107.415 +108.319 +109.783 +110.687 +111.246 +112.151 +113.055 +113.614 +114.519 +115.982 +116.887 +117.446 +118.35 +119.814 +120.718 +122.182 +123.086 +123.645 +124.55 +126.013 +126.918 +127.477 +128.381 +129.286 +129.845 +130.749 +132.213 +133.117 +133.676 +134.581 +136.044 +136.949 +138.412 +139.317 +139.876 +140.78 +142.244 +143.148 +143.707 +144.612 +146.075 +146.98 +148.443 +149.348 +149.907 +150.811 +152.275 +153.179 +153.738 +154.643 +155.547 +156.106 +157.011 +158.474 +159.379 +159.938 +160.842 +162.306 +163.21 +164.674 +165.578 +166.137 +167.042 +168.505 +169.41 +169.969 +170.873 +172.337 +173.242 +174.705 +175.609 +176.168 +177.073 +178.536 +179.441 -- cgit v1.2.3 From 4bacf27deec2abe993750ca7e1c873fd7aece8cd Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 16:18:38 +0100 Subject: fix FISTA and FunctionOperatorComposition --- .../Python/ccpi/optimisation/algorithms/FISTA.py | 8 +- .../functions/FunctionOperatorComposition.py | 87 ++++++++++++---------- 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py index 8ea2b6c..ee51049 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py @@ -65,10 +65,10 @@ class FISTA(Algorithm): # initialization if memopt: - self.y = x_init.clone() - self.x_old = x_init.clone() - self.x = x_init.clone() - self.u = x_init.clone() + self.y = x_init.copy() + self.x_old = x_init.copy() + self.x = x_init.copy() + self.u = x_init.copy() else: self.x_old = x_init.copy() self.y = x_init.copy() diff --git a/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py b/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py index 70511bb..8895f3d 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py +++ b/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py @@ -19,16 +19,13 @@ class FunctionOperatorComposition(Function): ''' - def __init__(self, operator, function): + def __init__(self, function, operator): super(FunctionOperatorComposition, self).__init__() + self.function = function self.operator = operator - alpha = 1 - - if isinstance (function, ScaledFunction): - alpha = function.scalar - self.L = 2 * alpha * operator.norm()**2 + self.L = function.L * operator.norm()**2 def __call__(self, x): @@ -39,47 +36,57 @@ class FunctionOperatorComposition(Function): ''' - return self.function(self.operator.direct(x)) + return self.function(self.operator.direct(x)) + + def gradient(self, x, out=None): +# + ''' Gradient takes into account the Operator''' + if out is None: + return self.operator.adjoint(self.function.gradient(self.operator.direct(x))) + else: + tmp = self.operator.range_geometry().allocate() + self.operator.direct(x, out=tmp) + self.function.gradient(tmp, out=tmp) + self.operator.adjoint(tmp, out=out) - #TODO do not know if we need it - def call_adjoint(self, x): - return self.function(self.operator.adjoint(x)) + + #TODO do not know if we need it + #def call_adjoint(self, x): + # + # return self.function(self.operator.adjoint(x)) - def convex_conjugate(self, x): - - ''' convex_conjugate does not take into account the Operator''' - return self.function.convex_conjugate(x) - def proximal(self, x, tau, out=None): - - '''proximal does not take into account the Operator''' - if out is None: - return self.function.proximal(x, tau) - else: - self.function.proximal(x, tau, out=out) - + #def convex_conjugate(self, x): + # + # ''' convex_conjugate does not take into account the Operator''' + # return self.function.convex_conjugate(x) - def proximal_conjugate(self, x, tau, out=None): + - ''' proximal conjugate does not take into account the Operator''' - if out is None: - return self.function.proximal_conjugate(x, tau) - else: - self.function.proximal_conjugate(x, tau, out=out) - def gradient(self, x, out=None): - - ''' Gradient takes into account the Operator''' - if out is None: - return self.operator.adjoint( - self.function.gradient(self.operator.direct(x)) - ) - else: - self.operator.adjoint( - self.function.gradient(self.operator.direct(x), - out=out) - ) + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + from ccpi.optimisation.operators import Gradient + from ccpi.optimisation.functions import L2NormSquared + + M, N, K = 2,3 + ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N) + + G = Gradient(ig) + alpha = 0.5 + + f = L2NormSquared() + f_comp = FunctionOperatorComposition(G, alpha * f) + x = ig.allocate('random_int') + print(f_comp.gradient(x).shape + + ) + + + \ No newline at end of file -- cgit v1.2.3 From d89b81276f492d747bdbda71856e11c0453fce23 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 16:19:09 +0100 Subject: fix gradient out --- Wrappers/Python/ccpi/optimisation/functions/ScaledFunction.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/ScaledFunction.py b/Wrappers/Python/ccpi/optimisation/functions/ScaledFunction.py index 7caeab2..1db223b 100755 --- a/Wrappers/Python/ccpi/optimisation/functions/ScaledFunction.py +++ b/Wrappers/Python/ccpi/optimisation/functions/ScaledFunction.py @@ -59,7 +59,8 @@ class ScaledFunction(object): if out is None: return self.scalar * self.function.gradient(x) else: - out.fill( self.scalar * self.function.gradient(x) ) + self.function.gradient(x, out=out) + out *= self.scalar def proximal(self, x, tau, out=None): '''This returns the proximal operator for the function at x, tau -- cgit v1.2.3 From a1a3483b5ab328a54815d6bc8066c0c49af91655 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 16:21:16 +0100 Subject: test precond --- .../functions/FunctionOperatorComposition_old.py | 85 ++++++++++++++ Wrappers/Python/wip/Demos/fista_test.py | 127 +++++++++++++++++++++ Wrappers/Python/wip/pdhg_TV_tomography2D.py | 2 +- 3 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition_old.py create mode 100644 Wrappers/Python/wip/Demos/fista_test.py diff --git a/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition_old.py b/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition_old.py new file mode 100644 index 0000000..70511bb --- /dev/null +++ b/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition_old.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 8 09:55:36 2019 + +@author: evangelos +""" + +from ccpi.optimisation.functions import Function +from ccpi.optimisation.functions import ScaledFunction + + +class FunctionOperatorComposition(Function): + + ''' Function composition with Operator, i.e., f(Ax) + + A: operator + f: function + + ''' + + def __init__(self, operator, function): + + super(FunctionOperatorComposition, self).__init__() + self.function = function + self.operator = operator + alpha = 1 + + if isinstance (function, ScaledFunction): + alpha = function.scalar + self.L = 2 * alpha * operator.norm()**2 + + + def __call__(self, x): + + ''' Evaluate FunctionOperatorComposition at x + + returns f(Ax) + + ''' + + return self.function(self.operator.direct(x)) + + #TODO do not know if we need it + def call_adjoint(self, x): + + return self.function(self.operator.adjoint(x)) + + + def convex_conjugate(self, x): + + ''' convex_conjugate does not take into account the Operator''' + return self.function.convex_conjugate(x) + + def proximal(self, x, tau, out=None): + + '''proximal does not take into account the Operator''' + if out is None: + return self.function.proximal(x, tau) + else: + self.function.proximal(x, tau, out=out) + + + def proximal_conjugate(self, x, tau, out=None): + + ''' proximal conjugate does not take into account the Operator''' + if out is None: + return self.function.proximal_conjugate(x, tau) + else: + self.function.proximal_conjugate(x, tau, out=out) + + def gradient(self, x, out=None): + + ''' Gradient takes into account the Operator''' + if out is None: + return self.operator.adjoint( + self.function.gradient(self.operator.direct(x)) + ) + else: + self.operator.adjoint( + self.function.gradient(self.operator.direct(x), + out=out) + ) + + \ No newline at end of file diff --git a/Wrappers/Python/wip/Demos/fista_test.py b/Wrappers/Python/wip/Demos/fista_test.py new file mode 100644 index 0000000..dd1f6fa --- /dev/null +++ b/Wrappers/Python/wip/Demos/fista_test.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import FISTA, PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient, Identity +from ccpi.optimisation.functions import L2NormSquared, L1Norm, \ + MixedL21Norm, FunctionOperatorComposition, BlockFunction, ZeroFunction + +from skimage.util import random_noise + +# Create phantom for TV Gaussian denoising +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Add Gaussian noise +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Regularisation Parameter +alpha = 5 + +operator = Gradient(ig) + +#fidelity = L1Norm(b=noisy_data) +#regulariser = FunctionOperatorComposition(alpha * L2NormSquared(), operator) + +fidelity = FunctionOperatorComposition(alpha * MixedL21Norm(), operator) +regulariser = 0.5 * L2NormSquared(b = noisy_data) + +x_init = ig.allocate() + +## Setup and run the PDHG algorithm +opt = {'tol': 1e-4, 'memopt':True} +fista = FISTA(x_init=x_init , f=regulariser, g=fidelity, opt=opt) +fista.max_iteration = 2000 +fista.update_objective_interval = 50 +fista.run(2000, verbose=True) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(fista.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() + +# Compare with PDHG +method = '0' +# +if method == '0': +# +# # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) +# +# # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + f = BlockFunction(alpha * L2NormSquared(), fidelity) + g = ZeroFunction() + +## Compute operator Norm +normK = operator.norm() +# +## Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +# +# +## Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) +# +#%% +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(fista.get_output().as_array()) +plt.title('FISTA') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(pdhg.get_output().as_array()) +plt.title('PDHG') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(np.abs(pdhg.get_output().as_array()-fista.get_output().as_array())) +plt.title('Diff FISTA-PDHG') +plt.colorbar() +plt.show() + + diff --git a/Wrappers/Python/wip/pdhg_TV_tomography2D.py b/Wrappers/Python/wip/pdhg_TV_tomography2D.py index cd91409..b04a609 100644 --- a/Wrappers/Python/wip/pdhg_TV_tomography2D.py +++ b/Wrappers/Python/wip/pdhg_TV_tomography2D.py @@ -91,7 +91,7 @@ g = ZeroFunction() normK = operator.norm() ## Primal & dual stepsizes -diag_precon = False +diag_precon = True if diag_precon: -- cgit v1.2.3 From 81f6aae31853cc431d439e55b7cb345e384d6400 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 16:21:55 +0100 Subject: minor fix --- .../Python/ccpi/optimisation/functions/L2NormSquared.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py index 6cb3d8a..df38f15 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py +++ b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py @@ -19,6 +19,7 @@ from ccpi.optimisation.functions import Function from ccpi.optimisation.functions.ScaledFunction import ScaledFunction +from ccpi.optimisation.functions import FunctionOperatorComposition class L2NormSquared(Function): @@ -95,20 +96,10 @@ class L2NormSquared(Function): else: tmp = x.subtract(self.b) -# tmp -= self.b tmp /= (1+2*tau) tmp += self.b return tmp -# return (x-self.b)/(1+2*tau) + self.b - -# if self.b is not None: -# out=x -# if self.b is not None: -# out -= self.b -# out /= (1+2*tau) -# if self.b is not None: -# out += self.b -# return out + else: out.fill(x) if self.b is not None: @@ -149,9 +140,9 @@ class L2NormSquared(Function): return ScaledFunction(self, scalar) - def operator_composition(self, operator): + def composition(self, operator): - return FunctionOperatorComposition + return FunctionOperatorComposition(operator) -- cgit v1.2.3 From 17f3d80556b9a234bccca2a764fe31b83b6f5744 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 16:23:52 +0100 Subject: fix demo --- Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py index ccdabb2..a54e5ee 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py @@ -48,7 +48,7 @@ noisy_data = ImageData(n1) # Regularisation Parameter alpha = 2 -method = '1' +method = '0' if method == '0': -- cgit v1.2.3 From f49f9835c03aae6a3abc4c35962f12e5d10b0ad2 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 16:25:22 +0100 Subject: fix KL funcs --- Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index 14b5ea0..dc0411e 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -63,6 +63,7 @@ class KullbackLeibler(Function): return 1 - self.b/(x + self.bnoise) else: x.add(self.bnoise, out=out) + self.b.divide(out, out=out) out.subtract(1, out=out) out *= -1 -- cgit v1.2.3 From 892002e03206a422a4ea89863939ee806be20c24 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 16:31:36 +0100 Subject: fix KL out --- .../ccpi/optimisation/functions/KullbackLeibler.py | 23 +++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index dc0411e..51a008f 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -63,7 +63,6 @@ class KullbackLeibler(Function): return 1 - self.b/(x + self.bnoise) else: x.add(self.bnoise, out=out) - self.b.divide(out, out=out) out.subtract(1, out=out) out *= -1 @@ -106,16 +105,18 @@ class KullbackLeibler(Function): z = x + tau * self.bnoise return 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) else: - z_m = x + tau * self.bnoise -1 - self.b.multiply(4*tau, out=out) - z_m.multiply(z_m, out=z_m) - out += z_m - out.sqrt(out=out) - z_m.sqrt(out=z_m) - z_m += 2 - out *= -1 - out += z_m - out *= 0.5 + z = x + tau * self.bnoise + out.fill( 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) ) + #z_m = x + tau * self.bnoise -1 + #self.b.multiply(4*tau, out=out) + #z_m.multiply(z_m, out=z_m) + #out += z_m + #out.sqrt(out=out) + #z_m.sqrt(out=z_m) + #z_m += 2 + #out *= -1 + #out += z_m + #out *= 0.5 -- cgit v1.2.3 From 58c3977744d09a4a8f72125902470098a10a75b4 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 16:37:10 +0100 Subject: check demos --- .../Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py | 6 +- .../Python/wip/Demos/PDHG_TV_Denoising_Poisson.py | 122 ++++++++++----------- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py index 1a3e0df..39bbb2c 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py @@ -32,7 +32,7 @@ from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ from skimage.util import random_noise # Create phantom for TV Gaussian denoising -N = 300 +N = 100 data = np.zeros((N,N)) data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 @@ -46,9 +46,9 @@ n1 = random_noise(data.as_array(), mode = 'gaussian', mean=0, var = 0.05, seed=1 noisy_data = ImageData(n1) # Regularisation Parameter -alpha = 0.5 +alpha = 2 -method = '0' +method = '1' if method == '0': diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py index a54e5ee..4903c44 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py @@ -48,7 +48,7 @@ noisy_data = ImageData(n1) # Regularisation Parameter alpha = 2 -method = '0' +method = '1' if method == '0': @@ -124,63 +124,63 @@ plt.show() #%% Check with CVX solution -#from ccpi.optimisation.operators import SparseFiniteDiff -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -# -#if cvx_not_installable: -# -# ##Construct problem -# u1 = Variable(ig.shape) -# q = Variable() -# -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# # Define Total Variation as a regulariser -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) -# -# fidelity = sum( u1 - multiply(noisy_data.as_array(), log(u1)) ) -# constraints = [q>= fidelity, u1>=0] -# -# solver = ECOS -# obj = Minimize( regulariser + q) -# prob = Problem(obj, constraints) -# result = prob.solve(verbose = True, solver = solver) -# -# -# diff_cvx = numpy.abs( pdhg.get_output().as_array() - u1.value ) -# -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(pdhg.get_output().as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# plt.subplot(3,1,2) -# plt.imshow(u1.value) -# plt.title('CVX solution') -# plt.colorbar() -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') -# plt.legend() -# plt.title('Middle Line Profiles') -# plt.show() -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) -# -# -# -# -# +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u1 = Variable(ig.shape) + q = Variable() + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) + + fidelity = sum( u1 - multiply(noisy_data.as_array(), log(u1)) ) + constraints = [q>= fidelity, u1>=0] + + solver = ECOS + obj = Minimize( regulariser + q) + prob = Problem(obj, constraints) + result = prob.solve(verbose = True, solver = solver) + + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u1.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u1.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + -- cgit v1.2.3 From d6dff2e80d5364450a6ca47a6f7aca66aa2ea289 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 16:53:24 +0100 Subject: fix KL funcs --- .../ccpi/optimisation/functions/KullbackLeibler.py | 27 ++++++++++++---------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index 51a008f..c5661b0 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -105,18 +105,21 @@ class KullbackLeibler(Function): z = x + tau * self.bnoise return 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) else: - z = x + tau * self.bnoise - out.fill( 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) ) - #z_m = x + tau * self.bnoise -1 - #self.b.multiply(4*tau, out=out) - #z_m.multiply(z_m, out=z_m) - #out += z_m - #out.sqrt(out=out) - #z_m.sqrt(out=z_m) - #z_m += 2 - #out *= -1 - #out += z_m - #out *= 0.5 +# z = x + tau * self.bnoise +# out.fill( 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) ) + + + tmp1 = x + tau * self.bnoise - 1 + tmp2 = tmp1 + 2 + + self.b.multiply(4*tau, out=out) + tmp1.multiply(tmp1, out=tmp1) + out += tmp1 + out.sqrt(out=out) + + out *= -1 + out += tmp2 + out *= 0.5 -- cgit v1.2.3 From 2eb83e4389c0a251aa218b200f1ccfb20e31b84a Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 16:56:19 +0100 Subject: fix demos --- Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py | 2 +- Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py index a54e5ee..ccdabb2 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py @@ -48,7 +48,7 @@ noisy_data = ImageData(n1) # Regularisation Parameter alpha = 2 -method = '0' +method = '1' if method == '0': diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py index 484fe42..4189acb 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py @@ -48,7 +48,7 @@ noisy_data = ImageData(n1) # Regularisation Parameter alpha = 2 -method = '1' +method = '0' if method == '0': -- cgit v1.2.3 From 05597b4428d85f5bf9a01f538f87ea5307740b54 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 16:58:47 +0100 Subject: fix out --- Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index c5661b0..b53f669 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -107,8 +107,7 @@ class KullbackLeibler(Function): else: # z = x + tau * self.bnoise # out.fill( 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) ) - - + tmp1 = x + tau * self.bnoise - 1 tmp2 = tmp1 + 2 -- cgit v1.2.3 From 98fe6b31fff9dcb212e83b96d0453743c6e4edd5 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 29 Apr 2019 17:35:54 +0100 Subject: minor fix --- Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py index df38f15..e73da93 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py +++ b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py @@ -106,7 +106,9 @@ class L2NormSquared(Function): out -= self.b out /= (1+2*tau) if self.b is not None: - out += self.b + out += self.b + + def proximal_conjugate(self, x, tau, out=None): -- cgit v1.2.3 From 5cea311e7364550943b10a0ff04bb11f6fc5c0e1 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 30 Apr 2019 11:13:46 +0100 Subject: try fix conda --- Wrappers/Python/build/lib/ccpi/contrib/__init__.py | 0 .../lib/ccpi/contrib/optimisation/__init__.py | 0 .../contrib/optimisation/algorithms/__init__.py | 0 .../ccpi/contrib/optimisation/algorithms/spdhg.py | 338 +++++++++++++++++++++ .../build/lib/ccpi/framework/BlockDataContainer.py | 13 +- .../build/lib/ccpi/framework/BlockGeometry.py | 45 ++- .../lib/ccpi/optimisation/algorithms/Algorithm.py | 37 ++- .../lib/ccpi/optimisation/algorithms/FISTA.py | 8 +- .../build/lib/ccpi/optimisation/algorithms/PDHG.py | 107 ++++--- .../Python/build/lib/ccpi/optimisation/funcs.py | 7 - .../ccpi/optimisation/functions/BlockFunction.py | 28 +- .../functions/FunctionOperatorComposition.py | 87 +++--- .../functions/FunctionOperatorComposition_old.py | 85 ++++++ .../ccpi/optimisation/functions/KullbackLeibler.py | 79 +++-- .../ccpi/optimisation/functions/L2NormSquared.py | 32 +- .../ccpi/optimisation/functions/MixedL21Norm.py | 60 ++-- .../ccpi/optimisation/functions/ScaledFunction.py | 3 +- .../ccpi/optimisation/functions/ZeroFunction.py | 8 +- .../ccpi/optimisation/operators/BlockOperator.py | 65 +++- .../operators/FiniteDifferenceOperator.py | 5 +- .../optimisation/operators/GradientOperator.py | 3 +- .../ccpi/optimisation/operators/LinearOperator.py | 45 +++ .../optimisation/operators/LinearOperatorMatrix.py | 51 ++++ .../operators/SymmetrizedGradientOperator.py | 274 ++++++++++------- .../ccpi/optimisation/operators/ZeroOperator.py | 27 +- .../lib/ccpi/optimisation/operators/__init__.py | 4 +- .../TV_WhiteBeam_reconstruction.py | 4 +- .../Python/wip/Demos/PDHG_TV_Denoising_Poisson.py | 2 +- .../wip/Demos/PDHG_TV_Denoising_SaltPepper.py | 2 +- Wrappers/Python/wip/test_symGrad_method1.py | 178 ----------- 30 files changed, 1085 insertions(+), 512 deletions(-) create mode 100644 Wrappers/Python/build/lib/ccpi/contrib/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/contrib/optimisation/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/spdhg.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition_old.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperatorMatrix.py delete mode 100644 Wrappers/Python/wip/test_symGrad_method1.py diff --git a/Wrappers/Python/build/lib/ccpi/contrib/__init__.py b/Wrappers/Python/build/lib/ccpi/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Wrappers/Python/build/lib/ccpi/contrib/optimisation/__init__.py b/Wrappers/Python/build/lib/ccpi/contrib/optimisation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/__init__.py b/Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/spdhg.py b/Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/spdhg.py new file mode 100644 index 0000000..263a7cd --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/spdhg.py @@ -0,0 +1,338 @@ +# Copyright 2018 Matthias Ehrhardt, Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy + +from ccpi.optimisation.funcs import Function +from ccpi.framework import ImageData +from ccpi.framework import AcquisitionData + + +class spdhg(): + """Computes a saddle point with a stochastic PDHG. + + This means, a solution (x*, y*), y* = (y*_1, ..., y*_n) such that + + (x*, y*) in arg min_x max_y sum_i=1^n - f*[i](y_i) + g(x) + + where g : X -> IR_infty and f[i] : Y[i] -> IR_infty are convex, l.s.c. and + proper functionals. For this algorithm, they all may be non-smooth and no + strong convexity is assumed. + + Parameters + ---------- + f : list of functions + Functionals Y[i] -> IR_infty that all have a convex conjugate with a + proximal operator, i.e. + f[i].convex_conj.prox(sigma[i]) : Y[i] -> Y[i]. + g : function + Functional X -> IR_infty that has a proximal operator, i.e. + g.prox(tau) : X -> X. + A : list of functions + Operators A[i] : X -> Y[i] that possess adjoints: A[i].adjoint + x : primal variable, optional + By default equals 0. + y : dual variable, optional + Part of a product space. By default equals 0. + z : variable, optional + Adjoint of dual variable, z = A^* y. By default equals 0 if y = 0. + tau : scalar / vector / matrix, optional + Step size for primal variable. Note that the proximal operator of g + has to be well-defined for this input. + sigma : scalar, optional + Scalar / vector / matrix used as step size for dual variable. Note that + the proximal operator related to f (see above) has to be well-defined + for this input. + prob : list of scalars, optional + Probabilities prob[i] that a subset i is selected in each iteration. + If fun_select is not given, then the sum of all probabilities must + equal 1. + A_norms : list of scalars, optional + Norms of the operators in A. Can be used to determine the step sizes + tau and sigma and the probabilities prob. + fun_select : function, optional + Function that selects blocks at every iteration IN -> {1,...,n}. By + default this is serial sampling, fun_select(k) selects an index + i \in {1,...,n} with probability prob[i]. + + References + ---------- + [CERS2018] A. Chambolle, M. J. Ehrhardt, P. Richtarik and C.-B. Schoenlieb, + *Stochastic Primal-Dual Hybrid Gradient Algorithm with Arbitrary Sampling + and Imaging Applications*. SIAM Journal on Optimization, 28(4), 2783-2808 + (2018) http://doi.org/10.1007/s10851-010-0251-1 + + [E+2017] M. J. Ehrhardt, P. J. Markiewicz, P. Richtarik, J. Schott, + A. Chambolle and C.-B. Schoenlieb, *Faster PET reconstruction with a + stochastic primal-dual hybrid gradient method*. Wavelets and Sparsity XVII, + 58 (2017) http://doi.org/10.1117/12.2272946. + + [EMS2018] M. J. Ehrhardt, P. J. Markiewicz and C.-B. Schoenlieb, *Faster + PET Reconstruction with Non-Smooth Priors by Randomization and + Preconditioning*. (2018) ArXiv: http://arxiv.org/abs/1808.07150 + """ + + def __init__(self, f, g, A, x=None, y=None, z=None, tau=None, sigma=None, + prob=None, A_norms=None, fun_select=None): + # fun_select is optional and by default performs serial sampling + + if x is None: + x = A[0].allocate_direct(0) + + if y is None: + if z is not None: + raise ValueError('y and z have to be defaulted together') + + y = [Ai.allocate_adjoint(0) for Ai in A] + z = 0 * x.copy() + + else: + if z is None: + raise ValueError('y and z have to be defaulted together') + + if A_norms is not None: + if tau is not None or sigma is not None or prob is not None: + raise ValueError('Either A_norms or (tau, sigma, prob) must ' + 'be given') + + tau = 1 / sum(A_norms) + sigma = [1 / nA for nA in A_norms] + prob = [nA / sum(A_norms) for nA in A_norms] + + #uniform prob, needs different sigma and tau + #n = len(A) + #prob = [1./n] * n + + if fun_select is None: + if prob is None: + raise ValueError('prob was not determined') + + def fun_select(k): + return [int(numpy.random.choice(len(A), 1, p=prob))] + + self.iter = 0 + self.x = x + + self.y = y + self.z = z + + self.f = f + self.g = g + self.A = A + self.tau = tau + self.sigma = sigma + self.prob = prob + self.fun_select = fun_select + + # Initialize variables + self.z_relax = z.copy() + self.tmp = self.x.copy() + + def update(self): + # select block + selected = self.fun_select(self.iter) + + # update primal variable + #tmp = (self.x - self.tau * self.z_relax).as_array() + #self.x.fill(self.g.prox(tmp, self.tau)) + self.tmp = - self.tau * self.z_relax + self.tmp += self.x + self.x = self.g.prox(self.tmp, self.tau) + + # update dual variable and z, z_relax + self.z_relax = self.z.copy() + for i in selected: + # save old yi + y_old = self.y[i].copy() + + # y[i]= prox(tmp) + tmp = y_old + self.sigma[i] * self.A[i].direct(self.x) + self.y[i] = self.f[i].convex_conj.prox(tmp, self.sigma[i]) + + # update adjoint of dual variable + dz = self.A[i].adjoint(self.y[i] - y_old) + self.z += dz + + # compute extrapolation + self.z_relax += (1 + 1 / self.prob[i]) * dz + + self.iter += 1 + + +## Functions + +class KullbackLeibler(Function): + def __init__(self, data, background): + self.data = data + self.background = background + self.__offset = None + + def __call__(self, x): + """Return the KL-diveregnce in the point ``x``. + + If any components of ``x`` is non-positive, the value is positive + infinity. + + Needs one extra array of memory of the size of `prior`. + """ + + # define short variable names + y = self.data + r = self.background + + # Compute + # sum(x + r - y + y * log(y / (x + r))) + # = sum(x - y * log(x + r)) + self.offset + # Assume that + # x + r > 0 + + # sum the result up + obj = numpy.sum(x - y * numpy.log(x + r)) + self.offset() + + if numpy.isnan(obj): + # In this case, some element was less than or equal to zero + return numpy.inf + else: + return obj + + @property + def convex_conj(self): + """The convex conjugate functional of the KL-functional.""" + return KullbackLeiblerConvexConjugate(self.data, self.background) + + def offset(self): + """The offset which is independent of the unknown.""" + + if self.__offset is None: + tmp = self.domain.element() + + # define short variable names + y = self.data + r = self.background + + tmp = self.domain.element(numpy.maximum(y, 1)) + tmp = r - y + y * numpy.log(tmp) + + # sum the result up + self.__offset = numpy.sum(tmp) + + return self.__offset + +# def __repr__(self): +# """to be added???""" +# """Return ``repr(self)``.""" + # return '{}({!r}, {!r}, {!r})'.format(self.__class__.__name__, + ## self.domain, self.data, + # self.background) + + +class KullbackLeiblerConvexConjugate(Function): + """The convex conjugate of Kullback-Leibler divergence functional. + + Notes + ----- + The functional :math:`F^*` with prior :math:`g>0` is given by: + + .. math:: + F^*(x) + = + \\begin{cases} + \\sum_{i} \left( -g_i \ln(1 - x_i) \\right) + & \\text{if } x_i < 1 \\forall i + \\\\ + +\\infty & \\text{else} + \\end{cases} + + See Also + -------- + KullbackLeibler : convex conjugate functional + """ + + def __init__(self, data, background): + self.data = data + self.background = background + + def __call__(self, x): + y = self.data + r = self.background + + tmp = numpy.sum(- x * r - y * numpy.log(1 - x)) + + if numpy.isnan(tmp): + # In this case, some element was larger than or equal to one + return numpy.inf + else: + return tmp + + + def prox(self, x, tau, out=None): + # Let y = data, r = background, z = x + tau * r + # Compute 0.5 * (z + 1 - sqrt((z - 1)**2 + 4 * tau * y)) + # Currently it needs 3 extra copies of memory. + + if out is None: + out = x.copy() + + # define short variable names + try: # this should be standard SIRF/CIL mode + y = self.data.as_array() + r = self.background.as_array() + x = x.as_array() + + try: + taua = tau.as_array() + except: + taua = tau + + z = x + tau * r + + out.fill(0.5 * (z + 1 - numpy.sqrt((z - 1) ** 2 + 4 * taua * y))) + + return out + + except: # e.g. for NumPy + y = self.data + r = self.background + + try: + taua = tau.as_array() + except: + taua = tau + + z = x + tau * r + + out[:] = 0.5 * (z + 1 - numpy.sqrt((z - 1) ** 2 + 4 * taua * y)) + + return out + + @property + def convex_conj(self): + return KullbackLeibler(self.data, self.background) + + +def mult(x, y): + try: + xa = x.as_array() + except: + xa = x + + out = y.clone() + out.fill(xa * y.as_array()) + + return out diff --git a/Wrappers/Python/build/lib/ccpi/framework/BlockDataContainer.py b/Wrappers/Python/build/lib/ccpi/framework/BlockDataContainer.py index 386cd87..166014b 100644 --- a/Wrappers/Python/build/lib/ccpi/framework/BlockDataContainer.py +++ b/Wrappers/Python/build/lib/ccpi/framework/BlockDataContainer.py @@ -270,6 +270,7 @@ class BlockDataContainer(object): def squared_norm(self): y = numpy.asarray([el.squared_norm() for el in self.containers]) return y.sum() + def norm(self): return numpy.sqrt(self.squared_norm()) @@ -442,9 +443,12 @@ class BlockDataContainer(object): '''Inline truedivision''' return self.__idiv__(other) + def dot(self, other): +# + tmp = [ self.containers[i].dot(other.containers[i]) for i in range(self.shape[0])] + return sum(tmp) - - + if __name__ == '__main__': @@ -456,6 +460,7 @@ if __name__ == '__main__': BG = BlockGeometry(ig, ig) U = BG.allocate('random_int') + V = BG.allocate('random_int') print ("test sum BDC " ) @@ -468,10 +473,10 @@ if __name__ == '__main__': z1 = sum(U**2).sqrt().as_array() numpy.testing.assert_array_equal(z, z1) - - z2 = U.pnorm(2) + zzz = U.dot(V) + diff --git a/Wrappers/Python/build/lib/ccpi/framework/BlockGeometry.py b/Wrappers/Python/build/lib/ccpi/framework/BlockGeometry.py index 5dd6750..ed44d99 100644 --- a/Wrappers/Python/build/lib/ccpi/framework/BlockGeometry.py +++ b/Wrappers/Python/build/lib/ccpi/framework/BlockGeometry.py @@ -31,7 +31,50 @@ class BlockGeometry(object): '''returns the Geometry in the BlockGeometry located at position index''' return self.geometries[index] - def allocate(self, value=0, dimension_labels=None): + def allocate(self, value=0, dimension_labels=None, **kwargs): + + symmetry = kwargs.get('symmetry',False) containers = [geom.allocate(value) for geom in self.geometries] + + if symmetry == True: + + # for 2x2 + # [ ig11, ig12\ + # ig21, ig22] + + # Row-wise Order + + if len(containers)==4: + containers[1]=containers[2] + + # for 3x3 + # [ ig11, ig12, ig13\ + # ig21, ig22, ig23\ + # ig31, ig32, ig33] + + elif len(containers)==9: + containers[1]=containers[3] + containers[2]=containers[6] + containers[5]=containers[7] + + # for 4x4 + # [ ig11, ig12, ig13, ig14\ + # ig21, ig22, ig23, ig24\ + # ig31, ig32, ig33, ig34 + # ig41, ig42, ig43, ig44] + + elif len(containers) == 16: + containers[1]=containers[4] + containers[2]=containers[8] + containers[3]=containers[12] + containers[6]=containers[9] + containers[7]=containers[10] + containers[11]=containers[15] + + + + return BlockDataContainer(*containers) + + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py index ed95c3f..12cbabc 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py @@ -145,13 +145,40 @@ class Algorithm(object): if self.should_stop(): print ("Stop cryterion has been reached.") i = 0 + +# print("Iteration {:<5} Primal {:<5} Dual {:<5} PDgap".format('','','')) for _ in self: - if verbose and self.iteration % self.update_objective_interval == 0: - print ("Iteration {}/{}, objective {}".format(self.iteration, - self.max_iteration, self.get_last_objective()) ) - else: + + + if self.iteration % self.update_objective_interval == 0: + if callback is not None: - callback(self.iteration, self.get_last_objective()) + callback(self.iteration, self.get_last_objective(), self.x) + + else: + + if verbose: + +# if verbose and self.iteration % self.update_objective_interval == 0: + #pass + # \t for tab +# print( "{:04}/{:04} {:<5} {:.4f} {:<5} {:.4f} {:<5} {:.4f}".\ +# format(self.iteration, self.max_iteration,'', \ +# self.get_last_objective()[0],'',\ +# self.get_last_objective()[1],'',\ +# self.get_last_objective()[2])) + + + print ("Iteration {}/{}, {}".format(self.iteration, + self.max_iteration, self.get_last_objective()) ) + + #print ("Iteration {}/{}, Primal, Dual, PDgap = {}".format(self.iteration, + # self.max_iteration, self.get_last_objective()) ) + + +# else: +# if callback is not None: +# callback(self.iteration, self.get_last_objective(), self.x) i += 1 if i == iterations: break diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FISTA.py index 8ea2b6c..ee51049 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FISTA.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FISTA.py @@ -65,10 +65,10 @@ class FISTA(Algorithm): # initialization if memopt: - self.y = x_init.clone() - self.x_old = x_init.clone() - self.x = x_init.clone() - self.u = x_init.clone() + self.y = x_init.copy() + self.x_old = x_init.copy() + self.x = x_init.copy() + self.u = x_init.copy() else: self.x_old = x_init.copy() self.y = x_init.copy() diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py index a41bd48..0f5e8ef 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py @@ -13,6 +13,7 @@ import time from ccpi.optimisation.operators import BlockOperator from ccpi.framework import BlockDataContainer from ccpi.optimisation.functions import FunctionOperatorComposition +import matplotlib.pyplot as plt class PDHG(Algorithm): '''Primal Dual Hybrid Gradient''' @@ -46,34 +47,43 @@ class PDHG(Algorithm): self.y_old = self.operator.range_geometry().allocate() self.xbar = self.x_old.copy() - + self.x_tmp = self.x_old.copy() self.x = self.x_old.copy() - self.y = self.y_old.copy() - if self.memopt: - self.y_tmp = self.y_old.copy() - self.x_tmp = self.x_old.copy() - #y = y_tmp + + self.y_tmp = self.y_old.copy() + self.y = self.y_tmp.copy() + + + + #self.y = self.y_old.copy() + + + #if self.memopt: + # self.y_tmp = self.y_old.copy() + # self.x_tmp = self.x_old.copy() + # relaxation parameter self.theta = 1 def update(self): + if self.memopt: # Gradient descent, Dual problem solution # self.y_old += self.sigma * self.operator.direct(self.xbar) self.operator.direct(self.xbar, out=self.y_tmp) self.y_tmp *= self.sigma - self.y_old += self.y_tmp + self.y_tmp += self.y_old #self.y = self.f.proximal_conjugate(self.y_old, self.sigma) - self.f.proximal_conjugate(self.y_old, self.sigma, out=self.y) + self.f.proximal_conjugate(self.y_tmp, self.sigma, out=self.y) # Gradient ascent, Primal problem solution self.operator.adjoint(self.y, out=self.x_tmp) - self.x_tmp *= self.tau - self.x_old -= self.x_tmp + self.x_tmp *= -1*self.tau + self.x_tmp += self.x_old - self.g.proximal(self.x_old, self.tau, out=self.x) + self.g.proximal(self.x_tmp, self.tau, out=self.x) #Update self.x.subtract(self.x_old, out=self.xbar) @@ -82,7 +92,8 @@ class PDHG(Algorithm): self.x_old.fill(self.x) self.y_old.fill(self.y) - + + else: # Gradient descent, Dual problem solution self.y_old += self.sigma * self.operator.direct(self.xbar) @@ -93,18 +104,28 @@ class PDHG(Algorithm): self.x = self.g.proximal(self.x_old, self.tau) #Update - #xbar = x + theta * (x - x_old) - self.xbar.fill(self.x) - self.xbar -= self.x_old + self.x.subtract(self.x_old, out=self.xbar) self.xbar *= self.theta self.xbar += self.x - self.x_old = self.x - self.y_old = self.y + self.x_old.fill(self.x) + self.y_old.fill(self.y) + + #xbar = x + theta * (x - x_old) +# self.xbar.fill(self.x) +# self.xbar -= self.x_old +# self.xbar *= self.theta +# self.xbar += self.x + +# self.x_old.fill(self.x) +# self.y_old.fill(self.y) + + def update_objective(self): + p1 = self.f(self.operator.direct(self.x)) + self.g(self.x) - d1 = -(self.f.convex_conjugate(self.y) + self.g(-1*self.operator.adjoint(self.y))) + d1 = -(self.f.convex_conjugate(self.y) + self.g.convex_conjugate(-1*self.operator.adjoint(self.y))) self.loss.append([p1,d1,p1-d1]) @@ -151,8 +172,8 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): if not memopt: - - y_tmp = y_old + sigma * operator.direct(xbar) + + y_tmp = y_old + sigma * operator.direct(xbar) y = f.proximal_conjugate(y_tmp, sigma) x_tmp = x_old - tau*operator.adjoint(y) @@ -161,20 +182,19 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): x.subtract(x_old, out=xbar) xbar *= theta xbar += x + + if i%50==0: + + p1 = f(operator.direct(x)) + g(x) + d1 = - ( f.convex_conjugate(y) + g.convex_conjugate(-1*operator.adjoint(y)) ) + primal.append(p1) + dual.append(d1) + pdgap.append(p1-d1) + print(p1, d1, p1-d1) x_old.fill(x) y_old.fill(y) - - - if i%10==0: -# - p1 = f(operator.direct(x)) + g(x) - print(p1) -# d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) -# primal.append(p1) -# dual.append(d1) -# pdgap.append(p1-d1) -# print(p1, d1, p1-d1) + else: @@ -184,7 +204,7 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): f.proximal_conjugate(y_tmp, sigma, out=y) operator.adjoint(y, out = x_tmp) - x_tmp *= -tau + x_tmp *= -1*tau x_tmp += x_old g.proximal(x_tmp, tau, out = x) @@ -192,19 +212,20 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): x.subtract(x_old, out=xbar) xbar *= theta xbar += x - - x_old.fill(x) - y_old.fill(y) - if i%10==0: -# + if i%50==0: + p1 = f(operator.direct(x)) + g(x) - print(p1) -# d1 = - ( f.convex_conjugate(y) + g(-1*operator.adjoint(y)) ) -# primal.append(p1) -# dual.append(d1) -# pdgap.append(p1-d1) -# print(p1, d1, p1-d1) + d1 = - ( f.convex_conjugate(y) + g.convex_conjugate(-1*operator.adjoint(y)) ) + primal.append(p1) + dual.append(d1) + pdgap.append(p1-d1) + print(p1, d1, p1-d1) + + x_old.fill(x) + y_old.fill(y) + + t_end = time.time() diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/funcs.py b/Wrappers/Python/build/lib/ccpi/optimisation/funcs.py index efc465c..b2b9791 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/funcs.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/funcs.py @@ -17,7 +17,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ccpi.optimisation.ops import Identity, FiniteDiff2D import numpy from ccpi.framework import DataContainer import warnings @@ -99,12 +98,6 @@ class Norm2(Function): raise ValueError ('Wrong size: x{0} out{1}'.format(x.shape,out.shape) ) -class TV2D(Norm2): - - def __init__(self, gamma): - super(TV2D,self).__init__(gamma, 0) - self.op = FiniteDiff2D() - self.L = self.op.get_max_sing_val() # Define a class for squared 2-norm diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/BlockFunction.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/BlockFunction.py index bf627a5..0836324 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/BlockFunction.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/BlockFunction.py @@ -93,16 +93,28 @@ class BlockFunction(Function): ''' + + if out is None: - out = [None]*self.length - if isinstance(tau, Number): - for i in range(self.length): - out[i] = self.functions[i].proximal(x.get_item(i), tau) + out = [None]*self.length + if isinstance(tau, Number): + for i in range(self.length): + out[i] = self.functions[i].proximal(x.get_item(i), tau) + else: + for i in range(self.length): + out[i] = self.functions[i].proximal(x.get_item(i), tau.get_item(i)) + + return BlockDataContainer(*out) + else: - for i in range(self.length): - out[i] = self.functions[i].proximal(x.get_item(i), tau.get_item(i)) - - return BlockDataContainer(*out) + if isinstance(tau, Number): + for i in range(self.length): + self.functions[i].proximal(x.get_item(i), tau, out[i]) + else: + for i in range(self.length): + self.functions[i].proximal(x.get_item(i), tau.get_item(i), out[i]) + + def gradient(self,x, out=None): diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition.py index 70511bb..8895f3d 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition.py @@ -19,16 +19,13 @@ class FunctionOperatorComposition(Function): ''' - def __init__(self, operator, function): + def __init__(self, function, operator): super(FunctionOperatorComposition, self).__init__() + self.function = function self.operator = operator - alpha = 1 - - if isinstance (function, ScaledFunction): - alpha = function.scalar - self.L = 2 * alpha * operator.norm()**2 + self.L = function.L * operator.norm()**2 def __call__(self, x): @@ -39,47 +36,57 @@ class FunctionOperatorComposition(Function): ''' - return self.function(self.operator.direct(x)) + return self.function(self.operator.direct(x)) + + def gradient(self, x, out=None): +# + ''' Gradient takes into account the Operator''' + if out is None: + return self.operator.adjoint(self.function.gradient(self.operator.direct(x))) + else: + tmp = self.operator.range_geometry().allocate() + self.operator.direct(x, out=tmp) + self.function.gradient(tmp, out=tmp) + self.operator.adjoint(tmp, out=out) - #TODO do not know if we need it - def call_adjoint(self, x): - return self.function(self.operator.adjoint(x)) + + #TODO do not know if we need it + #def call_adjoint(self, x): + # + # return self.function(self.operator.adjoint(x)) - def convex_conjugate(self, x): - - ''' convex_conjugate does not take into account the Operator''' - return self.function.convex_conjugate(x) - def proximal(self, x, tau, out=None): - - '''proximal does not take into account the Operator''' - if out is None: - return self.function.proximal(x, tau) - else: - self.function.proximal(x, tau, out=out) - + #def convex_conjugate(self, x): + # + # ''' convex_conjugate does not take into account the Operator''' + # return self.function.convex_conjugate(x) - def proximal_conjugate(self, x, tau, out=None): + - ''' proximal conjugate does not take into account the Operator''' - if out is None: - return self.function.proximal_conjugate(x, tau) - else: - self.function.proximal_conjugate(x, tau, out=out) - def gradient(self, x, out=None): - - ''' Gradient takes into account the Operator''' - if out is None: - return self.operator.adjoint( - self.function.gradient(self.operator.direct(x)) - ) - else: - self.operator.adjoint( - self.function.gradient(self.operator.direct(x), - out=out) - ) + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + from ccpi.optimisation.operators import Gradient + from ccpi.optimisation.functions import L2NormSquared + + M, N, K = 2,3 + ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N) + + G = Gradient(ig) + alpha = 0.5 + + f = L2NormSquared() + f_comp = FunctionOperatorComposition(G, alpha * f) + x = ig.allocate('random_int') + print(f_comp.gradient(x).shape + + ) + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition_old.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition_old.py new file mode 100644 index 0000000..70511bb --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition_old.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 8 09:55:36 2019 + +@author: evangelos +""" + +from ccpi.optimisation.functions import Function +from ccpi.optimisation.functions import ScaledFunction + + +class FunctionOperatorComposition(Function): + + ''' Function composition with Operator, i.e., f(Ax) + + A: operator + f: function + + ''' + + def __init__(self, operator, function): + + super(FunctionOperatorComposition, self).__init__() + self.function = function + self.operator = operator + alpha = 1 + + if isinstance (function, ScaledFunction): + alpha = function.scalar + self.L = 2 * alpha * operator.norm()**2 + + + def __call__(self, x): + + ''' Evaluate FunctionOperatorComposition at x + + returns f(Ax) + + ''' + + return self.function(self.operator.direct(x)) + + #TODO do not know if we need it + def call_adjoint(self, x): + + return self.function(self.operator.adjoint(x)) + + + def convex_conjugate(self, x): + + ''' convex_conjugate does not take into account the Operator''' + return self.function.convex_conjugate(x) + + def proximal(self, x, tau, out=None): + + '''proximal does not take into account the Operator''' + if out is None: + return self.function.proximal(x, tau) + else: + self.function.proximal(x, tau, out=out) + + + def proximal_conjugate(self, x, tau, out=None): + + ''' proximal conjugate does not take into account the Operator''' + if out is None: + return self.function.proximal_conjugate(x, tau) + else: + self.function.proximal_conjugate(x, tau, out=out) + + def gradient(self, x, out=None): + + ''' Gradient takes into account the Operator''' + if out is None: + return self.operator.adjoint( + self.function.gradient(self.operator.direct(x)) + ) + else: + self.operator.adjoint( + self.function.gradient(self.operator.direct(x), + out=out) + ) + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py index 7889cad..b53f669 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py @@ -20,7 +20,8 @@ import numpy from ccpi.optimisation.functions import Function from ccpi.optimisation.functions.ScaledFunction import ScaledFunction -from ccpi.framework import ImageData +from ccpi.framework import ImageData, ImageGeometry +import functools class KullbackLeibler(Function): @@ -37,16 +38,23 @@ class KullbackLeibler(Function): def __call__(self, x): - - # TODO check - tmp = x + self.bnoise - ind = tmp.as_array()>0 + res_tmp = numpy.zeros(x.shape) + + tmp = x + self.bnoise + ind = x.as_array()>0 + + res_tmp[ind] = x.as_array()[ind] - self.b.as_array()[ind] * numpy.log(tmp.as_array()[ind]) + + return res_tmp.sum() + + def log(self, datacontainer): + '''calculates the in-place log of the datacontainer''' + if not functools.reduce(lambda x,y: x and y>0, + datacontainer.as_array().ravel(), True): + raise ValueError('KullbackLeibler. Cannot calculate log of negative number') + datacontainer.fill( numpy.log(datacontainer.as_array()) ) - res = x.as_array()[ind] - self.b.as_array()[ind] * numpy.log(tmp.as_array()[ind]) - - return sum(res) - def gradient(self, x, out=None): @@ -54,27 +62,42 @@ class KullbackLeibler(Function): if out is None: return 1 - self.b/(x + self.bnoise) else: - self.b.divide(x+self.bnoise, out=out) + x.add(self.bnoise, out=out) + self.b.divide(out, out=out) out.subtract(1, out=out) - + out *= -1 + def convex_conjugate(self, x): - tmp = self.b/( 1 - x ) + tmp = self.b/(1-x) ind = tmp.as_array()>0 - sel - - return (self.b * ( ImageData( numpy.log(tmp) ) - 1 ) - self.bnoise * (x - 1)).sum() + return (self.b.as_array()[ind] * (numpy.log(tmp.as_array()[ind])-1)).sum() + def proximal(self, x, tau, out=None): if out is None: return 0.5 *( (x - self.bnoise - tau) + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() ) else: - tmp = 0.5 *( (x - self.bnoise - tau) + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() ) - out.fill(tmp) - + tmp = 0.5 *( (x - self.bnoise - tau) + + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() + ) + x.add(self.bnoise, out=out) + out -= tau + out *= out + tmp = self.b * (4 * tau) + out.add(tmp, out=out) + out.sqrt(out=out) + + x.subtract(self.bnoise, out=tmp) + tmp -= tau + + out += tmp + + out *= 0.5 + def proximal_conjugate(self, x, tau, out=None): @@ -82,10 +105,21 @@ class KullbackLeibler(Function): z = x + tau * self.bnoise return 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) else: - z = x + tau * self.bnoise - res = 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) - out.fill(res) +# z = x + tau * self.bnoise +# out.fill( 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) ) + + tmp1 = x + tau * self.bnoise - 1 + tmp2 = tmp1 + 2 + self.b.multiply(4*tau, out=out) + tmp1.multiply(tmp1, out=tmp1) + out += tmp1 + out.sqrt(out=out) + + out *= -1 + out += tmp2 + out *= 0.5 + def __rmul__(self, scalar): @@ -106,6 +140,7 @@ if __name__ == '__main__': from ccpi.framework import ImageGeometry import numpy + N, M = 2,3 ig = ImageGeometry(N, M) data = ImageData(numpy.random.randint(-10, 10, size=(M, N))) @@ -137,4 +172,4 @@ if __name__ == '__main__': # - \ No newline at end of file + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py index 6d3bf86..df38f15 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py @@ -19,6 +19,7 @@ from ccpi.optimisation.functions import Function from ccpi.optimisation.functions.ScaledFunction import ScaledFunction +from ccpi.optimisation.functions import FunctionOperatorComposition class L2NormSquared(Function): @@ -71,7 +72,7 @@ class L2NormSquared(Function): def convex_conjugate(self, x): ''' Evaluate convex conjugate of L2NormSquared at x: f^{*}(x)''' - + tmp = 0 if self.b is not None: @@ -93,21 +94,12 @@ class L2NormSquared(Function): if self.b is None: return x/(1+2*tau) else: -# tmp = x -# tmp -= self.b -# tmp /= (1+2*tau) -# tmp += self.b -# return tmp - return (x-self.b)/(1+2*tau) + self.b - -# if self.b is not None: -# out=x -# if self.b is not None: -# out -= self.b -# out /= (1+2*tau) -# if self.b is not None: -# out += self.b -# return out + + tmp = x.subtract(self.b) + tmp /= (1+2*tau) + tmp += self.b + return tmp + else: out.fill(x) if self.b is not None: @@ -145,7 +137,13 @@ class L2NormSquared(Function): ''' - return ScaledFunction(self, scalar) + return ScaledFunction(self, scalar) + + + def composition(self, operator): + + return FunctionOperatorComposition(operator) + if __name__ == '__main__': diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/MixedL21Norm.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/MixedL21Norm.py index 2004e5f..e8f6da4 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/MixedL21Norm.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/MixedL21Norm.py @@ -43,19 +43,9 @@ class MixedL21Norm(Function): ''' if not isinstance(x, BlockDataContainer): raise ValueError('__call__ expected BlockDataContainer, got {}'.format(type(x))) - - if self.SymTensor: - - #TODO fix this case - param = [1]*x.shape[0] - param[-1] = 2 - tmp = [param[i]*(x[i] ** 2) for i in range(x.shape[0])] - res = sum(tmp).sqrt().sum() - - else: - - tmp = [ el**2 for el in x.containers ] - res = sum(tmp).sqrt().sum() + + tmp = [ el**2 for el in x.containers ] + res = sum(tmp).sqrt().sum() return res @@ -67,7 +57,12 @@ class MixedL21Norm(Function): ''' This is the Indicator function of ||\cdot||_{2, \infty} which is either 0 if ||x||_{2, \infty} or \infty ''' + return 0.0 + + #tmp = [ el**2 for el in x.containers ] + #print(sum(tmp).sqrt().as_array().max()) + #return sum(tmp).sqrt().as_array().max() def proximal(self, x, tau, out=None): @@ -80,35 +75,24 @@ class MixedL21Norm(Function): def proximal_conjugate(self, x, tau, out=None): - if self.SymTensor: - - param = [1]*x.shape[0] - param[-1] = 2 - tmp = [param[i]*(x[i] ** 2) for i in range(x.shape[0])] - frac = [x[i]/(sum(tmp).sqrt()).maximum(1.0) for i in range(x.shape[0])] - res = BlockDataContainer(*frac) - - return res - - else: - if out is None: - tmp = [ el*el for el in x.containers] - res = sum(tmp).sqrt().maximum(1.0) - frac = [el/res for el in x.containers] - return BlockDataContainer(*frac) + if out is None: + tmp = [ el*el for el in x.containers] + res = sum(tmp).sqrt().maximum(1.0) + frac = [el/res for el in x.containers] + return BlockDataContainer(*frac) + - - #TODO this is slow, why??? + #TODO this is slow, why??? # return x.divide(x.pnorm().maximum(1.0)) - else: - - res1 = functools.reduce(lambda a,b: a + b*b, x.containers, x.get_item(0) * 0 ) - res = res1.sqrt().maximum(1.0) - x.divide(res, out=out) - + else: + + res1 = functools.reduce(lambda a,b: a + b*b, x.containers, x.get_item(0) * 0 ) + res = res1.sqrt().maximum(1.0) + x.divide(res, out=out) + # x.divide(sum([el*el for el in x.containers]).sqrt().maximum(1.0), out=out) - #TODO this is slow, why ??? + #TODO this is slow, why ??? # x.divide(x.pnorm().maximum(1.0), out=out) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/ScaledFunction.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/ScaledFunction.py index 7caeab2..1db223b 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/ScaledFunction.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/ScaledFunction.py @@ -59,7 +59,8 @@ class ScaledFunction(object): if out is None: return self.scalar * self.function.gradient(x) else: - out.fill( self.scalar * self.function.gradient(x) ) + self.function.gradient(x, out=out) + out *= self.scalar def proximal(self, x, tau, out=None): '''This returns the proximal operator for the function at x, tau diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFunction.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFunction.py index cce519a..a019815 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFunction.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFunction.py @@ -39,14 +39,8 @@ class ZeroFunction(Function): indicator function for the set = {0} So 0 if x=0, or inf if x neq 0 ''' + return x.maximum(0).sum() - if x.shape[0]==1: - return x.maximum(0).sum() - else: - if isinstance(x, BlockDataContainer): - return x.get_item(0).maximum(0).sum() + x.get_item(1).maximum(0).sum() - else: - return x.maximum(0).sum() + x.maximum(0).sum() def proximal(self, x, tau, out=None): if out is None: diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockOperator.py index 1d77510..c8bd546 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockOperator.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockOperator.py @@ -266,15 +266,30 @@ class BlockOperator(Operator): # column BlockOperator return self.get_item(0,0).domain_geometry() else: - shape = (self.shape[0], 1) - return BlockGeometry(*[el.domain_geometry() for el in self.operators], - shape=shape) + # get the geometries column wise + # we need only the geometries from the first row + # since it is compatible from __init__ + tmp = [] + for i in range(self.shape[1]): + tmp.append(self.get_item(0,i).domain_geometry()) + return BlockGeometry(*tmp) + + #shape = (self.shape[0], 1) + #return BlockGeometry(*[el.domain_geometry() for el in self.operators], + # shape=self.shape) def range_geometry(self): '''returns the range of the BlockOperator''' - shape = (self.shape[1], 1) - return BlockGeometry(*[el.range_geometry() for el in self.operators], - shape=shape) + + tmp = [] + for i in range(self.shape[0]): + tmp.append(self.get_item(i,0).range_geometry()) + return BlockGeometry(*tmp) + + + #shape = (self.shape[1], 1) + #return BlockGeometry(*[el.range_geometry() for el in self.operators], + # shape=shape) def sum_abs_row(self): @@ -312,7 +327,8 @@ class BlockOperator(Operator): if __name__ == '__main__': from ccpi.framework import ImageGeometry - from ccpi.optimisation.operators import Gradient, Identity, SparseFiniteDiff + from ccpi.optimisation.operators import Gradient, Identity, \ + SparseFiniteDiff, SymmetrizedGradient, ZeroOperator M, N = 4, 3 @@ -363,4 +379,39 @@ if __name__ == '__main__': + ########################################################################### + # Block Operator for TGV reconstruction + + M, N = 2,3 + ig = ImageGeometry(M, N) + ag = ig + + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + + op22 = SymmetrizedGradient(op11.domain_geometry()) + + op21 = ZeroOperator(ig, op22.range_geometry()) + + + op31 = Identity(ig, ag) + op32 = ZeroOperator(op22.domain_geometry(), ag) + + operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + + z1 = operator.domain_geometry() + z2 = operator.range_geometry() + + print(z1.shape) + print(z2.shape) + + + + + + + + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator.py index 835f96d..f459334 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator.py @@ -7,7 +7,6 @@ Created on Fri Mar 1 22:51:17 2019 """ from ccpi.optimisation.operators import LinearOperator -from ccpi.optimisation.ops import PowerMethodNonsquare from ccpi.framework import ImageData, BlockDataContainer import numpy as np @@ -42,7 +41,7 @@ class FiniteDiff(LinearOperator): #self.voxel_size = kwargs.get('voxel_size',1) # this wrongly assumes a homogeneous voxel size - self.voxel_size = self.gm_domain.voxel_size_x +# self.voxel_size = self.gm_domain.voxel_size_x def direct(self, x, out=None): @@ -318,7 +317,7 @@ class FiniteDiff(LinearOperator): def norm(self): x0 = self.gm_domain.allocate('random_int') - self.s1, sall, svec = PowerMethodNonsquare(self, 25, x0) + self.s1, sall, svec = LinearOperator.PowerMethod(self, 25, x0) return self.s1 diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py index 4935435..e0b8a32 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py @@ -7,7 +7,6 @@ Created on Fri Mar 1 22:50:04 2019 """ from ccpi.optimisation.operators import Operator, LinearOperator, ScaledOperator -from ccpi.optimisation.ops import PowerMethodNonsquare from ccpi.framework import ImageData, ImageGeometry, BlockGeometry, BlockDataContainer import numpy from ccpi.optimisation.operators import FiniteDiff, SparseFiniteDiff @@ -90,7 +89,7 @@ class Gradient(LinearOperator): def norm(self): x0 = self.gm_domain.allocate('random') - self.s1, sall, svec = PowerMethodNonsquare(self, 10, x0) + self.s1, sall, svec = LinearOperator.PowerMethod(self, 10, x0) return self.s1 def __rmul__(self, scalar): diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperator.py index e19304f..f304cc6 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperator.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperator.py @@ -6,6 +6,8 @@ Created on Tue Mar 5 15:57:52 2019 """ from ccpi.optimisation.operators import Operator +from ccpi.framework import ImageGeometry +import numpy class LinearOperator(Operator): @@ -20,3 +22,46 @@ class LinearOperator(Operator): only available to linear operators''' raise NotImplementedError + + @staticmethod + def PowerMethod(operator, iterations, x_init=None): + '''Power method to calculate iteratively the Lipschitz constant''' + + # Initialise random + if x_init is None: + x0 = operator.domain_geometry().allocate(ImageGeometry.RANDOM_INT) + else: + x0 = x_init.copy() + + x1 = operator.domain_geometry().allocate() + y_tmp = operator.range_geometry().allocate() + s = numpy.zeros(iterations) + # Loop + for it in numpy.arange(iterations): + operator.direct(x0,out=y_tmp) + operator.adjoint(y_tmp,out=x1) + x1norm = x1.norm() + s[it] = x1.dot(x0) / x0.squared_norm() + x1.multiply((1.0/x1norm), out=x0) + return numpy.sqrt(s[-1]), numpy.sqrt(s), x0 + + @staticmethod + def PowerMethodNonsquare(op,numiters , x_init=None): + '''Power method to calculate iteratively the Lipschitz constant''' + + if x_init is None: + x0 = op.allocate_direct() + x0.fill(numpy.random.randn(*x0.shape)) + else: + x0 = x_init.copy() + + s = numpy.zeros(numiters) + # Loop + for it in numpy.arange(numiters): + x1 = op.adjoint(op.direct(x0)) + x1norm = x1.norm() + s[it] = (x1*x0).sum() / (x0.squared_norm()) + x0 = (1.0/x1norm)*x1 + return numpy.sqrt(s[-1]), numpy.sqrt(s), x0 + + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperatorMatrix.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperatorMatrix.py new file mode 100644 index 0000000..62e22e0 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperatorMatrix.py @@ -0,0 +1,51 @@ +import numpy +from scipy.sparse.linalg import svds +from ccpi.framework import DataContainer +from ccpi.framework import AcquisitionData +from ccpi.framework import ImageData +from ccpi.framework import ImageGeometry +from ccpi.framework import AcquisitionGeometry +from numbers import Number +from ccpi.optimisation.operators import LinearOperator +class LinearOperatorMatrix(LinearOperator): + def __init__(self,A): + self.A = A + self.s1 = None # Largest singular value, initially unknown + super(LinearOperatorMatrix, self).__init__() + + def direct(self,x, out=None): + if out is None: + return type(x)(numpy.dot(self.A,x.as_array())) + else: + numpy.dot(self.A, x.as_array(), out=out.as_array()) + + + def adjoint(self,x, out=None): + if out is None: + return type(x)(numpy.dot(self.A.transpose(),x.as_array())) + else: + numpy.dot(self.A.transpose(),x.as_array(), out=out.as_array()) + + + def size(self): + return self.A.shape + + def get_max_sing_val(self): + # If unknown, compute and store. If known, simply return it. + if self.s1 is None: + self.s1 = svds(self.A,1,return_singular_vectors=False)[0] + return self.s1 + else: + return self.s1 + def allocate_direct(self): + '''allocates the memory to hold the result of adjoint''' + #numpy.dot(self.A.transpose(),x.as_array()) + M_A, N_A = self.A.shape + out = numpy.zeros((N_A,1)) + return DataContainer(out) + def allocate_adjoint(self): + '''allocate the memory to hold the result of direct''' + #numpy.dot(self.A.transpose(),x.as_array()) + M_A, N_A = self.A.shape + out = numpy.zeros((M_A,1)) + return DataContainer(out) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/SymmetrizedGradientOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/SymmetrizedGradientOperator.py index c38458d..243809b 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/SymmetrizedGradientOperator.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/SymmetrizedGradientOperator.py @@ -7,7 +7,6 @@ Created on Fri Mar 1 22:53:55 2019 """ from ccpi.optimisation.operators import Gradient, Operator, LinearOperator, ScaledOperator -from ccpi.optimisation.ops import PowerMethodNonsquare from ccpi.framework import ImageData, ImageGeometry, BlockGeometry, BlockDataContainer import numpy from ccpi.optimisation.operators import FiniteDiff, SparseFiniteDiff @@ -15,6 +14,12 @@ from ccpi.optimisation.operators import FiniteDiff, SparseFiniteDiff class SymmetrizedGradient(Gradient): + ''' Symmetrized Gradient, denoted by E: V --> W + where V is the Range of the Gradient Operator + and W is the Range of the Symmetrized Gradient. + ''' + + def __init__(self, gm_domain, bnd_cond = 'Neumann', **kwargs): super(SymmetrizedGradient, self).__init__(gm_domain, bnd_cond, **kwargs) @@ -22,165 +27,218 @@ class SymmetrizedGradient(Gradient): ''' Domain of SymGrad is the Range of Gradient ''' + self.gm_domain = self.gm_range self.bnd_cond = bnd_cond self.channels = self.gm_range.get_item(0).channels - if self.correlation=='Space': - if self.channels>1: - pass - else: -# # 2D image ---> Dx v1, Dyv2, Dx - tmp = self.gm_domain.geometries + (self.gm_domain.get_item(0),) - self.gm_range = BlockGeometry(*tmp ) - self.ind1 = range(self.gm_domain.get_item(0).length) - self.ind2 = range(self.gm_domain.get_item(0).length-1, -1, -1) -# self.order = myorder = [0,1,2 3] - - elif self.correlation=='SpaceChannels': - if self.channels>1: - pass - else: - raise ValueError('No channels to correlate') - - - def direct(self, x, out=None): + tmp_gm = len(self.gm_domain.geometries)*self.gm_domain.geometries -# tmp = numpy.zeros(self.gm_range) -# tmp[0] = FiniteDiff(self.gm_domain[1:], direction = 1, bnd_cond = self.bnd_cond).adjoint(x.as_array()[0]) -# tmp[1] = FiniteDiff(self.gm_domain[1:], direction = 0, bnd_cond = self.bnd_cond).adjoint(x.as_array()[1]) -# tmp[2] = 0.5 * (FiniteDiff(self.gm_domain[1:], direction = 0, bnd_cond = self.bnd_cond).adjoint(x.as_array()[0]) + -# FiniteDiff(self.gm_domain[1:], direction = 1, bnd_cond = self.bnd_cond).adjoint(x.as_array()[1]) ) -# -# return type(x)(tmp) - - tmp = [[None]*2]*2 - for i in range(2): - for j in range(2): - tmp[i][j]=FiniteDiff(self.gm_domain.get_item(0), direction = i, bnd_cond = self.bnd_cond).adjoint(x.get_item(j)) - tmp = numpy.array(tmp) - z = 0.5 * (tmp.T + tmp) - z = z.to + self.gm_range = BlockGeometry(*tmp_gm) - return BlockDataContainer(*z.tolist()) - - - def adjoint(self, x, out=None): - pass + self.FD = FiniteDiff(self.gm_domain, direction = 0, bnd_cond = self.bnd_cond) - res = [] - for i in range(2): - tmp = ImageData(np.zeros(x.get_item(0))) - for j in range(2): - tmp += FiniteDiff(self.gm_domain.get_item(0), direction = i, bnd_cond = self.bnd_cond).direct(x.get_item(j)) - res.append(tmp) - return res - + if self.gm_domain.shape[0]==2: + self.order_ind = [0,2,1,3] + else: + self.order_ind = [0,3,6,1,4,7,2,5,8] -# for + def direct(self, x, out=None): + + if out is None: + + tmp = [] + for i in range(self.gm_domain.shape[0]): + for j in range(x.shape[0]): + self.FD.direction = i + tmp.append(self.FD.adjoint(x.get_item(j))) + + tmp1 = [tmp[i] for i in self.order_ind] + + res = [0.5 * sum(x) for x in zip(tmp, tmp1)] + + return BlockDataContainer(*res) + + else: + + ind = 0 + for i in range(self.gm_domain.shape[0]): + for j in range(x.shape[0]): + self.FD.direction = i + self.FD.adjoint(x.get_item(j), out=out[ind]) + ind+=1 + out1 = BlockDataContainer(*[out[i] for i in self.order_ind]) + out.fill( 0.5 * (out + out1) ) + + + def adjoint(self, x, out=None): -# tmp = numpy.zeros(self.gm_domain) -# -# tmp[0] = FiniteDiff(self.gm_domain[1:], direction = 1, bnd_cond = self.bnd_cond).direct(x.as_array()[0]) + \ -# FiniteDiff(self.gm_domain[1:], direction = 0, bnd_cond = self.bnd_cond).direct(x.as_array()[2]) -# -# tmp[1] = FiniteDiff(self.gm_domain[1:], direction = 1, bnd_cond = self.bnd_cond).direct(x.as_array()[2]) + \ -# FiniteDiff(self.gm_domain[1:], direction = 0, bnd_cond = self.bnd_cond).direct(x.as_array()[1]) -# -# return type(x)(tmp) + if out is None: - def alloc_domain_dim(self): - return ImageData(numpy.zeros(self.gm_domain)) - - def alloc_range_dim(self): - return ImageData(numpy.zeros(self.range_dim)) - - def domain_dim(self): + tmp = [None]*self.gm_domain.shape[0] + i = 0 + + for k in range(self.gm_domain.shape[0]): + tmp1 = 0 + for j in range(self.gm_domain.shape[0]): + self.FD.direction = j + tmp1 += self.FD.direct(x[i]) + i+=1 + tmp[k] = tmp1 + return BlockDataContainer(*tmp) + + + else: + + tmp = self.gm_domain.allocate() + i = 0 + for k in range(self.gm_domain.shape[0]): + tmp1 = 0 + for j in range(self.gm_domain.shape[0]): + self.FD.direction = j + self.FD.direct(x[i], out=tmp[j]) + i+=1 + tmp1+=tmp[j] + out[k].fill(tmp1) +# tmp = self.adjoint(x) +# out.fill(tmp) + + + def domain_geometry(self): return self.gm_domain - def range_dim(self): + def range_geometry(self): return self.gm_range def norm(self): -# return np.sqrt(4*len(self.domainDim())) - #TODO this takes time for big ImageData - # for 2D ||grad|| = sqrt(8), 3D ||grad|| = sqrt(12) - x0 = ImageData(np.random.random_sample(self.domain_dim())) - self.s1, sall, svec = PowerMethodNonsquare(self, 25, x0) - return self.s1 + +# TODO need dot method for BlockDataContainer +# return numpy.sqrt(4*self.gm_domain.shape[0]) + +# x0 = self.gm_domain.allocate('random') + self.s1, sall, svec = LinearOperator.PowerMethod(self, 50) + return self.s1 if __name__ == '__main__': ########################################################################### - ## Symmetrized Gradient + ## Symmetrized Gradient Tests from ccpi.framework import DataContainer from ccpi.optimisation.operators import Gradient, BlockOperator, FiniteDiff import numpy as np N, M = 2, 3 K = 2 + C = 2 ig1 = ImageGeometry(N, M) - ig2 = ImageGeometry(N, M, channels=K) + ig2 = ImageGeometry(N, M, channels=C) E1 = SymmetrizedGradient(ig1, correlation = 'Space', bnd_cond='Neumann') - E2 = SymmetrizedGradient(ig2, correlation = 'SpaceChannels', bnd_cond='Periodic') - print(E1.domain_geometry().shape) - print(E2.domain_geometry().shape) + try: + E1 = SymmetrizedGradient(ig1, correlation = 'SpaceChannels', bnd_cond='Neumann') + except: + print("No Channels to correlate") + + E2 = SymmetrizedGradient(ig2, correlation = 'SpaceChannels', bnd_cond='Neumann') + + print(E1.domain_geometry().shape, E1.range_geometry().shape) + print(E2.domain_geometry().shape, E2.range_geometry().shape) + + #check Linear operator property + + u1 = E1.domain_geometry().allocate('random_int') + u2 = E2.domain_geometry().allocate('random_int') + + # Need to allocate random_int at the Range of SymGradient + + #a1 = ig1.allocate('random_int') + #a2 = ig1.allocate('random_int') + #a3 = ig1.allocate('random_int') + + #a4 = ig1.allocate('random_int') + #a5 = ig1.allocate('random_int') + #a6 = ig1.allocate('random_int') + + # TODO allocate has to create this symmetry by default!!!!! + #w1 = BlockDataContainer(*[a1, a2, \ + # a2, a3]) + w1 = E1.range_geometry().allocate('random_int',symmetry=True) + + LHS = (E1.direct(u1) * w1).sum() + RHS = (u1 * E1.adjoint(w1)).sum() + + numpy.testing.assert_equal(LHS, RHS) - u1 = E1.gm_domain.allocate('random_int') u2 = E2.gm_domain.allocate('random_int') - - res = E1.direct(u1) + #aa1 = ig2.allocate('random_int') + #aa2 = ig2.allocate('random_int') + #aa3 = ig2.allocate('random_int') + #aa4 = ig2.allocate('random_int') + #aa5 = ig2.allocate('random_int') + #aa6 = ig2.allocate('random_int') - res1 = E1.adjoint(res) + #w2 = BlockDataContainer(*[aa1, aa2, aa3, \ + # aa2, aa4, aa5, \ + # aa3, aa5, aa6]) + w2 = E2.range_geometry().allocate('random_int',symmetry=True) + -# Dx = FiniteDiff(ig1, direction = 1, bnd_cond = 'Neumann') -# Dy = FiniteDiff(ig1, direction = 0, bnd_cond = 'Neumann') -# -# B = BlockOperator(Dy, Dx) -# V = BlockDataContainer(u1,u2) -# -# res = B.adjoint(V) + LHS1 = (E2.direct(u2) * w2).sum() + RHS1 = (u2 * E2.adjoint(w2)).sum() -# ig = (N,M) -# ig2 = (2,) + ig -# ig3 = (3,) + ig -# u1 = ig.allocate('random_int') -# w1 = E.gm_range.allocate('random_int') -# DataContainer(np.random.randint(10, size=ig3)) + numpy.testing.assert_equal(LHS1, RHS1) + out = E1.range_geometry().allocate() + E1.direct(u1, out=out) + a1 = E1.direct(u1) + numpy.testing.assert_array_equal(a1[0].as_array(), out[0].as_array()) + numpy.testing.assert_array_equal(a1[1].as_array(), out[1].as_array()) + numpy.testing.assert_array_equal(a1[2].as_array(), out[2].as_array()) + numpy.testing.assert_array_equal(a1[3].as_array(), out[3].as_array()) -# d1 = E.direct(u1) -# d2 = E.adjoint(w1) + out1 = E1.domain_geometry().allocate() + E1.adjoint(w1, out=out1) + b1 = E1.adjoint(w1) -# LHS = (d1.as_array()[0]*w1.as_array()[0] + \ -# d1.as_array()[1]*w1.as_array()[1] + \ -# 2*d1.as_array()[2]*w1.as_array()[2]).sum() -# -# RHS = (u1.as_array()[0]*d2.as_array()[0] + \ -# u1.as_array()[1]*d2.as_array()[1]).sum() -# -# -# print(LHS, RHS, E.norm()) -# + LHS_out = (out * w1).sum() + RHS_out = (u1 * out1).sum() + print(LHS_out, RHS_out) -# + out2 = E2.range_geometry().allocate() + E2.direct(u2, out=out2) + a2 = E2.direct(u2) + + out21 = E2.domain_geometry().allocate() + E2.adjoint(w2, out=out21) + b2 = E2.adjoint(w2) + LHS_out = (out2 * w2).sum() + RHS_out = (u2 * out21).sum() + print(LHS_out, RHS_out) + out = E1.range_geometry().allocate() + E1.direct(u1, out=out) + E1.adjoint(out, out=out1) + print(E1.norm()) + print(E2.norm()) - \ No newline at end of file + +# +# +# +# \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ZeroOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ZeroOperator.py index a7c5f09..8168f0b 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ZeroOperator.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ZeroOperator.py @@ -8,32 +8,37 @@ Created on Wed Mar 6 19:25:53 2019 import numpy as np from ccpi.framework import ImageData -from ccpi.optimisation.operators import Operator +from ccpi.optimisation.operators import LinearOperator -class ZeroOp(Operator): +class ZeroOperator(LinearOperator): - def __init__(self, gm_domain, gm_range): + def __init__(self, gm_domain, gm_range=None): + + super(ZeroOperator, self).__init__() + self.gm_domain = gm_domain - self.gm_range = gm_range - super(ZeroOp, self).__init__() + self.gm_range = gm_range + if self.gm_range is None: + self.gm_range = self.gm_domain + def direct(self,x,out=None): if out is None: - return ImageData(np.zeros(self.gm_range)) + return self.gm_range.allocate() else: - return ImageData(np.zeros(self.gm_range)) + out.fill(self.gm_range.allocate()) def adjoint(self,x, out=None): if out is None: - return ImageData(np.zeros(self.gm_domain)) + return self.gm_domain.allocate() else: - return ImageData(np.zeros(self.gm_domain)) + out.fill(self.gm_domain.allocate()) def norm(self): return 0 - def domain_dim(self): + def domain_geometry(self): return self.gm_domain - def range_dim(self): + def range_geometry(self): return self.gm_range \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/__init__.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/__init__.py index 7040d3a..23222d4 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/__init__.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/__init__.py @@ -18,6 +18,6 @@ from .FiniteDifferenceOperator import FiniteDiff from .GradientOperator import Gradient from .SymmetrizedGradientOperator import SymmetrizedGradient from .IdentityOperator import Identity -from .ZeroOperator import ZeroOp - +from .ZeroOperator import ZeroOperator +from .LinearOperatorMatrix import LinearOperatorMatrix diff --git a/Wrappers/Python/wip/Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py b/Wrappers/Python/wip/Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py index ef7a9ec..10d15fa 100644 --- a/Wrappers/Python/wip/Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py +++ b/Wrappers/Python/wip/Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py @@ -52,11 +52,11 @@ pixv = sinogram_wb.shape[1] # detectors igWB = ImageGeometry(voxel_num_x = pixh, voxel_num_y = pixv) # Load Golden angles -with open("/media/newhd/vaggelis/CCPi/CCPi-Block/CCPi-Framework/Wrappers/Python/wip/Demos/IMAT_Reconstruction/golden_angles.txt") as f: +with open("golden_angles.txt") as f: angles_string = [line.rstrip() for line in f] angles = numpy.array(angles_string).astype(float) agWB = AcquisitionGeometry('parallel', '2D', angles * numpy.pi / 180, pixh) -op_WB = AstraProjectorSimple(igWB, agWB, 'gpu') +op_WB = AstraProjectorSimple(igWB, agWB, 'cpu') sinogram_aqdata = AcquisitionData(sinogram_wb, agWB) # BackProjection diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py index 4903c44..58978ae 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py @@ -48,7 +48,7 @@ noisy_data = ImageData(n1) # Regularisation Parameter alpha = 2 -method = '1' +method = '0' if method == '0': diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py index 484fe42..4189acb 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py @@ -48,7 +48,7 @@ noisy_data = ImageData(n1) # Regularisation Parameter alpha = 2 -method = '1' +method = '0' if method == '0': diff --git a/Wrappers/Python/wip/test_symGrad_method1.py b/Wrappers/Python/wip/test_symGrad_method1.py deleted file mode 100644 index 36adee1..0000000 --- a/Wrappers/Python/wip/test_symGrad_method1.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 22 14:53:03 2019 - -@author: evangelos -""" - -from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, PDHG_old - -from ccpi.optimisation.operators import BlockOperator, Identity, \ - Gradient, SymmetrizedGradient, ZeroOperator -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -from timeit import default_timer as timer -#def dt(steps): -# return steps[-1] - steps[-2] - -# Create phantom for TGV Gaussian denoising - -N = 3 - -data = np.zeros((N,N)) - -x1 = np.linspace(0, int(N/2), N) -x2 = np.linspace(int(N/2), 0., N) -xv, yv = np.meshgrid(x1, x2) - -xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T - -data = xv -data = data/data.max() - -plt.imshow(data) -plt.show() - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Add Gaussian noise -n1 = random_noise(data, mode = 'gaussian', mean=0, var = 0.005, seed=10) -noisy_data = ImageData(n1) - - -plt.imshow(noisy_data.as_array()) -plt.title('Noisy data') -plt.show() - -alpha, beta = 0.2, 1 - -#method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") -method = '1' - - -# Create operators -op11 = Gradient(ig) -op12 = Identity(op11.range_geometry()) - -op22 = SymmetrizedGradient(op11.domain_geometry()) - -op21 = ZeroOperator(ig, op22.range_geometry()) - - -op31 = Identity(ig, ag) -op32 = ZeroOperator(op22.domain_geometry(), ag) - -operator1 = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) - - -f1 = alpha * MixedL21Norm() -f2 = beta * MixedL21Norm() -f3 = ZeroFunction() -f_B3 = BlockFunction(f1, f2, f3) -g_B3 = ZeroFunction() - - - -# Create operators -op11 = Gradient(ig) -op12 = Identity(op11.range_geometry()) - -op22 = SymmetrizedGradient(op11.domain_geometry()) - -op21 = ZeroOperator(ig, op22.range_geometry()) - -operator2 = BlockOperator(op11, -1*op12, \ - op21, op22, \ - shape=(2,2) ) - -#f1 = alpha * MixedL21Norm() -#f2 = beta * MixedL21Norm() -f_B2 = BlockFunction(f1, f2) -g_B2 = 0.5 * L2NormSquared(b = noisy_data) - - -#%% - -x_old1 = operator1.domain_geometry().allocate('random_int') -y_old1 = operator1.range_geometry().allocate() - -xbar1 = x_old1.copy() -x_tmp1 = x_old1.copy() -x1 = x_old1.copy() - -y_tmp1 = y_old1.copy() -y1 = y_tmp1.copy() - -x_old2 = x_old1.copy() -y_old2 = operator2.range_geometry().allocate() - -xbar2 = x_old2.copy() -x_tmp2 = x_old2.copy() -x2 = x_old2.copy() - -y_tmp2 = y_old2.copy() -y2 = y_tmp2.copy() - -sigma = 0.4 -tau = 0.4 - -y_tmp1 = y_old1 + sigma * operator1.direct(xbar1) -y_tmp2 = y_old2 + sigma * operator2.direct(xbar2) - -numpy.testing.assert_array_equal(y_tmp1[0][0].as_array(), y_tmp2[0][0].as_array()) -numpy.testing.assert_array_equal(y_tmp1[0][1].as_array(), y_tmp2[0][1].as_array()) -numpy.testing.assert_array_equal(y_tmp1[1][0].as_array(), y_tmp2[1][0].as_array()) -numpy.testing.assert_array_equal(y_tmp1[1][1].as_array(), y_tmp2[1][1].as_array()) - - -y1 = f_B3.proximal_conjugate(y_tmp1, sigma) -y2 = f_B2.proximal_conjugate(y_tmp2, sigma) - -numpy.testing.assert_array_equal(y1[0][0].as_array(), y2[0][0].as_array()) -numpy.testing.assert_array_equal(y1[0][1].as_array(), y2[0][1].as_array()) -numpy.testing.assert_array_equal(y1[1][0].as_array(), y2[1][0].as_array()) -numpy.testing.assert_array_equal(y1[1][1].as_array(), y2[1][1].as_array()) - - -x_tmp1 = x_old1 - tau * operator1.adjoint(y1) -x_tmp2 = x_old2 - tau * operator2.adjoint(y2) - -numpy.testing.assert_array_equal(x_tmp1[0].as_array(), x_tmp2[0].as_array()) - - - - - - - - - - - -############################################################################## -#x_1 = operator1.domain_geometry().allocate('random_int') -# -#x_2 = BlockDataContainer(x_1[0], x_1[1]) -# -#res1 = operator1.direct(x_1) -#res2 = operator2.direct(x_2) -# -#print(res1[0][0].as_array(), res2[0][0].as_array()) -#print(res1[0][1].as_array(), res2[0][1].as_array()) -# -#print(res1[1][0].as_array(), res2[1][0].as_array()) -#print(res1[1][1].as_array(), res2[1][1].as_array()) -# -##res1 = op11.direct(x1[0]) - op12.direct(x1[1]) -##res2 = op21.direct(x1[0]) - op22.direct(x1[1]) -- cgit v1.2.3 From 43d0a144e4d8766b8275d9c13e06f2d0423d198e Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 30 Apr 2019 13:55:28 +0100 Subject: removed numpy from build requirements --- Wrappers/Python/conda-recipe/meta.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Wrappers/Python/conda-recipe/meta.yaml b/Wrappers/Python/conda-recipe/meta.yaml index dd3238e..ac5f763 100644 --- a/Wrappers/Python/conda-recipe/meta.yaml +++ b/Wrappers/Python/conda-recipe/meta.yaml @@ -26,7 +26,6 @@ requirements: build: - {{ pin_compatible('numpy', max_pin='x.x') }} - python - - numpy - setuptools run: @@ -34,7 +33,6 @@ requirements: - python - numpy - scipy - #- matplotlib - h5py about: -- cgit v1.2.3 From 64ad60acb5c2a993e9b61682c18105723d9ae912 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 30 Apr 2019 13:56:46 +0100 Subject: restructured processors --- .../ccpi/processors/CenterOfRotationFinder.py | 408 +++++++++++++++++++++ Wrappers/Python/ccpi/processors/Normalizer.py | 124 +++++++ Wrappers/Python/ccpi/processors/__init__.py | 9 + Wrappers/Python/setup.py | 1 + 4 files changed, 542 insertions(+) create mode 100755 Wrappers/Python/ccpi/processors/CenterOfRotationFinder.py create mode 100755 Wrappers/Python/ccpi/processors/Normalizer.py create mode 100755 Wrappers/Python/ccpi/processors/__init__.py diff --git a/Wrappers/Python/ccpi/processors/CenterOfRotationFinder.py b/Wrappers/Python/ccpi/processors/CenterOfRotationFinder.py new file mode 100755 index 0000000..936dc05 --- /dev/null +++ b/Wrappers/Python/ccpi/processors/CenterOfRotationFinder.py @@ -0,0 +1,408 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +from ccpi.framework import DataProcessor, DataContainer, AcquisitionData,\ + AcquisitionGeometry, ImageGeometry, ImageData +import numpy +from scipy import ndimage + +class CenterOfRotationFinder(DataProcessor): + '''Processor to find the center of rotation in a parallel beam experiment + + This processor read in a AcquisitionDataSet and finds the center of rotation + based on Nghia Vo's method. https://doi.org/10.1364/OE.22.019078 + + Input: AcquisitionDataSet + + Output: float. center of rotation in pixel coordinate + ''' + + def __init__(self): + kwargs = { + + } + + #DataProcessor.__init__(self, **kwargs) + super(CenterOfRotationFinder, self).__init__(**kwargs) + + def check_input(self, dataset): + if dataset.number_of_dimensions == 3: + if dataset.geometry.geom_type == 'parallel': + return True + else: + raise ValueError('{0} is suitable only for parallel beam geometry'\ + .format(self.__class__.__name__)) + else: + raise ValueError("Expected input dimensions is 3, got {0}"\ + .format(dataset.number_of_dimensions)) + + + # ######################################################################### + # Copyright (c) 2015, UChicago Argonne, LLC. All rights reserved. # + # # + # Copyright 2015. UChicago Argonne, LLC. This software was produced # + # under U.S. Government contract DE-AC02-06CH11357 for Argonne National # + # Laboratory (ANL), which is operated by UChicago Argonne, LLC for the # + # U.S. Department of Energy. The U.S. Government has rights to use, # + # reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR # + # UChicago Argonne, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR # + # ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is # + # modified to produce derivative works, such modified software should # + # be clearly marked, so as not to confuse it with the version available # + # from ANL. # + # # + # Additionally, redistribution and use in source and binary forms, with # + # or without modification, are permitted provided that the following # + # conditions are met: # + # # + # * Redistributions of source code must retain the above copyright # + # notice, this list of conditions and the following disclaimer. # + # # + # * Redistributions in binary form must reproduce the above copyright # + # notice, this list of conditions and the following disclaimer in # + # the documentation and/or other materials provided with the # + # distribution. # + # # + # * Neither the name of UChicago Argonne, LLC, Argonne National # + # Laboratory, ANL, the U.S. Government, nor the names of its # + # contributors may be used to endorse or promote products derived # + # from this software without specific prior written permission. # + # # + # THIS SOFTWARE IS PROVIDED BY UChicago Argonne, LLC AND CONTRIBUTORS # + # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # + # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # + # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UChicago # + # Argonne, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # + # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # + # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # + # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # + # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # + # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # + # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # + # POSSIBILITY OF SUCH DAMAGE. # + # ######################################################################### + + @staticmethod + def as_ndarray(arr, dtype=None, copy=False): + if not isinstance(arr, numpy.ndarray): + arr = numpy.array(arr, dtype=dtype, copy=copy) + return arr + + @staticmethod + def as_dtype(arr, dtype, copy=False): + if not arr.dtype == dtype: + arr = numpy.array(arr, dtype=dtype, copy=copy) + return arr + + @staticmethod + def as_float32(arr): + arr = CenterOfRotationFinder.as_ndarray(arr, numpy.float32) + return CenterOfRotationFinder.as_dtype(arr, numpy.float32) + + + + + @staticmethod + def find_center_vo(tomo, ind=None, smin=-40, smax=40, srad=10, step=0.5, + ratio=2., drop=20): + """ + Find rotation axis location using Nghia Vo's method. :cite:`Vo:14`. + + Parameters + ---------- + tomo : ndarray + 3D tomographic data. + ind : int, optional + Index of the slice to be used for reconstruction. + smin, smax : int, optional + Reference to the horizontal center of the sinogram. + srad : float, optional + Fine search radius. + step : float, optional + Step of fine searching. + ratio : float, optional + The ratio between the FOV of the camera and the size of object. + It's used to generate the mask. + drop : int, optional + Drop lines around vertical center of the mask. + + Returns + ------- + float + Rotation axis location. + + Notes + ----- + The function may not yield a correct estimate, if: + + - the sample size is bigger than the field of view of the camera. + In this case the ``ratio`` argument need to be set larger + than the default of 2.0. + + - there is distortion in the imaging hardware. If there's + no correction applied, the center of the projection image may + yield a better estimate. + + - the sample contrast is weak. Paganin's filter need to be applied + to overcome this. + + - the sample was changed during the scan. + """ + tomo = CenterOfRotationFinder.as_float32(tomo) + + if ind is None: + ind = tomo.shape[1] // 2 + _tomo = tomo[:, ind, :] + + + + # Reduce noise by smooth filters. Use different filters for coarse and fine search + _tomo_cs = ndimage.filters.gaussian_filter(_tomo, (3, 1)) + _tomo_fs = ndimage.filters.median_filter(_tomo, (2, 2)) + + # Coarse and fine searches for finding the rotation center. + if _tomo.shape[0] * _tomo.shape[1] > 4e6: # If data is large (>2kx2k) + #_tomo_coarse = downsample(numpy.expand_dims(_tomo_cs,1), level=2)[:, 0, :] + #init_cen = _search_coarse(_tomo_coarse, smin, smax, ratio, drop) + #fine_cen = _search_fine(_tomo_fs, srad, step, init_cen*4, ratio, drop) + init_cen = CenterOfRotationFinder._search_coarse(_tomo_cs, smin, + smax, ratio, drop) + fine_cen = CenterOfRotationFinder._search_fine(_tomo_fs, srad, + step, init_cen, + ratio, drop) + else: + init_cen = CenterOfRotationFinder._search_coarse(_tomo_cs, + smin, smax, + ratio, drop) + fine_cen = CenterOfRotationFinder._search_fine(_tomo_fs, srad, + step, init_cen, + ratio, drop) + + #logger.debug('Rotation center search finished: %i', fine_cen) + return fine_cen + + + @staticmethod + def _search_coarse(sino, smin, smax, ratio, drop): + """ + Coarse search for finding the rotation center. + """ + (Nrow, Ncol) = sino.shape + centerfliplr = (Ncol - 1.0) / 2.0 + + # Copy the sinogram and flip left right, the purpose is to + # make a full [0;2Pi] sinogram + _copy_sino = numpy.fliplr(sino[1:]) + + # This image is used for compensating the shift of sinogram 2 + temp_img = numpy.zeros((Nrow - 1, Ncol), dtype='float32') + temp_img[:] = sino[-1] + + # Start coarse search in which the shift step is 1 + listshift = numpy.arange(smin, smax + 1) + listmetric = numpy.zeros(len(listshift), dtype='float32') + mask = CenterOfRotationFinder._create_mask(2 * Nrow - 1, Ncol, + 0.5 * ratio * Ncol, drop) + for i in listshift: + _sino = numpy.roll(_copy_sino, i, axis=1) + if i >= 0: + _sino[:, 0:i] = temp_img[:, 0:i] + else: + _sino[:, i:] = temp_img[:, i:] + listmetric[i - smin] = numpy.sum(numpy.abs(numpy.fft.fftshift( + #pyfftw.interfaces.numpy_fft.fft2( + # numpy.vstack((sino, _sino))) + numpy.fft.fft2(numpy.vstack((sino, _sino))) + )) * mask) + minpos = numpy.argmin(listmetric) + return centerfliplr + listshift[minpos] / 2.0 + + @staticmethod + def _search_fine(sino, srad, step, init_cen, ratio, drop): + """ + Fine search for finding the rotation center. + """ + Nrow, Ncol = sino.shape + centerfliplr = (Ncol + 1.0) / 2.0 - 1.0 + # Use to shift the sinogram 2 to the raw CoR. + shiftsino = numpy.int16(2 * (init_cen - centerfliplr)) + _copy_sino = numpy.roll(numpy.fliplr(sino[1:]), shiftsino, axis=1) + if init_cen <= centerfliplr: + lefttake = numpy.int16(numpy.ceil(srad + 1)) + righttake = numpy.int16(numpy.floor(2 * init_cen - srad - 1)) + else: + lefttake = numpy.int16(numpy.ceil( + init_cen - (Ncol - 1 - init_cen) + srad + 1)) + righttake = numpy.int16(numpy.floor(Ncol - 1 - srad - 1)) + Ncol1 = righttake - lefttake + 1 + mask = CenterOfRotationFinder._create_mask(2 * Nrow - 1, Ncol1, + 0.5 * ratio * Ncol, drop) + numshift = numpy.int16((2 * srad) / step) + 1 + listshift = numpy.linspace(-srad, srad, num=numshift) + listmetric = numpy.zeros(len(listshift), dtype='float32') + factor1 = numpy.mean(sino[-1, lefttake:righttake]) + num1 = 0 + for i in listshift: + _sino = ndimage.interpolation.shift( + _copy_sino, (0, i), prefilter=False) + factor2 = numpy.mean(_sino[0,lefttake:righttake]) + _sino = _sino * factor1 / factor2 + sinojoin = numpy.vstack((sino, _sino)) + listmetric[num1] = numpy.sum(numpy.abs(numpy.fft.fftshift( + #pyfftw.interfaces.numpy_fft.fft2( + # sinojoin[:, lefttake:righttake + 1]) + numpy.fft.fft2(sinojoin[:, lefttake:righttake + 1]) + )) * mask) + num1 = num1 + 1 + minpos = numpy.argmin(listmetric) + return init_cen + listshift[minpos] / 2.0 + + @staticmethod + def _create_mask(nrow, ncol, radius, drop): + du = 1.0 / ncol + dv = (nrow - 1.0) / (nrow * 2.0 * numpy.pi) + centerrow = numpy.ceil(nrow / 2) - 1 + centercol = numpy.ceil(ncol / 2) - 1 + # added by Edoardo Pasca + centerrow = int(centerrow) + centercol = int(centercol) + mask = numpy.zeros((nrow, ncol), dtype='float32') + for i in range(nrow): + num1 = numpy.round(((i - centerrow) * dv / radius) / du) + (p1, p2) = numpy.int16(numpy.clip(numpy.sort( + (-num1 + centercol, num1 + centercol)), 0, ncol - 1)) + mask[i, p1:p2 + 1] = numpy.ones(p2 - p1 + 1, dtype='float32') + if drop < centerrow: + mask[centerrow - drop:centerrow + drop + 1, + :] = numpy.zeros((2 * drop + 1, ncol), dtype='float32') + mask[:,centercol-1:centercol+2] = numpy.zeros((nrow, 3), dtype='float32') + return mask + + def process(self, out=None): + + projections = self.get_input() + + cor = CenterOfRotationFinder.find_center_vo(projections.as_array()) + + return cor + + +class AcquisitionDataPadder(DataProcessor): + '''Normalization based on flat and dark + + This processor read in a AcquisitionData and normalises it based on + the instrument reading with and without incident photons or neutrons. + + Input: AcquisitionData + Parameter: 2D projection with flat field (or stack) + 2D projection with dark field (or stack) + Output: AcquisitionDataSetn + ''' + + def __init__(self, + center_of_rotation = None, + acquisition_geometry = None, + pad_value = 1e-5): + kwargs = { + 'acquisition_geometry' : acquisition_geometry, + 'center_of_rotation' : center_of_rotation, + 'pad_value' : pad_value + } + + super(AcquisitionDataPadder, self).__init__(**kwargs) + + def check_input(self, dataset): + if self.acquisition_geometry is None: + self.acquisition_geometry = dataset.geometry + if dataset.number_of_dimensions == 3: + return True + else: + raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ + .format(dataset.number_of_dimensions)) + + def process(self, out=None): + projections = self.get_input() + w = projections.get_dimension_size('horizontal') + delta = w - 2 * self.center_of_rotation + + padded_width = int ( + numpy.ceil(abs(delta)) + w + ) + delta_pix = padded_width - w + + voxel_per_pixel = 1 + geom = pbalg.pb_setup_geometry_from_acquisition(projections.as_array(), + self.acquisition_geometry.angles, + self.center_of_rotation, + voxel_per_pixel ) + + padded_geometry = self.acquisition_geometry.clone() + + padded_geometry.pixel_num_h = geom['n_h'] + padded_geometry.pixel_num_v = geom['n_v'] + + delta_pix_h = padded_geometry.pixel_num_h - self.acquisition_geometry.pixel_num_h + delta_pix_v = padded_geometry.pixel_num_v - self.acquisition_geometry.pixel_num_v + + if delta_pix_h == 0: + delta_pix_h = delta_pix + padded_geometry.pixel_num_h = padded_width + #initialize a new AcquisitionData with values close to 0 + out = AcquisitionData(geometry=padded_geometry) + out = out + self.pad_value + + + #pad in the horizontal-vertical plane -> slice on angles + if delta > 0: + #pad left of middle + command = "out.array[" + for i in range(out.number_of_dimensions): + if out.dimension_labels[i] == 'horizontal': + value = '{0}:{1}'.format(delta_pix_h, delta_pix_h+w) + command = command + str(value) + else: + if out.dimension_labels[i] == 'vertical' : + value = '{0}:'.format(delta_pix_v) + command = command + str(value) + else: + command = command + ":" + if i < out.number_of_dimensions -1: + command = command + ',' + command = command + '] = projections.array' + #print (command) + else: + #pad right of middle + command = "out.array[" + for i in range(out.number_of_dimensions): + if out.dimension_labels[i] == 'horizontal': + value = '{0}:{1}'.format(0, w) + command = command + str(value) + else: + if out.dimension_labels[i] == 'vertical' : + value = '{0}:'.format(delta_pix_v) + command = command + str(value) + else: + command = command + ":" + if i < out.number_of_dimensions -1: + command = command + ',' + command = command + '] = projections.array' + #print (command) + #cleaned = eval(command) + exec(command) + return out \ No newline at end of file diff --git a/Wrappers/Python/ccpi/processors/Normalizer.py b/Wrappers/Python/ccpi/processors/Normalizer.py new file mode 100755 index 0000000..da65e5c --- /dev/null +++ b/Wrappers/Python/ccpi/processors/Normalizer.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +from ccpi.framework import DataProcessor, DataContainer, AcquisitionData,\ + AcquisitionGeometry, ImageGeometry, ImageData +import numpy + +class Normalizer(DataProcessor): + '''Normalization based on flat and dark + + This processor read in a AcquisitionData and normalises it based on + the instrument reading with and without incident photons or neutrons. + + Input: AcquisitionData + Parameter: 2D projection with flat field (or stack) + 2D projection with dark field (or stack) + Output: AcquisitionDataSetn + ''' + + def __init__(self, flat_field = None, dark_field = None, tolerance = 1e-5): + kwargs = { + 'flat_field' : flat_field, + 'dark_field' : dark_field, + # very small number. Used when there is a division by zero + 'tolerance' : tolerance + } + + #DataProcessor.__init__(self, **kwargs) + super(Normalizer, self).__init__(**kwargs) + if not flat_field is None: + self.set_flat_field(flat_field) + if not dark_field is None: + self.set_dark_field(dark_field) + + def check_input(self, dataset): + if dataset.number_of_dimensions == 3 or\ + dataset.number_of_dimensions == 2: + return True + else: + raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ + .format(dataset.number_of_dimensions)) + + def set_dark_field(self, df): + if type(df) is numpy.ndarray: + if len(numpy.shape(df)) == 3: + raise ValueError('Dark Field should be 2D') + elif len(numpy.shape(df)) == 2: + self.dark_field = df + elif issubclass(type(df), DataContainer): + self.dark_field = self.set_dark_field(df.as_array()) + + def set_flat_field(self, df): + if type(df) is numpy.ndarray: + if len(numpy.shape(df)) == 3: + raise ValueError('Flat Field should be 2D') + elif len(numpy.shape(df)) == 2: + self.flat_field = df + elif issubclass(type(df), DataContainer): + self.flat_field = self.set_flat_field(df.as_array()) + + @staticmethod + def normalize_projection(projection, flat, dark, tolerance): + a = (projection - dark) + b = (flat-dark) + with numpy.errstate(divide='ignore', invalid='ignore'): + c = numpy.true_divide( a, b ) + c[ ~ numpy.isfinite( c )] = tolerance # set to not zero if 0/0 + return c + + @staticmethod + def estimate_normalised_error(projection, flat, dark, delta_flat, delta_dark): + '''returns the estimated relative error of the normalised projection + + n = (projection - dark) / (flat - dark) + Dn/n = (flat-dark + projection-dark)/((flat-dark)*(projection-dark))*(Df/f + Dd/d) + ''' + a = (projection - dark) + b = (flat-dark) + df = delta_flat / flat + dd = delta_dark / dark + rel_norm_error = (b + a) / (b * a) * (df + dd) + return rel_norm_error + + def process(self, out=None): + + projections = self.get_input() + dark = self.dark_field + flat = self.flat_field + + if projections.number_of_dimensions == 3: + if not (projections.shape[1:] == dark.shape and \ + projections.shape[1:] == flat.shape): + raise ValueError('Flats/Dark and projections size do not match.') + + + a = numpy.asarray( + [ Normalizer.normalize_projection( + projection, flat, dark, self.tolerance) \ + for projection in projections.as_array() ] + ) + elif projections.number_of_dimensions == 2: + a = Normalizer.normalize_projection(projections.as_array(), + flat, dark, self.tolerance) + y = type(projections)( a , True, + dimension_labels=projections.dimension_labels, + geometry=projections.geometry) + return y + \ No newline at end of file diff --git a/Wrappers/Python/ccpi/processors/__init__.py b/Wrappers/Python/ccpi/processors/__init__.py new file mode 100755 index 0000000..f8d050e --- /dev/null +++ b/Wrappers/Python/ccpi/processors/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Apr 30 13:51:09 2019 + +@author: ofn77899 +""" + +from .CenterOfRotationFinder import CenterOfRotationFinder +from .Normalizer import Normalizer diff --git a/Wrappers/Python/setup.py b/Wrappers/Python/setup.py index a3fde59..78f88dd 100644 --- a/Wrappers/Python/setup.py +++ b/Wrappers/Python/setup.py @@ -36,6 +36,7 @@ setup( 'ccpi.optimisation.operators', 'ccpi.optimisation.algorithms', 'ccpi.optimisation.functions', + 'ccpi.optimisation.processors', 'ccpi.contrib','ccpi.contrib.optimisation', 'ccpi.contrib.optimisation.algorithms'], -- cgit v1.2.3 From 136d908afbac2b1010b56f8b96081df956f2b8bf Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 30 Apr 2019 13:57:42 +0100 Subject: added test for center of rotation finder --- Wrappers/Python/test/test_DataProcessor.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Wrappers/Python/test/test_DataProcessor.py b/Wrappers/Python/test/test_DataProcessor.py index 1c1de3a..a5bd9c6 100755 --- a/Wrappers/Python/test/test_DataProcessor.py +++ b/Wrappers/Python/test/test_DataProcessor.py @@ -11,8 +11,29 @@ from timeit import default_timer as timer from ccpi.framework import AX, CastDataContainer, PixelByPixelDataProcessor +from ccpi.io.reader import NexusReader +from ccpi.processors import CenterOfRotationFinder +import wget +import os + class TestDataProcessor(unittest.TestCase): + def setUp(self): + wget.download('https://github.com/DiamondLightSource/Savu/raw/master/test_data/data/24737_fd.nxs') + self.filename = '24737_fd.nxs' + + def tearDown(self): + os.remove(self.filename) + def test_CenterOfRotation(self): + reader = NexusReader(self.filename) + ad = reader.get_acquisition_data_whole() + print (ad.geometry) + cf = CenterOfRotationFinder() + cf.set_input(ad) + print ("Center of rotation", cf.get_output()) + + + def test_DataProcessorChaining(self): shape = (2,3,4,5) size = shape[0] -- cgit v1.2.3 From d4abd5cec7097caeda5d30a7c891fd6a47162a8a Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 30 Apr 2019 14:21:52 +0100 Subject: added test and removed processors.py --- Wrappers/Python/ccpi/processors.py | 514 ----------------------------- Wrappers/Python/setup.py | 2 +- Wrappers/Python/test/test_DataProcessor.py | 3 + 3 files changed, 4 insertions(+), 515 deletions(-) delete mode 100755 Wrappers/Python/ccpi/processors.py diff --git a/Wrappers/Python/ccpi/processors.py b/Wrappers/Python/ccpi/processors.py deleted file mode 100755 index ccef410..0000000 --- a/Wrappers/Python/ccpi/processors.py +++ /dev/null @@ -1,514 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License - -from ccpi.framework import DataProcessor, DataContainer, AcquisitionData,\ - AcquisitionGeometry, ImageGeometry, ImageData -from ccpi.reconstruction.parallelbeam import alg as pbalg -import numpy -from scipy import ndimage - -import matplotlib.pyplot as plt - - -class Normalizer(DataProcessor): - '''Normalization based on flat and dark - - This processor read in a AcquisitionData and normalises it based on - the instrument reading with and without incident photons or neutrons. - - Input: AcquisitionData - Parameter: 2D projection with flat field (or stack) - 2D projection with dark field (or stack) - Output: AcquisitionDataSetn - ''' - - def __init__(self, flat_field = None, dark_field = None, tolerance = 1e-5): - kwargs = { - 'flat_field' : flat_field, - 'dark_field' : dark_field, - # very small number. Used when there is a division by zero - 'tolerance' : tolerance - } - - #DataProcessor.__init__(self, **kwargs) - super(Normalizer, self).__init__(**kwargs) - if not flat_field is None: - self.set_flat_field(flat_field) - if not dark_field is None: - self.set_dark_field(dark_field) - - def check_input(self, dataset): - if dataset.number_of_dimensions == 3 or\ - dataset.number_of_dimensions == 2: - return True - else: - raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ - .format(dataset.number_of_dimensions)) - - def set_dark_field(self, df): - if type(df) is numpy.ndarray: - if len(numpy.shape(df)) == 3: - raise ValueError('Dark Field should be 2D') - elif len(numpy.shape(df)) == 2: - self.dark_field = df - elif issubclass(type(df), DataContainer): - self.dark_field = self.set_dark_field(df.as_array()) - - def set_flat_field(self, df): - if type(df) is numpy.ndarray: - if len(numpy.shape(df)) == 3: - raise ValueError('Flat Field should be 2D') - elif len(numpy.shape(df)) == 2: - self.flat_field = df - elif issubclass(type(df), DataContainer): - self.flat_field = self.set_flat_field(df.as_array()) - - @staticmethod - def normalize_projection(projection, flat, dark, tolerance): - a = (projection - dark) - b = (flat-dark) - with numpy.errstate(divide='ignore', invalid='ignore'): - c = numpy.true_divide( a, b ) - c[ ~ numpy.isfinite( c )] = tolerance # set to not zero if 0/0 - return c - - @staticmethod - def estimate_normalised_error(projection, flat, dark, delta_flat, delta_dark): - '''returns the estimated relative error of the normalised projection - - n = (projection - dark) / (flat - dark) - Dn/n = (flat-dark + projection-dark)/((flat-dark)*(projection-dark))*(Df/f + Dd/d) - ''' - a = (projection - dark) - b = (flat-dark) - df = delta_flat / flat - dd = delta_dark / dark - rel_norm_error = (b + a) / (b * a) * (df + dd) - return rel_norm_error - - def process(self, out=None): - - projections = self.get_input() - dark = self.dark_field - flat = self.flat_field - - if projections.number_of_dimensions == 3: - if not (projections.shape[1:] == dark.shape and \ - projections.shape[1:] == flat.shape): - raise ValueError('Flats/Dark and projections size do not match.') - - - a = numpy.asarray( - [ Normalizer.normalize_projection( - projection, flat, dark, self.tolerance) \ - for projection in projections.as_array() ] - ) - elif projections.number_of_dimensions == 2: - a = Normalizer.normalize_projection(projections.as_array(), - flat, dark, self.tolerance) - y = type(projections)( a , True, - dimension_labels=projections.dimension_labels, - geometry=projections.geometry) - return y - - -class CenterOfRotationFinder(DataProcessor): - '''Processor to find the center of rotation in a parallel beam experiment - - This processor read in a AcquisitionDataSet and finds the center of rotation - based on Nghia Vo's method. https://doi.org/10.1364/OE.22.019078 - - Input: AcquisitionDataSet - - Output: float. center of rotation in pixel coordinate - ''' - - def __init__(self): - kwargs = { - - } - - #DataProcessor.__init__(self, **kwargs) - super(CenterOfRotationFinder, self).__init__(**kwargs) - - def check_input(self, dataset): - if dataset.number_of_dimensions == 3: - if dataset.geometry.geom_type == 'parallel': - return True - else: - raise ValueError('{0} is suitable only for parallel beam geometry'\ - .format(self.__class__.__name__)) - else: - raise ValueError("Expected input dimensions is 3, got {0}"\ - .format(dataset.number_of_dimensions)) - - - # ######################################################################### - # Copyright (c) 2015, UChicago Argonne, LLC. All rights reserved. # - # # - # Copyright 2015. UChicago Argonne, LLC. This software was produced # - # under U.S. Government contract DE-AC02-06CH11357 for Argonne National # - # Laboratory (ANL), which is operated by UChicago Argonne, LLC for the # - # U.S. Department of Energy. The U.S. Government has rights to use, # - # reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR # - # UChicago Argonne, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR # - # ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is # - # modified to produce derivative works, such modified software should # - # be clearly marked, so as not to confuse it with the version available # - # from ANL. # - # # - # Additionally, redistribution and use in source and binary forms, with # - # or without modification, are permitted provided that the following # - # conditions are met: # - # # - # * Redistributions of source code must retain the above copyright # - # notice, this list of conditions and the following disclaimer. # - # # - # * Redistributions in binary form must reproduce the above copyright # - # notice, this list of conditions and the following disclaimer in # - # the documentation and/or other materials provided with the # - # distribution. # - # # - # * Neither the name of UChicago Argonne, LLC, Argonne National # - # Laboratory, ANL, the U.S. Government, nor the names of its # - # contributors may be used to endorse or promote products derived # - # from this software without specific prior written permission. # - # # - # THIS SOFTWARE IS PROVIDED BY UChicago Argonne, LLC AND CONTRIBUTORS # - # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # - # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # - # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UChicago # - # Argonne, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # - # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # - # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # - # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # - # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # - # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # - # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # - # POSSIBILITY OF SUCH DAMAGE. # - # ######################################################################### - - @staticmethod - def as_ndarray(arr, dtype=None, copy=False): - if not isinstance(arr, numpy.ndarray): - arr = numpy.array(arr, dtype=dtype, copy=copy) - return arr - - @staticmethod - def as_dtype(arr, dtype, copy=False): - if not arr.dtype == dtype: - arr = numpy.array(arr, dtype=dtype, copy=copy) - return arr - - @staticmethod - def as_float32(arr): - arr = CenterOfRotationFinder.as_ndarray(arr, numpy.float32) - return CenterOfRotationFinder.as_dtype(arr, numpy.float32) - - - - - @staticmethod - def find_center_vo(tomo, ind=None, smin=-40, smax=40, srad=10, step=0.5, - ratio=2., drop=20): - """ - Find rotation axis location using Nghia Vo's method. :cite:`Vo:14`. - - Parameters - ---------- - tomo : ndarray - 3D tomographic data. - ind : int, optional - Index of the slice to be used for reconstruction. - smin, smax : int, optional - Reference to the horizontal center of the sinogram. - srad : float, optional - Fine search radius. - step : float, optional - Step of fine searching. - ratio : float, optional - The ratio between the FOV of the camera and the size of object. - It's used to generate the mask. - drop : int, optional - Drop lines around vertical center of the mask. - - Returns - ------- - float - Rotation axis location. - - Notes - ----- - The function may not yield a correct estimate, if: - - - the sample size is bigger than the field of view of the camera. - In this case the ``ratio`` argument need to be set larger - than the default of 2.0. - - - there is distortion in the imaging hardware. If there's - no correction applied, the center of the projection image may - yield a better estimate. - - - the sample contrast is weak. Paganin's filter need to be applied - to overcome this. - - - the sample was changed during the scan. - """ - tomo = CenterOfRotationFinder.as_float32(tomo) - - if ind is None: - ind = tomo.shape[1] // 2 - _tomo = tomo[:, ind, :] - - - - # Reduce noise by smooth filters. Use different filters for coarse and fine search - _tomo_cs = ndimage.filters.gaussian_filter(_tomo, (3, 1)) - _tomo_fs = ndimage.filters.median_filter(_tomo, (2, 2)) - - # Coarse and fine searches for finding the rotation center. - if _tomo.shape[0] * _tomo.shape[1] > 4e6: # If data is large (>2kx2k) - #_tomo_coarse = downsample(numpy.expand_dims(_tomo_cs,1), level=2)[:, 0, :] - #init_cen = _search_coarse(_tomo_coarse, smin, smax, ratio, drop) - #fine_cen = _search_fine(_tomo_fs, srad, step, init_cen*4, ratio, drop) - init_cen = CenterOfRotationFinder._search_coarse(_tomo_cs, smin, - smax, ratio, drop) - fine_cen = CenterOfRotationFinder._search_fine(_tomo_fs, srad, - step, init_cen, - ratio, drop) - else: - init_cen = CenterOfRotationFinder._search_coarse(_tomo_cs, - smin, smax, - ratio, drop) - fine_cen = CenterOfRotationFinder._search_fine(_tomo_fs, srad, - step, init_cen, - ratio, drop) - - #logger.debug('Rotation center search finished: %i', fine_cen) - return fine_cen - - - @staticmethod - def _search_coarse(sino, smin, smax, ratio, drop): - """ - Coarse search for finding the rotation center. - """ - (Nrow, Ncol) = sino.shape - centerfliplr = (Ncol - 1.0) / 2.0 - - # Copy the sinogram and flip left right, the purpose is to - # make a full [0;2Pi] sinogram - _copy_sino = numpy.fliplr(sino[1:]) - - # This image is used for compensating the shift of sinogram 2 - temp_img = numpy.zeros((Nrow - 1, Ncol), dtype='float32') - temp_img[:] = sino[-1] - - # Start coarse search in which the shift step is 1 - listshift = numpy.arange(smin, smax + 1) - listmetric = numpy.zeros(len(listshift), dtype='float32') - mask = CenterOfRotationFinder._create_mask(2 * Nrow - 1, Ncol, - 0.5 * ratio * Ncol, drop) - for i in listshift: - _sino = numpy.roll(_copy_sino, i, axis=1) - if i >= 0: - _sino[:, 0:i] = temp_img[:, 0:i] - else: - _sino[:, i:] = temp_img[:, i:] - listmetric[i - smin] = numpy.sum(numpy.abs(numpy.fft.fftshift( - #pyfftw.interfaces.numpy_fft.fft2( - # numpy.vstack((sino, _sino))) - numpy.fft.fft2(numpy.vstack((sino, _sino))) - )) * mask) - minpos = numpy.argmin(listmetric) - return centerfliplr + listshift[minpos] / 2.0 - - @staticmethod - def _search_fine(sino, srad, step, init_cen, ratio, drop): - """ - Fine search for finding the rotation center. - """ - Nrow, Ncol = sino.shape - centerfliplr = (Ncol + 1.0) / 2.0 - 1.0 - # Use to shift the sinogram 2 to the raw CoR. - shiftsino = numpy.int16(2 * (init_cen - centerfliplr)) - _copy_sino = numpy.roll(numpy.fliplr(sino[1:]), shiftsino, axis=1) - if init_cen <= centerfliplr: - lefttake = numpy.int16(numpy.ceil(srad + 1)) - righttake = numpy.int16(numpy.floor(2 * init_cen - srad - 1)) - else: - lefttake = numpy.int16(numpy.ceil( - init_cen - (Ncol - 1 - init_cen) + srad + 1)) - righttake = numpy.int16(numpy.floor(Ncol - 1 - srad - 1)) - Ncol1 = righttake - lefttake + 1 - mask = CenterOfRotationFinder._create_mask(2 * Nrow - 1, Ncol1, - 0.5 * ratio * Ncol, drop) - numshift = numpy.int16((2 * srad) / step) + 1 - listshift = numpy.linspace(-srad, srad, num=numshift) - listmetric = numpy.zeros(len(listshift), dtype='float32') - factor1 = numpy.mean(sino[-1, lefttake:righttake]) - num1 = 0 - for i in listshift: - _sino = ndimage.interpolation.shift( - _copy_sino, (0, i), prefilter=False) - factor2 = numpy.mean(_sino[0,lefttake:righttake]) - _sino = _sino * factor1 / factor2 - sinojoin = numpy.vstack((sino, _sino)) - listmetric[num1] = numpy.sum(numpy.abs(numpy.fft.fftshift( - #pyfftw.interfaces.numpy_fft.fft2( - # sinojoin[:, lefttake:righttake + 1]) - numpy.fft.fft2(sinojoin[:, lefttake:righttake + 1]) - )) * mask) - num1 = num1 + 1 - minpos = numpy.argmin(listmetric) - return init_cen + listshift[minpos] / 2.0 - - @staticmethod - def _create_mask(nrow, ncol, radius, drop): - du = 1.0 / ncol - dv = (nrow - 1.0) / (nrow * 2.0 * numpy.pi) - centerrow = numpy.ceil(nrow / 2) - 1 - centercol = numpy.ceil(ncol / 2) - 1 - # added by Edoardo Pasca - centerrow = int(centerrow) - centercol = int(centercol) - mask = numpy.zeros((nrow, ncol), dtype='float32') - for i in range(nrow): - num1 = numpy.round(((i - centerrow) * dv / radius) / du) - (p1, p2) = numpy.int16(numpy.clip(numpy.sort( - (-num1 + centercol, num1 + centercol)), 0, ncol - 1)) - mask[i, p1:p2 + 1] = numpy.ones(p2 - p1 + 1, dtype='float32') - if drop < centerrow: - mask[centerrow - drop:centerrow + drop + 1, - :] = numpy.zeros((2 * drop + 1, ncol), dtype='float32') - mask[:,centercol-1:centercol+2] = numpy.zeros((nrow, 3), dtype='float32') - return mask - - def process(self, out=None): - - projections = self.get_input() - - cor = CenterOfRotationFinder.find_center_vo(projections.as_array()) - - return cor - - -class AcquisitionDataPadder(DataProcessor): - '''Normalization based on flat and dark - - This processor read in a AcquisitionData and normalises it based on - the instrument reading with and without incident photons or neutrons. - - Input: AcquisitionData - Parameter: 2D projection with flat field (or stack) - 2D projection with dark field (or stack) - Output: AcquisitionDataSetn - ''' - - def __init__(self, - center_of_rotation = None, - acquisition_geometry = None, - pad_value = 1e-5): - kwargs = { - 'acquisition_geometry' : acquisition_geometry, - 'center_of_rotation' : center_of_rotation, - 'pad_value' : pad_value - } - - super(AcquisitionDataPadder, self).__init__(**kwargs) - - def check_input(self, dataset): - if self.acquisition_geometry is None: - self.acquisition_geometry = dataset.geometry - if dataset.number_of_dimensions == 3: - return True - else: - raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ - .format(dataset.number_of_dimensions)) - - def process(self, out=None): - projections = self.get_input() - w = projections.get_dimension_size('horizontal') - delta = w - 2 * self.center_of_rotation - - padded_width = int ( - numpy.ceil(abs(delta)) + w - ) - delta_pix = padded_width - w - - voxel_per_pixel = 1 - geom = pbalg.pb_setup_geometry_from_acquisition(projections.as_array(), - self.acquisition_geometry.angles, - self.center_of_rotation, - voxel_per_pixel ) - - padded_geometry = self.acquisition_geometry.clone() - - padded_geometry.pixel_num_h = geom['n_h'] - padded_geometry.pixel_num_v = geom['n_v'] - - delta_pix_h = padded_geometry.pixel_num_h - self.acquisition_geometry.pixel_num_h - delta_pix_v = padded_geometry.pixel_num_v - self.acquisition_geometry.pixel_num_v - - if delta_pix_h == 0: - delta_pix_h = delta_pix - padded_geometry.pixel_num_h = padded_width - #initialize a new AcquisitionData with values close to 0 - out = AcquisitionData(geometry=padded_geometry) - out = out + self.pad_value - - - #pad in the horizontal-vertical plane -> slice on angles - if delta > 0: - #pad left of middle - command = "out.array[" - for i in range(out.number_of_dimensions): - if out.dimension_labels[i] == 'horizontal': - value = '{0}:{1}'.format(delta_pix_h, delta_pix_h+w) - command = command + str(value) - else: - if out.dimension_labels[i] == 'vertical' : - value = '{0}:'.format(delta_pix_v) - command = command + str(value) - else: - command = command + ":" - if i < out.number_of_dimensions -1: - command = command + ',' - command = command + '] = projections.array' - #print (command) - else: - #pad right of middle - command = "out.array[" - for i in range(out.number_of_dimensions): - if out.dimension_labels[i] == 'horizontal': - value = '{0}:{1}'.format(0, w) - command = command + str(value) - else: - if out.dimension_labels[i] == 'vertical' : - value = '{0}:'.format(delta_pix_v) - command = command + str(value) - else: - command = command + ":" - if i < out.number_of_dimensions -1: - command = command + ',' - command = command + '] = projections.array' - #print (command) - #cleaned = eval(command) - exec(command) - return out \ No newline at end of file diff --git a/Wrappers/Python/setup.py b/Wrappers/Python/setup.py index 78f88dd..95c0dea 100644 --- a/Wrappers/Python/setup.py +++ b/Wrappers/Python/setup.py @@ -36,7 +36,7 @@ setup( 'ccpi.optimisation.operators', 'ccpi.optimisation.algorithms', 'ccpi.optimisation.functions', - 'ccpi.optimisation.processors', + 'ccpi.processors', 'ccpi.contrib','ccpi.contrib.optimisation', 'ccpi.contrib.optimisation.algorithms'], diff --git a/Wrappers/Python/test/test_DataProcessor.py b/Wrappers/Python/test/test_DataProcessor.py index a5bd9c6..3e6a83e 100755 --- a/Wrappers/Python/test/test_DataProcessor.py +++ b/Wrappers/Python/test/test_DataProcessor.py @@ -31,6 +31,9 @@ class TestDataProcessor(unittest.TestCase): cf = CenterOfRotationFinder() cf.set_input(ad) print ("Center of rotation", cf.get_output()) + self.assertAlmostEqual(86.25, cf.get_output()) + def test_Normalizer(self): + pass -- cgit v1.2.3 From 590a593b692b07af76e3e87a95e4cc01a2843aea Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 30 Apr 2019 14:23:50 +0100 Subject: change numpy versions takes into consideration #271 --- Wrappers/Python/conda-recipe/conda_build_config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/conda-recipe/conda_build_config.yaml b/Wrappers/Python/conda-recipe/conda_build_config.yaml index 30c8e9d..393ae18 100644 --- a/Wrappers/Python/conda-recipe/conda_build_config.yaml +++ b/Wrappers/Python/conda-recipe/conda_build_config.yaml @@ -4,5 +4,5 @@ python: - 3.6 numpy: # TODO investigage, as it doesn't currently build with cvxp, requires >1.14 + - 1.11 - 1.12 - - 1.15 -- cgit v1.2.3 From cf0efd0a729be773cb6ae9a0f3700267447e2674 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 30 Apr 2019 14:52:38 +0100 Subject: minor fix demos --- Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py | 2 +- Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py | 2 +- Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py | 2 +- Wrappers/Python/wip/Demos/PDHG_Tikhonov_Tomo2D.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py b/Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py index 26578bb..49d4db6 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py +++ b/Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py @@ -51,7 +51,7 @@ detectors = N angles = np.linspace(0, np.pi, N, dtype=np.float32) ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'cpu') +Aop = AstraProjectorSimple(ig, ag, 'gpu') sin = Aop.direct(data) # Create noisy data. Apply Poisson noise diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py index 58978ae..4903c44 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py @@ -48,7 +48,7 @@ noisy_data = ImageData(n1) # Regularisation Parameter alpha = 2 -method = '0' +method = '1' if method == '0': diff --git a/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py b/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py index 3f275e2..041d4ee 100644 --- a/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py +++ b/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py @@ -47,7 +47,7 @@ noisy_data = ImageData(n1) # Regularisation Parameter alpha = 4 -method = '1' +method = '0' if method == '0': diff --git a/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Tomo2D.py b/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Tomo2D.py index 5c03362..f17c4fe 100644 --- a/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Tomo2D.py +++ b/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Tomo2D.py @@ -43,7 +43,7 @@ detectors = N angles = np.linspace(0, np.pi, N, dtype=np.float32) ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'cpu') +Aop = AstraProjectorSimple(ig, ag, 'gpu') sin = Aop.direct(data) # Create noisy data. Apply Gaussian noise -- cgit v1.2.3 From f0e113082137af07d3a184b238f6b50bee395ff2 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 30 Apr 2019 14:53:09 +0100 Subject: add FISTA demos --- Wrappers/Python/wip/Demos/FISTA_vs_CGLS.py | 121 +++++++++++++++++++++++++++++ Wrappers/Python/wip/Demos/FISTA_vs_PDHG.py | 120 ++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 Wrappers/Python/wip/Demos/FISTA_vs_CGLS.py create mode 100644 Wrappers/Python/wip/Demos/FISTA_vs_PDHG.py diff --git a/Wrappers/Python/wip/Demos/FISTA_vs_CGLS.py b/Wrappers/Python/wip/Demos/FISTA_vs_CGLS.py new file mode 100644 index 0000000..df3c153 --- /dev/null +++ b/Wrappers/Python/wip/Demos/FISTA_vs_CGLS.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import FISTA, CGLS + +from ccpi.optimisation.operators import Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, FunctionOperatorComposition +from skimage.util import random_noise +from ccpi.astra.ops import AstraProjectorSimple + +#%% + +N = 75 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'gpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Gaussian noise + +np.random.seed(10) +noisy_data = sin + AcquisitionData(np.random.normal(0, 3, sin.shape)) + +# Regularisation Parameter + +operator = Gradient(ig) + +fidelity = FunctionOperatorComposition(L2NormSquared(b=noisy_data), Aop) +regularizer = ZeroFunction() + +x_init = ig.allocate() + +## Setup and run the FISTA algorithm +opt = {'tol': 1e-4, 'memopt':True} +fista = FISTA(x_init=x_init , f=fidelity, g=regularizer, opt=opt) +fista.max_iteration = 20 +fista.update_objective_interval = 1 +fista.run(20, verbose=True) + +## Setup and run the CGLS algorithm +cgls = CGLS(x_init=x_init, operator=Aop, data=noisy_data) +cgls.max_iteration = 20 +cgls.run(20, verbose=True) + +diff = np.abs(fista.get_output().as_array() - cgls.get_output().as_array()) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(fista.get_output().as_array()) +plt.title('FISTA reconstruction') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(cgls.get_output().as_array()) +plt.title('CGLS reconstruction') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(diff) +plt.title('Difference reconstruction') +plt.colorbar() +plt.show() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Wrappers/Python/wip/Demos/FISTA_vs_PDHG.py b/Wrappers/Python/wip/Demos/FISTA_vs_PDHG.py new file mode 100644 index 0000000..b7777ef --- /dev/null +++ b/Wrappers/Python/wip/Demos/FISTA_vs_PDHG.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import FISTA, PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient, Identity +from ccpi.optimisation.functions import L2NormSquared, L1Norm, \ + MixedL21Norm, FunctionOperatorComposition, BlockFunction, ZeroFunction + +from skimage.util import random_noise + +# Create phantom for TV Gaussian denoising +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Add Gaussian noise +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Regularisation Parameter +alpha = 5 + +operator = Gradient(ig) + +fidelity = L1Norm(b=noisy_data) +regulariser = FunctionOperatorComposition(alpha * L2NormSquared(), operator) + +x_init = ig.allocate() + +## Setup and run the PDHG algorithm +opt = {'tol': 1e-4, 'memopt':True} +fista = FISTA(x_init=x_init , f=regulariser, g=fidelity, opt=opt) +fista.max_iteration = 2000 +fista.update_objective_interval = 50 +fista.run(2000, verbose=True) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(fista.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() + +# Compare with PDHG +# Create operators +op1 = Gradient(ig) +op2 = Identity(ig, ag) + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) +f = BlockFunction(alpha * L2NormSquared(), fidelity) +g = ZeroFunction() + +## Compute operator Norm +normK = operator.norm() +# +## Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +# +# +## Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) +# +#%% +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(fista.get_output().as_array()) +plt.title('FISTA') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(pdhg.get_output().as_array()) +plt.title('PDHG') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(np.abs(pdhg.get_output().as_array()-fista.get_output().as_array())) +plt.title('Diff FISTA-PDHG') +plt.colorbar() +plt.show() + + -- cgit v1.2.3 From ab97687bd091f523beaeca897a98164318ce3c52 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 30 Apr 2019 14:56:27 +0100 Subject: test IMAT wb recon --- .../Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Wrappers/Python/wip/Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py b/Wrappers/Python/wip/Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py index 10d15fa..e67bdb1 100644 --- a/Wrappers/Python/wip/Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py +++ b/Wrappers/Python/wip/Demos/IMAT_Reconstruction/TV_WhiteBeam_reconstruction.py @@ -56,7 +56,7 @@ with open("golden_angles.txt") as f: angles_string = [line.rstrip() for line in f] angles = numpy.array(angles_string).astype(float) agWB = AcquisitionGeometry('parallel', '2D', angles * numpy.pi / 180, pixh) -op_WB = AstraProjectorSimple(igWB, agWB, 'cpu') +op_WB = AstraProjectorSimple(igWB, agWB, 'gpu') sinogram_aqdata = AcquisitionData(sinogram_wb, agWB) # BackProjection @@ -71,7 +71,7 @@ plt.show() #%% # Regularisation Parameter -alpha = 10 +alpha = 2000 # Create operators op1 = Gradient(igWB) @@ -89,7 +89,7 @@ f = BlockFunction(f1, f2) g = ZeroFunction() -diag_precon = True +diag_precon = False if diag_precon: @@ -114,8 +114,8 @@ else: ## Primal & dual stepsizes -sigma = 0.1 -tau = 1/(sigma*normK**2) +#sigma = 0.1 +#tau = 1/(sigma*normK**2) # # ## Setup and run the PDHG algorithm -- cgit v1.2.3 From f0204684c5d9c731fcc9a49ffab2a49638a38ef1 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Wed, 1 May 2019 10:58:34 +0100 Subject: update demo --- Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py | 201 ++++++++-------------- 1 file changed, 71 insertions(+), 130 deletions(-) diff --git a/Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py b/Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py index 9de48a5..854f645 100644 --- a/Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py +++ b/Wrappers/Python/demos/pdhg_TV_tomography2Dccpi.py @@ -20,11 +20,17 @@ from ccpi.optimisation.operators import BlockOperator, Identity, Gradient from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ MixedL21Norm, BlockFunction, ScaledFunction -#from ccpi.astra.ops import AstraProjectorSimple -#from ccpi.plugins.ops import CCPiProjectorSimple from ccpi.plugins.operators import CCPiProjectorSimple -#from skimage.util import random_noise from timeit import default_timer as timer +from ccpi.reconstruction.parallelbeam import alg as pbalg +import os + +try: + import tomophantom + from tomophantom import TomoP3D + no_tomophantom = False +except ImportError as ie: + no_tomophantom = True #%% @@ -52,32 +58,11 @@ N = 75 vert = 4 ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, voxel_num_z=vert) -data = ig.allocate() -Phantom = data -# Populate image data by looping over and filling slices -i = 0 -while i < vert: - if vert > 1: - x = Phantom.subset(vertical=i).array - else: - x = Phantom.array - x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 - x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 0.98 - if vert > 1 : - Phantom.fill(x, vertical=i) - i += 1 - -#%% -#detectors = N -#angles = np.linspace(0,np.pi,100) -#angles_num = 100 -angles_num = N +angles_num = 100 det_w = 1.0 det_num = N -angles = np.linspace(0,np.pi,angles_num,endpoint=False,dtype=np.float32)*\ - 180/np.pi angles = np.linspace(-90.,90.,N, dtype=np.float32) # Inputs: Geometry, 2D or 3D, angles, horz detector pixel count, # horz detector pixel size, vert detector pixel count, @@ -90,73 +75,59 @@ ag = AcquisitionGeometry('parallel', vert, det_w) -from ccpi.reconstruction.parallelbeam import alg as pbalg -from ccpi.plugins.processors import setupCCPiGeometries -def ssetupCCPiGeometries(ig, ag, counter): +#no_tomophantom = True +if no_tomophantom: + data = ig.allocate() + Phantom = data + # Populate image data by looping over and filling slices + i = 0 + while i < vert: + if vert > 1: + x = Phantom.subset(vertical=i).array + else: + x = Phantom.array + x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 + x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 0.98 + if vert > 1 : + Phantom.fill(x, vertical=i) + i += 1 - #vg = ImageGeometry(voxel_num_x=voxel_num_x,voxel_num_y=voxel_num_y, voxel_num_z=voxel_num_z) - #Phantom_ccpi = ImageData(geometry=vg, - # dimension_labels=['horizontal_x','horizontal_y','vertical']) - ##.subset(['horizontal_x','horizontal_y','vertical']) - ## ask the ccpi code what dimensions it would like - Phantom_ccpi = ig.allocate(dimension_labels=[ImageGeometry.HORIZONTAL_X, - ImageGeometry.HORIZONTAL_Y, - ImageGeometry.VERTICAL]) + Aop = CCPiProjectorSimple(ig, ag, 'cpu') + sin = Aop.direct(data) +else: + + model = 13 # select a model number from the library + N_size = N # Define phantom dimensions using a scalar value (cubic phantom) + path = os.path.dirname(tomophantom.__file__) + path_library3D = os.path.join(path, "Phantom3DLibrary.dat") + #This will generate a N_size x N_size x N_size phantom (3D) + phantom_tm = TomoP3D.Model(model, N_size, path_library3D) - voxel_per_pixel = 1 - angles = ag.angles - geoms = pbalg.pb_setup_geometry_from_image(Phantom_ccpi.as_array(), - angles, - voxel_per_pixel ) + #%% + Horiz_det = int(np.sqrt(2)*N_size) # detector column count (horizontal) + Vert_det = N_size # detector row count (vertical) (no reason for it to be > N) + #angles_num = int(0.5*np.pi*N_size); # angles number + #angles = np.linspace(0.0,179.9,angles_num,dtype='float32') # in degrees - pg = AcquisitionGeometry('parallel', - '3D', - angles, - geoms['n_h'], 1.0, - geoms['n_v'], 1.0 #2D in 3D is a slice 1 pixel thick - ) + print ("Building 3D analytical projection data with TomoPhantom") + projData3D_analyt = TomoP3D.ModelSino(model, + N_size, + Horiz_det, + Vert_det, + angles, + path_library3D) - center_of_rotation = Phantom_ccpi.get_dimension_size('horizontal_x') / 2 - #ad = AcquisitionData(geometry=pg,dimension_labels=['angle','vertical','horizontal']) - ad = pg.allocate(dimension_labels=[AcquisitionGeometry.ANGLE, - AcquisitionGeometry.VERTICAL, - AcquisitionGeometry.HORIZONTAL]) - geoms_i = pbalg.pb_setup_geometry_from_acquisition(ad.as_array(), - angles, - center_of_rotation, - voxel_per_pixel ) + # tomophantom outputs in [vert,angles,horiz] + # we want [angle,vert,horiz] + data = np.transpose(projData3D_analyt, [1,0,2]) + ag.pixel_num_h = Horiz_det + ag.pixel_num_v = Vert_det + sin = ag.allocate() + sin.fill(data) + ig.voxel_num_y = Vert_det - counter+=1 + Aop = CCPiProjectorSimple(ig, ag, 'cpu') - if counter < 4: - print (geoms, geoms_i) - if (not ( geoms_i == geoms )): - print ("not equal and {} {} {}".format(counter, geoms['output_volume_z'], geoms_i['output_volume_z'])) - X = max(geoms['output_volume_x'], geoms_i['output_volume_x']) - Y = max(geoms['output_volume_y'], geoms_i['output_volume_y']) - Z = max(geoms['output_volume_z'], geoms_i['output_volume_z']) - return setupCCPiGeometries(X,Y,Z,angles, counter) - else: - print ("happy now {} {} {}".format(counter, geoms['output_volume_z'], geoms_i['output_volume_z'])) - - return geoms - else: - return geoms_i - - - -#voxel_num_x, voxel_num_y, voxel_num_z, angles, counter -print ("###############################################") -print (ig) -print (ag) -g = setupCCPiGeometries(ig, ag, 0) -print (g) -print ("###############################################") -print ("###############################################") -#ag = AcquisitionGeometry('parallel','2D',angles, detectors) -#Aop = AstraProjectorSimple(ig, ag, 'cpu') -Aop = CCPiProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) plt.imshow(sin.subset(vertical=0).as_array()) plt.title('Sinogram') @@ -228,70 +199,40 @@ opt1 = {'niter':niter, 'memopt': True} -pdhg1 = PDHG(f=f,g=g, operator=operator, tau=tau, sigma=sigma, memopt=True, max_iteration=niter) +pdhg1 = PDHG(f=f,g=g, operator=operator, tau=tau, sigma=sigma, max_iteration=niter) #pdhg1.max_iteration = 2000 pdhg1.update_objective_interval = 100 -pdhg2 = PDHG(f=f,g=g, operator=operator, tau=tau, sigma=sigma, memopt=False) -pdhg2.max_iteration = 2000 -pdhg2.update_objective_interval = 100 t1_old = timer() resold, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) t2_old = timer() -print ("memopt = False, shouldn't matter") pdhg1.run(niter) print (sum(pdhg1.timing)) res = pdhg1.get_output().subset(vertical=0) -print (pdhg1.objective) -t3 = timer() -#res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) -print ("memopt = True, shouldn't matter") -pdhg2.run(niter) -print (sum(pdhg2.timing)) -res1 = pdhg2.get_output().subset(vertical=0) -t4 = timer() -# -print ("No memopt in {}s, memopt in {}/{}s old {}s".format(sum(pdhg1.timing), - sum(pdhg2.timing),t4-t3, t2_old-t1_old)) - -t1_old = timer() -resold1, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) -t2_old = timer() #%% plt.figure() -plt.subplot(2,3,1) +plt.subplot(1,4,1) plt.imshow(res.as_array()) -plt.title('no memopt') -plt.colorbar() -plt.subplot(2,3,2) -plt.imshow(res1.as_array()) -plt.title('memopt') +plt.title('Algorithm') plt.colorbar() -plt.subplot(2,3,3) -plt.imshow((res1 - resold1.subset(vertical=0)).abs().as_array()) -plt.title('diff') -plt.colorbar() -plt.subplot(2,3,4) +plt.subplot(1,4,2) plt.imshow(resold.subset(vertical=0).as_array()) -plt.title('old nomemopt') +plt.title('function') plt.colorbar() -plt.subplot(2,3,5) -plt.imshow(resold1.subset(vertical=0).as_array()) -plt.title('old memopt') -plt.colorbar() -plt.subplot(2,3,6) -plt.imshow((resold1 - resold).subset(vertical=0).as_array()) -plt.title('diff old') +plt.subplot(1,4,3) +plt.imshow((res - resold.subset(vertical=0)).abs().as_array()) +plt.title('diff') plt.colorbar() -#plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'memopt') -#plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'no memopt') -#plt.legend() +plt.subplot(1,4,4) +plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'Algorithm') +plt.plot(np.linspace(0,N,N), resold.subset(vertical=0).as_array()[int(N/2),:], label = 'function') +plt.legend() plt.show() # -print ("Time: No memopt in {}s, \n Time: Memopt in {}s ".format(sum(pdhg1.timing), t4 -t3)) -diff = (res1 - res).abs().as_array().max() +print ("Time: No memopt in {}s, \n Time: Memopt in {}s ".format(sum(pdhg1.timing), t2_old -t1_old)) +diff = (res - resold.subset(vertical=0)).abs().as_array().max() # print(" Max of abs difference is {}".format(diff)) -- cgit v1.2.3 From 819ef5dd4f9429971729b62ed6cf9205e841d20a Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 1 May 2019 16:34:30 +0100 Subject: comparison algs LS prob --- .../Python/wip/Demos/LeastSq_CGLS_FISTA_PDHG.py | 154 +++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 Wrappers/Python/wip/Demos/LeastSq_CGLS_FISTA_PDHG.py diff --git a/Wrappers/Python/wip/Demos/LeastSq_CGLS_FISTA_PDHG.py b/Wrappers/Python/wip/Demos/LeastSq_CGLS_FISTA_PDHG.py new file mode 100644 index 0000000..97c71ba --- /dev/null +++ b/Wrappers/Python/wip/Demos/LeastSq_CGLS_FISTA_PDHG.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, CGLS, FISTA + +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, FunctionOperatorComposition +from ccpi.astra.ops import AstraProjectorSimple + +#%% + +N = 68 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +noisy_data = sin + + +#%% +############################################################################### +## Setup and run the CGLS algorithm + +x_init = ig.allocate() +cgls = CGLS(x_init=x_init, operator=Aop, data=noisy_data) +cgls.max_iteration = 500 +cgls.update_objective_interval = 50 +cgls.run(500, verbose=True) + +#%% +plt.imshow(cgls.get_output().as_array()) +#%% +############################################################################### +## Setup and run the PDHG algorithm + +operator = Aop +f = L2NormSquared(b = noisy_data) +g = ZeroFunction() + +## Compute operator Norm +normK = operator.norm() + +## Primal & dual stepsizes +sigma = 0.1 +tau = 1/(sigma*normK**2) + +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + + +#%% +############################################################################### +## Setup and run the FISTA algorithm + +fidelity = FunctionOperatorComposition(L2NormSquared(b=noisy_data), Aop) +regularizer = ZeroFunction() + +## Setup and run the FISTA algorithm +opt = {'memopt':True} +fista = FISTA(x_init=x_init , f=fidelity, g=regularizer, opt=opt) +fista.max_iteration = 2000 +fista.update_objective_interval = 200 +fista.run(2000, verbose=True) + +#%% Show results + +diff1 = pdhg.get_output() - cgls.get_output() +diff2 = fista.get_output() - cgls.get_output() + +print( diff1.norm()) +print( diff2.norm()) + +plt.figure(figsize=(10,10)) +plt.subplot(2,3,1) +plt.imshow(cgls.get_output().as_array()) +plt.title('CGLS reconstruction') +plt.subplot(2,3,2) +plt.imshow(pdhg.get_output().as_array()) +plt.title('PDHG reconstruction') +plt.subplot(2,3,3) +plt.imshow(fista.get_output().as_array()) +plt.title('FISTA reconstruction') +plt.subplot(2,3,4) +plt.imshow(diff1.abs().as_array()) +plt.title('Diff PDHG vs CGLS') +plt.colorbar() +plt.subplot(2,3,5) +plt.imshow(diff2.abs().as_array()) +plt.title('Diff FISTA vs CGLS') +plt.colorbar() +plt.show() + + + + + + + + + + + + + + + + + + + + + + +# +# +# +# +# +# +# +# -- cgit v1.2.3 From cd5bed436dea298e77202e7e198b131e59c00af3 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 1 May 2019 16:35:12 +0100 Subject: comparison algs --- Wrappers/Python/wip/Demos/FISTA_vs_CGLS.py | 30 ++++--- Wrappers/Python/wip/Demos/PDHG_vs_CGLS.py | 127 +++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 Wrappers/Python/wip/Demos/PDHG_vs_CGLS.py diff --git a/Wrappers/Python/wip/Demos/FISTA_vs_CGLS.py b/Wrappers/Python/wip/Demos/FISTA_vs_CGLS.py index df3c153..2dcaa89 100644 --- a/Wrappers/Python/wip/Demos/FISTA_vs_CGLS.py +++ b/Wrappers/Python/wip/Demos/FISTA_vs_CGLS.py @@ -44,17 +44,10 @@ detectors = N angles = np.linspace(0, np.pi, N, dtype=np.float32) ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'gpu') +Aop = AstraProjectorSimple(ig, ag, 'cpu') sin = Aop.direct(data) -# Create noisy data. Apply Gaussian noise - -np.random.seed(10) -noisy_data = sin + AcquisitionData(np.random.normal(0, 3, sin.shape)) - -# Regularisation Parameter - -operator = Gradient(ig) +noisy_data = sin fidelity = FunctionOperatorComposition(L2NormSquared(b=noisy_data), Aop) regularizer = ZeroFunction() @@ -64,16 +57,21 @@ x_init = ig.allocate() ## Setup and run the FISTA algorithm opt = {'tol': 1e-4, 'memopt':True} fista = FISTA(x_init=x_init , f=fidelity, g=regularizer, opt=opt) -fista.max_iteration = 20 -fista.update_objective_interval = 1 -fista.run(20, verbose=True) +fista.max_iteration = 500 +fista.update_objective_interval = 50 +fista.run(500, verbose=True) ## Setup and run the CGLS algorithm cgls = CGLS(x_init=x_init, operator=Aop, data=noisy_data) -cgls.max_iteration = 20 -cgls.run(20, verbose=True) +cgls.max_iteration = 500 +cgls.update_objective_interval = 50 +cgls.run(500, verbose=True) -diff = np.abs(fista.get_output().as_array() - cgls.get_output().as_array()) +diff = fista.get_output() - cgls.get_output() + + +#%% +print( diff.norm()) plt.figure(figsize=(15,15)) plt.subplot(3,1,1) @@ -85,7 +83,7 @@ plt.imshow(cgls.get_output().as_array()) plt.title('CGLS reconstruction') plt.colorbar() plt.subplot(3,1,3) -plt.imshow(diff) +plt.imshow(diff.abs().as_array()) plt.title('Difference reconstruction') plt.colorbar() plt.show() diff --git a/Wrappers/Python/wip/Demos/PDHG_vs_CGLS.py b/Wrappers/Python/wip/Demos/PDHG_vs_CGLS.py new file mode 100644 index 0000000..3155654 --- /dev/null +++ b/Wrappers/Python/wip/Demos/PDHG_vs_CGLS.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, CGLS + +from ccpi.optimisation.operators import Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, FunctionOperatorComposition +from skimage.util import random_noise +from ccpi.astra.ops import AstraProjectorSimple + +#%% + +N = 128 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +noisy_data = sin + +x_init = ig.allocate() + +## Setup and run the CGLS algorithm +cgls = CGLS(x_init=x_init, operator=Aop, data=noisy_data) +cgls.max_iteration = 500 +cgls.update_objective_interval = 50 +cgls.run(500, verbose=True) + +# Create BlockOperator +operator = Aop +f = 0.5 * L2NormSquared(b = noisy_data) +g = ZeroFunction() + +## Compute operator Norm +normK = operator.norm() + +## Primal & dual stepsizes +sigma = 0.1 +tau = 1/(sigma*normK**2) +# +# +## Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + +#%% + +diff = pdhg.get_output() - cgls.get_output() +print( diff.norm()) +# +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(pdhg.get_output().as_array()) +plt.title('PDHG reconstruction') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(cgls.get_output().as_array()) +plt.title('CGLS reconstruction') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(diff.abs().as_array()) +plt.title('Difference reconstruction') +plt.colorbar() +plt.show() + + + + + + + + + + + + + + + + + + + + + + +# +# +# +# +# +# +# +# -- cgit v1.2.3 From 525e6bbcd28cb8c04a41463bf7e9806dbd9b747e Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 1 May 2019 16:35:47 +0100 Subject: fix CGLS --- Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py b/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py index e65bc89..4d4843c 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py @@ -23,6 +23,7 @@ Created on Thu Feb 21 11:11:23 2019 """ from ccpi.optimisation.algorithms import Algorithm + class CGLS(Algorithm): '''Conjugate Gradient Least Squares algorithm @@ -83,4 +84,4 @@ class CGLS(Algorithm): self.d = s + beta*self.d def update_objective(self): - self.loss.append(self.r.squared_norm()) + self.loss.append(self.r.squared_norm()) \ No newline at end of file -- cgit v1.2.3 From af85032142d8280cafdd2e92389c9fcef5d27f0f Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 1 May 2019 16:36:14 +0100 Subject: tests with dot method --- Wrappers/Python/ccpi/framework/framework.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/framework.py b/Wrappers/Python/ccpi/framework/framework.py index 7516447..a8a0ab4 100755 --- a/Wrappers/Python/ccpi/framework/framework.py +++ b/Wrappers/Python/ccpi/framework/framework.py @@ -758,12 +758,33 @@ class DataContainer(object): def norm(self): '''return the euclidean norm of the DataContainer viewed as a vector''' return numpy.sqrt(self.squared_norm()) + + +# def dot(self, other, *args, **kwargs): +# '''return the inner product of 2 DataContainers viewed as vectors''' +# if self.shape == other.shape: +# return numpy.dot(self.as_array().ravel(), other.as_array().ravel()) +# else: +# raise ValueError('Shapes are not aligned: {} != {}'.format(self.shape, other.shape)) + def dot(self, other, *args, **kwargs): '''return the inner product of 2 DataContainers viewed as vectors''' + method = kwargs.get('method', 'reduce') if self.shape == other.shape: - return numpy.dot(self.as_array().ravel(), other.as_array().ravel()) + # return (self*other).sum() + if method == 'numpy': + return numpy.dot(self.as_array().ravel(), other.as_array()) + elif method == 'reduce': + # see https://github.com/vais-ral/CCPi-Framework/pull/273 + # notice that Python seems to be smart enough to use + # the appropriate type to hold the result of the reduction + sf = reduce(lambda x,y: x + y[0]*y[1], + zip(self.as_array().ravel(), + other.as_array().ravel()), + 0) + return sf else: - raise ValueError('Shapes are not aligned: {} != {}'.format(self.shape, other.shape)) + raise ValueError('Shapes are not aligned: {} != {}'.format(self.shape, other.shape)) -- cgit v1.2.3 From 5a00e2ec4690f73770ab19b32137519b54d7b1ab Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Fri, 3 May 2019 12:07:25 +0100 Subject: algs comparison --- Wrappers/Python/wip/Compare_Algs/FISTA_vs_PDHG.py | 117 ++++++++++++ .../wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py | 198 +++++++++++++++++++++ Wrappers/Python/wip/Compare_Algs/PDHG_vs_CGLS.py | 127 +++++++++++++ .../Python/wip/Compare_Algs/Tikhonov_CGLS_PDHG.py | 152 ++++++++++++++++ 4 files changed, 594 insertions(+) create mode 100644 Wrappers/Python/wip/Compare_Algs/FISTA_vs_PDHG.py create mode 100644 Wrappers/Python/wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py create mode 100644 Wrappers/Python/wip/Compare_Algs/PDHG_vs_CGLS.py create mode 100644 Wrappers/Python/wip/Compare_Algs/Tikhonov_CGLS_PDHG.py diff --git a/Wrappers/Python/wip/Compare_Algs/FISTA_vs_PDHG.py b/Wrappers/Python/wip/Compare_Algs/FISTA_vs_PDHG.py new file mode 100644 index 0000000..eb62761 --- /dev/null +++ b/Wrappers/Python/wip/Compare_Algs/FISTA_vs_PDHG.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" +Compare FISTA & PDHG classes + + +Problem: min_x alpha * ||\grad x ||^{2}_{2} + || x - g ||_{1} + + A: Projection operator + g: Sinogram + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import FISTA, PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient, Identity +from ccpi.optimisation.functions import L2NormSquared, L1Norm, \ + FunctionOperatorComposition, BlockFunction, ZeroFunction + +from skimage.util import random_noise + + +# Create Ground truth and noisy data + +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Regularisation Parameter +alpha = 5 + + +# Setup and run the FISTA algorithm +operator = Gradient(ig) +fidelity = L1Norm(b=noisy_data) +regulariser = FunctionOperatorComposition(alpha * L2NormSquared(), operator) + +x_init = ig.allocate() +opt = {'memopt':True} +fista = FISTA(x_init=x_init , f=regulariser, g=fidelity, opt=opt) +fista.max_iteration = 2000 +fista.update_objective_interval = 50 +fista.run(2000, verbose=False) + +# Setup and run the PDHG algorithm +op1 = Gradient(ig) +op2 = Identity(ig, ag) + +operator = BlockOperator(op1, op2, shape=(2,1) ) +f = BlockFunction(alpha * L2NormSquared(), fidelity) +g = ZeroFunction() + +normK = operator.norm() + +sigma = 1 +tau = 1/(sigma*normK**2) + +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000, verbose=False) + +#%% +# Show results + +plt.figure(figsize=(15,15)) + +plt.subplot(1,2,1) +plt.imshow(pdhg.get_output().as_array()) +plt.title('PDHG reconstruction') + +plt.subplot(1,2,2) +plt.imshow(fista.get_output().as_array()) +plt.title('FISTA reconstruction') + +plt.show() + +diff1 = pdhg.get_output() - fista.get_output() + +plt.imshow(diff1.abs().as_array()) +plt.title('Diff PDHG vs CGLS') +plt.colorbar() +plt.show() + + diff --git a/Wrappers/Python/wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py b/Wrappers/Python/wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py new file mode 100644 index 0000000..c877018 --- /dev/null +++ b/Wrappers/Python/wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" +Compare Least Squares minimization problem using FISTA, PDHG, CGLS classes +and Astra Built-in CGLS + +Problem: min_x || A x - g ||_{2}^{2} + + A: Projection operator + g: Sinogram + +""" + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, CGLS, FISTA + +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, FunctionOperatorComposition +from ccpi.astra.ops import AstraProjectorSimple +import astra + +# Create Ground truth phantom and Sinogram + +N = 128 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D', angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +noisy_data = sin + +# Setup and run Astra CGLS algorithm + +vol_geom = astra.create_vol_geom(N, N) +proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + +proj_id = astra.create_projector('strip', proj_geom, vol_geom) + +# Create a sinogram from a phantom +sinogram_id, sinogram = astra.create_sino(x, proj_id) + +# Create a data object for the reconstruction +rec_id = astra.data2d.create('-vol', vol_geom) + +cgls_astra = astra.astra_dict('CGLS') +cgls_astra['ReconstructionDataId'] = rec_id +cgls_astra['ProjectionDataId'] = sinogram_id +cgls_astra['ProjectorId'] = proj_id + +# Create the algorithm object from the configuration structure +alg_id = astra.algorithm.create(cgls_astra) + +astra.algorithm.run(alg_id, 1000) + +recon_cgls_astra = astra.data2d.get(rec_id) + +# Setup and run the CGLS algorithm + +x_init = ig.allocate() + +cgls = CGLS(x_init=x_init, operator=Aop, data=noisy_data) +cgls.max_iteration = 1000 +cgls.update_objective_interval = 200 +cgls.run(1000) + +# Setup and run the PDHG algorithm + +operator = Aop +f = L2NormSquared(b = noisy_data) +g = ZeroFunction() + +## Compute operator Norm +normK = operator.norm() + +## Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 1000 +pdhg.update_objective_interval = 200 +pdhg.run(1000) + +# Setup and run the FISTA algorithm + +fidelity = FunctionOperatorComposition(L2NormSquared(b=noisy_data), Aop) +regularizer = ZeroFunction() + +opt = {'memopt':True} +fista = FISTA(x_init=x_init , f=fidelity, g=regularizer, opt=opt) +fista.max_iteration = 1000 +fista.update_objective_interval = 200 +fista.run(1000, verbose=True) + +#%% Show results + +plt.figure(figsize=(15,15)) +plt.suptitle('Reconstructions ') + +plt.subplot(2,2,1) +plt.imshow(cgls.get_output().as_array()) +plt.title('CGLS reconstruction') + +plt.subplot(2,2,2) +plt.imshow(fista.get_output().as_array()) +plt.title('FISTA reconstruction') + +plt.subplot(2,2,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('PDHG reconstruction') + +plt.subplot(2,2,4) +plt.imshow(recon_cgls_astra) +plt.title('CGLS astra') + +#%% +diff1 = pdhg.get_output() - cgls.get_output() +diff2 = fista.get_output() - cgls.get_output() +diff3 = ImageData(recon_cgls_astra) - cgls.get_output() + +print( diff1.squared_norm()) +print( diff2.squared_norm()) +print( diff3.squared_norm()) + +plt.figure(figsize=(15,15)) + +plt.subplot(3,1,1) +plt.imshow(diff1.abs().as_array()) +plt.title('Diff PDHG vs CGLS') +plt.colorbar() + +plt.subplot(3,1,2) +plt.imshow(diff2.abs().as_array()) +plt.title('Diff FISTA vs CGLS') +plt.colorbar() + +plt.subplot(3,1,3) +plt.imshow(diff3.abs().as_array()) +plt.title('Diff CLGS astra vs CGLS') +plt.colorbar() + + + + + + + + + + + + + + + + + + + +# +# +# +# +# +# +# +# diff --git a/Wrappers/Python/wip/Compare_Algs/PDHG_vs_CGLS.py b/Wrappers/Python/wip/Compare_Algs/PDHG_vs_CGLS.py new file mode 100644 index 0000000..3155654 --- /dev/null +++ b/Wrappers/Python/wip/Compare_Algs/PDHG_vs_CGLS.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, CGLS + +from ccpi.optimisation.operators import Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, FunctionOperatorComposition +from skimage.util import random_noise +from ccpi.astra.ops import AstraProjectorSimple + +#%% + +N = 128 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +noisy_data = sin + +x_init = ig.allocate() + +## Setup and run the CGLS algorithm +cgls = CGLS(x_init=x_init, operator=Aop, data=noisy_data) +cgls.max_iteration = 500 +cgls.update_objective_interval = 50 +cgls.run(500, verbose=True) + +# Create BlockOperator +operator = Aop +f = 0.5 * L2NormSquared(b = noisy_data) +g = ZeroFunction() + +## Compute operator Norm +normK = operator.norm() + +## Primal & dual stepsizes +sigma = 0.1 +tau = 1/(sigma*normK**2) +# +# +## Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + +#%% + +diff = pdhg.get_output() - cgls.get_output() +print( diff.norm()) +# +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(pdhg.get_output().as_array()) +plt.title('PDHG reconstruction') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(cgls.get_output().as_array()) +plt.title('CGLS reconstruction') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(diff.abs().as_array()) +plt.title('Difference reconstruction') +plt.colorbar() +plt.show() + + + + + + + + + + + + + + + + + + + + + + +# +# +# +# +# +# +# +# diff --git a/Wrappers/Python/wip/Compare_Algs/Tikhonov_CGLS_PDHG.py b/Wrappers/Python/wip/Compare_Algs/Tikhonov_CGLS_PDHG.py new file mode 100644 index 0000000..984fca4 --- /dev/null +++ b/Wrappers/Python/wip/Compare_Algs/Tikhonov_CGLS_PDHG.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Compare Tikhonov with PDHG, CGLS classes + + +Problem: min_x alpha * ||\grad x ||^{2}_{2} + || A x - g ||_{2}^{2} + + A: Projection operator + g: Sinogram + +""" + + +from ccpi.framework import ImageData, ImageGeometry, \ + AcquisitionGeometry, BlockDataContainer, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, CGLS +from ccpi.optimisation.operators import BlockOperator, Gradient + +from ccpi.optimisation.functions import ZeroFunction, BlockFunction, L2NormSquared +from ccpi.astra.ops import AstraProjectorSimple + +# Create Ground truth phantom and Sinogram + +N = 128 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D', angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +noisy_data = AcquisitionData( sin.as_array() + np.random.normal(0,3,ig.shape)) + +# Setup and run the CGLS algorithm + +alpha = 50 +Grad = Gradient(ig) + +# Form Tikhonov as a Block CGLS structure + +#%% +op_CGLS = BlockOperator( Aop, alpha * Grad, shape=(2,1)) +block_data = BlockDataContainer(noisy_data, Grad.range_geometry().allocate()) + +x_init = ig.allocate() + +cgls = CGLS(x_init=x_init, operator=op_CGLS, data=block_data) +cgls.max_iteration = 1000 +cgls.update_objective_interval = 200 +cgls.run(1000) + +#%% +#Setup and run the PDHG algorithm + +# Create BlockOperator +op_PDHG = BlockOperator(Grad, Aop, shape=(2,1) ) + +# Create functions + +f1 = 0.5 * alpha**2 * L2NormSquared() +f2 = 0.5 * L2NormSquared(b = noisy_data) +f = BlockFunction(f1, f2) +g = ZeroFunction() + +## Compute operator Norm +normK = op_PDHG.norm() + +## Primal & dual stepsizes +sigma = 10 +tau = 1/(sigma*normK**2) + +pdhg = PDHG(f=f,g=g,operator=op_PDHG, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 1000 +pdhg.update_objective_interval = 200 +pdhg.run(1000) + +#%% +# Show results + +plt.figure(figsize=(15,15)) + +plt.subplot(1,2,1) +plt.imshow(cgls.get_output().as_array()) +plt.title('CGLS reconstruction') + +plt.subplot(1,2,2) +plt.imshow(pdhg.get_output().as_array()) +plt.title('PDHG reconstruction') + +plt.show() + +diff1 = pdhg.get_output() - cgls.get_output() + +plt.imshow(diff1.abs().as_array()) +plt.title('Diff PDHG vs CGLS') +plt.colorbar() +plt.show() + +#%% + +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') +plt.plot(np.linspace(0,N,N), cgls.get_output().as_array()[int(N/2),:], label = 'CGLS') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + + + + + + + + +# +# +# +# +# +# +# +# -- cgit v1.2.3 From d3bd9291774caf9e1f523d6c568a59a02dbc37b6 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 7 May 2019 10:03:38 +0100 Subject: fix precond --- .../optimisation/operators/IdentityOperator.py | 2 +- .../optimisation/operators/SparseFiniteDiff.py | 4 +- .../Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py | 54 ++++++++++++++-------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/operators/IdentityOperator.py b/Wrappers/Python/ccpi/optimisation/operators/IdentityOperator.py index a58a296..a853b8d 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/IdentityOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/IdentityOperator.py @@ -50,7 +50,7 @@ class Identity(LinearOperator): def sum_abs_row(self): - return self.gm_domain.allocate(1)#ImageData(np.array(np.reshape(abs(self.matrix()).sum(axis=0), self.gm_domain.shape, 'F'))) + return self.gm_range.allocate(1)#ImageData(np.array(np.reshape(abs(self.matrix()).sum(axis=0), self.gm_domain.shape, 'F'))) def sum_abs_col(self): diff --git a/Wrappers/Python/ccpi/optimisation/operators/SparseFiniteDiff.py b/Wrappers/Python/ccpi/optimisation/operators/SparseFiniteDiff.py index 5e318ff..c5c2ec9 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/SparseFiniteDiff.py +++ b/Wrappers/Python/ccpi/optimisation/operators/SparseFiniteDiff.py @@ -65,13 +65,13 @@ class SparseFiniteDiff(): def sum_abs_row(self): res = np.array(np.reshape(abs(self.matrix()).sum(axis=0), self.gm_domain.shape, 'F')) - res[res==0]=1 + #res[res==0]=0 return ImageData(res) def sum_abs_col(self): res = np.array(np.reshape(abs(self.matrix()).sum(axis=1), self.gm_domain.shape, 'F') ) - res[res==0]=1 + #res[res==0]=0 return ImageData(res) if __name__ == '__main__': diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py index 39bbb2c..860e76e 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py @@ -17,6 +17,28 @@ # See the License for the specific language governing permissions and # limitations under the License. + +""" + +Total Variation Denoising using PDHG algorithm: + + min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) + + +Problem: min_x \alpha * ||\nabla x||_{1} + || x - g ||_{2}^{2} + + \nabla: Gradient operator + g: Noisy Data with Gaussian Noise + \alpha: Regularization parameter + + Method = 0: K = [ \nabla, + Identity] + + Method = 1: K = \nabla + + +""" + from ccpi.framework import ImageData, ImageGeometry import numpy as np @@ -29,10 +51,9 @@ from ccpi.optimisation.operators import BlockOperator, Identity, Gradient from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ MixedL21Norm, BlockFunction -from skimage.util import random_noise # Create phantom for TV Gaussian denoising -N = 100 +N = 200 data = np.zeros((N,N)) data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 @@ -42,13 +63,13 @@ ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) ag = ig # Create noisy data. Add Gaussian noise -n1 = random_noise(data.as_array(), mode = 'gaussian', mean=0, var = 0.05, seed=10) -noisy_data = ImageData(n1) +np.random.seed(10) +noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.05, size=ig.shape) ) # Regularisation Parameter alpha = 2 -method = '1' +method = '0' if method == '0': @@ -70,11 +91,10 @@ if method == '0': else: # Without the "Block Framework" - operator = Gradient(ig) + operator = Gradient(ig) f = alpha * MixedL21Norm() g = 0.5 * L2NormSquared(b = noisy_data) - # Compute operator Norm normK = operator.norm() @@ -82,13 +102,11 @@ normK = operator.norm() sigma = 1 tau = 1/(sigma*normK**2) - # Setup and run the PDHG algorithm pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - +pdhg.max_iteration = 3000 +pdhg.update_objective_interval = 200 +pdhg.run(3000, verbose=False) plt.figure(figsize=(15,15)) plt.subplot(3,1,1) @@ -104,7 +122,7 @@ plt.imshow(pdhg.get_output().as_array()) plt.title('TV Reconstruction') plt.colorbar() plt.show() -## + plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') plt.legend() @@ -112,7 +130,7 @@ plt.title('Middle Line Profiles') plt.show() -##%% Check with CVX solution +#%% Check with CVX solution from ccpi.optimisation.operators import SparseFiniteDiff @@ -143,7 +161,7 @@ if cvx_not_installable: obj = Minimize( regulariser + fidelity) prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) + result = prob.solve(verbose = True, solver = MOSEK) diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) @@ -164,6 +182,8 @@ if cvx_not_installable: plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'Truth') + plt.legend() plt.title('Middle Line Profiles') plt.show() @@ -171,7 +191,3 @@ if cvx_not_installable: print('Primal Objective (CVX) {} '.format(obj.value)) print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - -- cgit v1.2.3 From 79300e1b4e4cbf6200def41b9e38deb589dd890b Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 7 May 2019 10:04:25 +0100 Subject: add docstring --- Wrappers/Python/ccpi/framework/framework.py | 7 - .../optimisation/operators/GradientOperator.py | 4 +- .../Python/wip/Demos/PDHG_TV_Denoising_Poisson.py | 21 ++ .../wip/Demos/PDHG_TV_Denoising_SaltPepper.py | 21 ++ Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py | 247 ++++++++++++--------- 5 files changed, 185 insertions(+), 115 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/framework.py b/Wrappers/Python/ccpi/framework/framework.py index a8a0ab4..387b5c1 100755 --- a/Wrappers/Python/ccpi/framework/framework.py +++ b/Wrappers/Python/ccpi/framework/framework.py @@ -760,13 +760,6 @@ class DataContainer(object): return numpy.sqrt(self.squared_norm()) -# def dot(self, other, *args, **kwargs): -# '''return the inner product of 2 DataContainers viewed as vectors''' -# if self.shape == other.shape: -# return numpy.dot(self.as_array().ravel(), other.as_array().ravel()) -# else: -# raise ValueError('Shapes are not aligned: {} != {}'.format(self.shape, other.shape)) - def dot(self, other, *args, **kwargs): '''return the inner product of 2 DataContainers viewed as vectors''' method = kwargs.get('method', 'reduce') diff --git a/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py b/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py index e0b8a32..6ffaf70 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py @@ -111,7 +111,7 @@ class Gradient(LinearOperator): return BlockDataContainer(*mat) - def sum_abs_row(self): + def sum_abs_col(self): tmp = self.gm_range.allocate() res = self.gm_domain.allocate() @@ -120,7 +120,7 @@ class Gradient(LinearOperator): res += spMat.sum_abs_row() return res - def sum_abs_col(self): + def sum_abs_row(self): tmp = self.gm_range.allocate() res = [] diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py index 4903c44..3c295f5 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py @@ -17,6 +17,27 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" + +Total Variation Denoising using PDHG algorithm: + + min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + \int x - g * log(x) + + \nabla: Gradient operator + g: Noisy Data with Poisson Noise + \alpha: Regularization parameter + + Method = 0: K = [ \nabla, + Identity] + + Method = 1: K = \nabla + + +""" + from ccpi.framework import ImageData, ImageGeometry import numpy as np diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py index 4189acb..f5d4ce4 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py @@ -17,6 +17,27 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" + +Total Variation Denoising using PDHG algorithm: + + min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + ||x-g||_{1} + + \nabla: Gradient operator + g: Noisy Data with Salt & Pepper Noise + \alpha: Regularization parameter + + Method = 0: K = [ \nabla, + Identity] + + Method = 1: K = \nabla + + +""" + from ccpi.framework import ImageData, ImageGeometry import numpy as np diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py b/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py index 0711e91..75286e5 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py @@ -31,6 +31,25 @@ from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ from ccpi.astra.ops import AstraProjectorSimple +""" + +Total Variation Denoising using PDHG algorithm: + + min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + int A x -g log(Ax + \eta) + + \nabla: Gradient operator + + A: Projection Matrix + g: Noisy sinogram corrupted with Poisson Noise + + \eta: Background Noise + \alpha: Regularization parameter + +""" + # Create phantom for TV 2D tomography N = 75 x = np.zeros((N,N)) @@ -70,12 +89,26 @@ f = BlockFunction(f1, f2) g = ZeroFunction() -# Compute operator Norm -normK = operator.norm() +diag_precon = True + +if diag_precon: + + def tau_sigma_precond(operator): + + tau = 1/operator.sum_abs_row() + sigma = 1/ operator.sum_abs_col() + + return tau, sigma + + tau, sigma = tau_sigma_precond(operator) + +else: + # Compute operator Norm + normK = operator.norm() + # Primal & dual stepsizes + sigma = 10 + tau = 1/(sigma*normK**2) -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) # Setup and run the PDHG algorithm @@ -84,6 +117,8 @@ pdhg.max_iteration = 2000 pdhg.update_objective_interval = 50 pdhg.run(2000) + +#%% plt.figure(figsize=(15,15)) plt.subplot(3,1,1) plt.imshow(data.as_array()) @@ -108,104 +143,104 @@ plt.show() #%% Check with CVX solution -from ccpi.optimisation.operators import SparseFiniteDiff -import astra -import numpy - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - - ##Construct problem - u = Variable(N*N) - #q = Variable() - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - - # create matrix representation for Astra operator - - vol_geom = astra.create_vol_geom(N, N) - proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) - - proj_id = astra.create_projector('strip', proj_geom, vol_geom) - - matrix_id = astra.projector.matrix(proj_id) - - ProjMat = astra.matrix.get(matrix_id) - - fidelity = sum( ProjMat * u - noisy_data.as_array().ravel() * log(ProjMat * u)) - #constraints = [q>= fidelity, u>=0] - constraints = [u>=0] - - solver = SCS - obj = Minimize( regulariser + fidelity) - prob = Problem(obj, constraints) - result = prob.solve(verbose = True, solver = solver) - - -##%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = pnorm( u - noisy_data.as_array(),1) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(np.reshape(u.value, (N, N))) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file +#from ccpi.optimisation.operators import SparseFiniteDiff +#import astra +#import numpy +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +# +#if cvx_not_installable: +# +# +# ##Construct problem +# u = Variable(N*N) +# #q = Variable() +# +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) +# +# # create matrix representation for Astra operator +# +# vol_geom = astra.create_vol_geom(N, N) +# proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) +# +# proj_id = astra.create_projector('strip', proj_geom, vol_geom) +# +# matrix_id = astra.projector.matrix(proj_id) +# +# ProjMat = astra.matrix.get(matrix_id) +# +# fidelity = sum( ProjMat * u - noisy_data.as_array().ravel() * log(ProjMat * u)) +# #constraints = [q>= fidelity, u>=0] +# constraints = [u>=0] +# +# solver = SCS +# obj = Minimize( regulariser + fidelity) +# prob = Problem(obj, constraints) +# result = prob.solve(verbose = True, solver = solver) +# +# +###%% Check with CVX solution +# +#from ccpi.optimisation.operators import SparseFiniteDiff +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +# +#if cvx_not_installable: +# +# ##Construct problem +# u = Variable(ig.shape) +# +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# # Define Total Variation as a regulariser +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) +# fidelity = pnorm( u - noisy_data.as_array(),1) +# +# # choose solver +# if 'MOSEK' in installed_solvers(): +# solver = MOSEK +# else: +# solver = SCS +# +# obj = Minimize( regulariser + fidelity) +# prob = Problem(obj) +# result = prob.solve(verbose = True, solver = solver) +# +# +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(pdhg.get_output().as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# plt.subplot(3,1,2) +# plt.imshow(np.reshape(u.value, (N, N))) +# plt.title('CVX solution') +# plt.colorbar() +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() +# +# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') +# plt.legend() +# plt.title('Middle Line Profiles') +# plt.show() +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file -- cgit v1.2.3 From 9fbb3cd01264a387ec96ddb2fa459546b5d1c60e Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 7 May 2019 10:05:14 +0100 Subject: new dir old demos --- Wrappers/Python/wip/old_demos/demo_colourbay.py | 137 +++++++++ Wrappers/Python/wip/old_demos/demo_compare_cvx.py | 306 ++++++++++++++++++++ .../Python/wip/old_demos/demo_gradient_descent.py | 295 ++++++++++++++++++++ .../wip/old_demos/demo_imat_multichan_RGLTK.py | 151 ++++++++++ .../Python/wip/old_demos/demo_imat_whitebeam.py | 138 +++++++++ Wrappers/Python/wip/old_demos/demo_memhandle.py | 193 +++++++++++++ Wrappers/Python/wip/old_demos/demo_test_sirt.py | 176 ++++++++++++ Wrappers/Python/wip/old_demos/multifile_nexus.py | 307 +++++++++++++++++++++ 8 files changed, 1703 insertions(+) create mode 100644 Wrappers/Python/wip/old_demos/demo_colourbay.py create mode 100644 Wrappers/Python/wip/old_demos/demo_compare_cvx.py create mode 100755 Wrappers/Python/wip/old_demos/demo_gradient_descent.py create mode 100644 Wrappers/Python/wip/old_demos/demo_imat_multichan_RGLTK.py create mode 100644 Wrappers/Python/wip/old_demos/demo_imat_whitebeam.py create mode 100755 Wrappers/Python/wip/old_demos/demo_memhandle.py create mode 100644 Wrappers/Python/wip/old_demos/demo_test_sirt.py create mode 100755 Wrappers/Python/wip/old_demos/multifile_nexus.py diff --git a/Wrappers/Python/wip/old_demos/demo_colourbay.py b/Wrappers/Python/wip/old_demos/demo_colourbay.py new file mode 100644 index 0000000..5dbf2e1 --- /dev/null +++ b/Wrappers/Python/wip/old_demos/demo_colourbay.py @@ -0,0 +1,137 @@ +# This script demonstrates how to load a mat-file with UoM colour-bay data +# into the CIL optimisation framework and run (simple) multichannel +# reconstruction methods. + +# All third-party imports. +import numpy +from scipy.io import loadmat +import matplotlib.pyplot as plt + +# All own imports. +from ccpi.framework import AcquisitionData, AcquisitionGeometry, ImageGeometry, ImageData +from ccpi.astra.ops import AstraProjectorMC +from ccpi.optimisation.algs import CGLS, FISTA +from ccpi.optimisation.funcs import Norm2sq, Norm1 + +# Load full data and permute to expected ordering. Change path as necessary. +# The loaded X has dims 80x60x80x150, which is pix x angle x pix x channel. +# Permute (numpy.transpose) puts into our default ordering which is +# (channel, angle, vertical, horizontal). + +pathname = '/media/jakob/050d8d45-fab3-4285-935f-260e6c5f162c1/Data/ColourBay/spectral_data_sets/CarbonPd/' +filename = 'carbonPd_full_sinogram_stripes_removed.mat' + +X = loadmat(pathname + filename) +X = numpy.transpose(X['SS'],(3,1,2,0)) + +# Store geometric variables for reuse +num_channels = X.shape[0] +num_pixels_h = X.shape[3] +num_pixels_v = X.shape[2] +num_angles = X.shape[1] + +# Display a single projection in a single channel +plt.imshow(X[100,5,:,:]) +plt.title('Example of a projection image in one channel' ) +plt.show() + +# Set angles to use +angles = numpy.linspace(-numpy.pi,numpy.pi,num_angles,endpoint=False) + +# Define full 3D acquisition geometry and data container. +# Geometric info is taken from the txt-file in the same dir as the mat-file +ag = AcquisitionGeometry('cone', + '3D', + angles, + pixel_num_h=num_pixels_h, + pixel_size_h=0.25, + pixel_num_v=num_pixels_v, + pixel_size_v=0.25, + dist_source_center=233.0, + dist_center_detector=245.0, + channels=num_channels) +data = AcquisitionData(X, geometry=ag) + +# Reduce to central slice by extracting relevant parameters from data and its +# geometry. Perhaps create function to extract central slice automatically? +data2d = data.subset(vertical=40) +ag2d = AcquisitionGeometry('cone', + '2D', + ag.angles, + pixel_num_h=ag.pixel_num_h, + pixel_size_h=ag.pixel_size_h, + pixel_num_v=1, + pixel_size_v=ag.pixel_size_h, + dist_source_center=ag.dist_source_center, + dist_center_detector=ag.dist_center_detector, + channels=ag.channels) +data2d.geometry = ag2d + +# Set up 2D Image Geometry. +# First need the geometric magnification to scale the voxel size relative +# to the detector pixel size. +mag = (ag.dist_source_center + ag.dist_center_detector)/ag.dist_source_center +ig2d = ImageGeometry(voxel_num_x=ag2d.pixel_num_h, + voxel_num_y=ag2d.pixel_num_h, + voxel_size_x=ag2d.pixel_size_h/mag, + voxel_size_y=ag2d.pixel_size_h/mag, + channels=X.shape[0]) + +# Create GPU multichannel projector/backprojector operator with ASTRA. +Aall = AstraProjectorMC(ig2d,ag2d,'gpu') + +# Compute and simple backprojction and display one channel as image. +Xbp = Aall.adjoint(data2d) +plt.imshow(Xbp.subset(channel=100).array) +plt.show() + +# Set initial guess ImageData with zeros for algorithms, and algorithm options. +x_init = ImageData(numpy.zeros((num_channels,num_pixels_v,num_pixels_h)), + geometry=ig2d, + dimension_labels=['channel','horizontal_y','horizontal_x']) +opt_CGLS = {'tol': 1e-4, 'iter': 5} + +# Run CGLS algorithm and display one channel. +x_CGLS, it_CGLS, timing_CGLS, criter_CGLS = CGLS(x_init, Aall, data2d, opt_CGLS) + +plt.imshow(x_CGLS.subset(channel=100).array) +plt.title('CGLS') +plt.show() + +plt.semilogy(criter_CGLS) +plt.title('CGLS Criterion vs iterations') +plt.show() + +# Create least squares object instance with projector, test data and a constant +# coefficient of 0.5. Note it is least squares over all channels. +f = Norm2sq(Aall,data2d,c=0.5) + +# Options for FISTA algorithm. +opt = {'tol': 1e-4, 'iter': 100} + +# Run FISTA for least squares without regularization and display one channel +# reconstruction as image. +x_fista0, it0, timing0, criter0 = FISTA(x_init, f, None, opt) + +plt.imshow(x_fista0.subset(channel=100).array) +plt.title('FISTA LS') +plt.show() + +plt.semilogy(criter0) +plt.title('FISTA LS Criterion vs iterations') +plt.show() + +# Set up 1-norm regularisation (over all channels), solve with FISTA, and +# display one channel of reconstruction. +lam = 0.1 +g0 = Norm1(lam) + +x_fista1, it1, timing1, criter1 = FISTA(x_init, f, g0, opt) + +plt.imshow(x_fista1.subset(channel=100).array) +plt.title('FISTA LS+1') +plt.show() + +plt.semilogy(criter1) +plt.title('FISTA LS+1 Criterion vs iterations') +plt.show() \ No newline at end of file diff --git a/Wrappers/Python/wip/old_demos/demo_compare_cvx.py b/Wrappers/Python/wip/old_demos/demo_compare_cvx.py new file mode 100644 index 0000000..27b1c97 --- /dev/null +++ b/Wrappers/Python/wip/old_demos/demo_compare_cvx.py @@ -0,0 +1,306 @@ + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, DataContainer +from ccpi.optimisation.algs import FISTA, FBPD, CGLS +from ccpi.optimisation.funcs import Norm2sq, ZeroFun, Norm1, TV2D, Norm2 + +from ccpi.optimisation.ops import LinearOperatorMatrix, TomoIdentity +from ccpi.optimisation.ops import Identity +from ccpi.optimisation.ops import FiniteDiff2D + +# Requires CVXPY, see http://www.cvxpy.org/ +# CVXPY can be installed in anaconda using +# conda install -c cvxgrp cvxpy libgcc + +# Whether to use or omit CVXPY +use_cvxpy = True +if use_cvxpy: + from cvxpy import * + +import numpy as np +import matplotlib.pyplot as plt + +# Problem data. +m = 30 +n = 20 +np.random.seed(1) +Amat = np.random.randn(m, n) +A = LinearOperatorMatrix(Amat) +bmat = np.random.randn(m) +bmat.shape = (bmat.shape[0],1) + +# A = Identity() +# Change n to equal to m. + +b = DataContainer(bmat) + +# Regularization parameter +lam = 10 +opt = {'memopt':True} +# Create object instances with the test data A and b. +f = Norm2sq(A,b,c=0.5, memopt=True) +g0 = ZeroFun() + +# Initial guess +x_init = DataContainer(np.zeros((n,1))) + +f.grad(x_init) + +# Run FISTA for least squares plus zero function. +x_fista0, it0, timing0, criter0 = FISTA(x_init, f, g0 , opt=opt) + +# Print solution and final objective/criterion value for comparison +print("FISTA least squares plus zero function solution and objective value:") +print(x_fista0.array) +print(criter0[-1]) + +if use_cvxpy: + # Compare to CVXPY + + # Construct the problem. + x0 = Variable(n) + objective0 = Minimize(0.5*sum_squares(Amat*x0 - bmat.T[0]) ) + prob0 = Problem(objective0) + + # The optimal objective is returned by prob.solve(). + result0 = prob0.solve(verbose=False,solver=SCS,eps=1e-9) + + # The optimal solution for x is stored in x.value and optimal objective value + # is in result as well as in objective.value + print("CVXPY least squares plus zero function solution and objective value:") + print(x0.value) + print(objective0.value) + +# Plot criterion curve to see FISTA converge to same value as CVX. +iternum = np.arange(1,1001) +plt.figure() +plt.loglog(iternum[[0,-1]],[objective0.value, objective0.value], label='CVX LS') +plt.loglog(iternum,criter0,label='FISTA LS') +plt.legend() +plt.show() + +# Create 1-norm object instance +g1 = Norm1(lam) + +g1(x_init) +x_rand = DataContainer(np.reshape(np.random.rand(n),(n,1))) +x_rand2 = DataContainer(np.reshape(np.random.rand(n-1),(n-1,1))) +v = g1.prox(x_rand,0.02) +#vv = g1.prox(x_rand2,0.02) +vv = v.copy() +vv *= 0 +print (">>>>>>>>>>vv" , vv.as_array()) +vv.fill(v) +print (">>>>>>>>>>fill" , vv.as_array()) +g1.proximal(x_rand, 0.02, out=vv) +print (">>>>>>>>>>v" , v.as_array()) +print (">>>>>>>>>>gradient" , vv.as_array()) + +print (">>>>>>>>>>" , (v-vv).as_array()) +import sys +#sys.exit(0) +# Combine with least squares and solve using generic FISTA implementation +x_fista1, it1, timing1, criter1 = FISTA(x_init, f, g1,opt=opt) + +# Print for comparison +print("FISTA least squares plus 1-norm solution and objective value:") +print(x_fista1) +print(criter1[-1]) + +if use_cvxpy: + # Compare to CVXPY + + # Construct the problem. + x1 = Variable(n) + objective1 = Minimize(0.5*sum_squares(Amat*x1 - bmat.T[0]) + lam*norm(x1,1) ) + prob1 = Problem(objective1) + + # The optimal objective is returned by prob.solve(). + result1 = prob1.solve(verbose=False,solver=SCS,eps=1e-9) + + # The optimal solution for x is stored in x.value and optimal objective value + # is in result as well as in objective.value + print("CVXPY least squares plus 1-norm solution and objective value:") + print(x1.value) + print(objective1.value) + +# Now try another algorithm FBPD for same problem: +x_fbpd1, itfbpd1, timingfbpd1, criterfbpd1 = FBPD(x_init,Identity(), None, f, g1) +print(x_fbpd1) +print(criterfbpd1[-1]) + +# Plot criterion curve to see both FISTA and FBPD converge to same value. +# Note that FISTA is very efficient for 1-norm minimization so it beats +# FBPD in this test by a lot. But FBPD can handle a larger class of problems +# than FISTA can. +plt.figure() +plt.loglog(iternum[[0,-1]],[objective1.value, objective1.value], label='CVX LS+1') +plt.loglog(iternum,criter1,label='FISTA LS+1') +plt.legend() +plt.show() + +plt.figure() +plt.loglog(iternum[[0,-1]],[objective1.value, objective1.value], label='CVX LS+1') +plt.loglog(iternum,criter1,label='FISTA LS+1') +plt.loglog(iternum,criterfbpd1,label='FBPD LS+1') +plt.legend() +plt.show() + +# Now try 1-norm and TV denoising with FBPD, first 1-norm. + +# Set up phantom size NxN by creating ImageGeometry, initialising the +# ImageData object with this geometry and empty array and finally put some +# data into its array, and display as image. +N = 64 +ig = ImageGeometry(voxel_num_x=N,voxel_num_y=N) +Phantom = ImageData(geometry=ig) + +x = Phantom.as_array() +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +plt.imshow(x) +plt.title('Phantom image') +plt.show() + +# Identity operator for denoising +I = TomoIdentity(ig) + +# Data and add noise +y = I.direct(Phantom) +y.array = y.array + 0.1*np.random.randn(N, N) + +plt.imshow(y.array) +plt.title('Noisy image') +plt.show() + + +################### +# Data fidelity term +f_denoise = Norm2sq(I,y,c=0.5,memopt=True) + +# 1-norm regulariser +lam1_denoise = 1.0 +g1_denoise = Norm1(lam1_denoise) + +# Initial guess +x_init_denoise = ImageData(np.zeros((N,N))) + +# Combine with least squares and solve using generic FISTA implementation +x_fista1_denoise, it1_denoise, timing1_denoise, criter1_denoise = FISTA(x_init_denoise, f_denoise, g1_denoise, opt=opt) + +print(x_fista1_denoise) +print(criter1_denoise[-1]) + +#plt.imshow(x_fista1_denoise.as_array()) +#plt.title('FISTA LS+1') +#plt.show() + +# Now denoise LS + 1-norm with FBPD +x_fbpd1_denoise, itfbpd1_denoise, timingfbpd1_denoise, \ + criterfbpd1_denoise = FBPD(x_init_denoise, I, None, f_denoise, g1_denoise) +print(x_fbpd1_denoise) +print(criterfbpd1_denoise[-1]) + +#plt.imshow(x_fbpd1_denoise.as_array()) +#plt.title('FBPD LS+1') +#plt.show() + +if use_cvxpy: + # Compare to CVXPY + + # Construct the problem. + x1_denoise = Variable(N**2,1) + objective1_denoise = Minimize(0.5*sum_squares(x1_denoise - y.array.flatten()) + lam1_denoise*norm(x1_denoise,1) ) + prob1_denoise = Problem(objective1_denoise) + + # The optimal objective is returned by prob.solve(). + result1_denoise = prob1_denoise.solve(verbose=False,solver=SCS,eps=1e-12) + + # The optimal solution for x is stored in x.value and optimal objective value + # is in result as well as in objective.value + print("CVXPY least squares plus 1-norm solution and objective value:") + print(x1_denoise.value) + print(objective1_denoise.value) + +x1_cvx = x1_denoise.value +x1_cvx.shape = (N,N) + + + +#plt.imshow(x1_cvx) +#plt.title('CVX LS+1') +#plt.show() + +fig = plt.figure() +plt.subplot(1,4,1) +plt.imshow(y.array) +plt.title("LS+1") +plt.subplot(1,4,2) +plt.imshow(x_fista1_denoise.as_array()) +plt.title("fista") +plt.subplot(1,4,3) +plt.imshow(x_fbpd1_denoise.as_array()) +plt.title("fbpd") +plt.subplot(1,4,4) +plt.imshow(x1_cvx) +plt.title("cvx") +plt.show() + +############################################################## +# Now TV with FBPD and Norm2 +lam_tv = 0.1 +gtv = TV2D(lam_tv) +norm2 = Norm2(lam_tv) +op = FiniteDiff2D() +#gtv(gtv.op.direct(x_init_denoise)) + +opt_tv = {'tol': 1e-4, 'iter': 10000} + +x_fbpdtv_denoise, itfbpdtv_denoise, timingfbpdtv_denoise, \ + criterfbpdtv_denoise = FBPD(x_init_denoise, op, None, \ + f_denoise, norm2 ,opt=opt_tv) +print(x_fbpdtv_denoise) +print(criterfbpdtv_denoise[-1]) + +plt.imshow(x_fbpdtv_denoise.as_array()) +plt.title('FBPD TV') +#plt.show() + +if use_cvxpy: + # Compare to CVXPY + + # Construct the problem. + xtv_denoise = Variable((N,N)) + #print (xtv_denoise.value.shape) + objectivetv_denoise = Minimize(0.5*sum_squares(xtv_denoise - y.array) + lam_tv*tv(xtv_denoise) ) + probtv_denoise = Problem(objectivetv_denoise) + + # The optimal objective is returned by prob.solve(). + resulttv_denoise = probtv_denoise.solve(verbose=False,solver=SCS,eps=1e-12) + + # The optimal solution for x is stored in x.value and optimal objective value + # is in result as well as in objective.value + print("CVXPY least squares plus 1-norm solution and objective value:") + print(xtv_denoise.value) + print(objectivetv_denoise.value) + +plt.imshow(xtv_denoise.value) +plt.title('CVX TV') +#plt.show() + +fig = plt.figure() +plt.subplot(1,3,1) +plt.imshow(y.array) +plt.title("TV2D") +plt.subplot(1,3,2) +plt.imshow(x_fbpdtv_denoise.as_array()) +plt.title("fbpd tv denoise") +plt.subplot(1,3,3) +plt.imshow(xtv_denoise.value) +plt.title("CVX tv") +plt.show() + + + +plt.loglog([0,opt_tv['iter']], [objectivetv_denoise.value,objectivetv_denoise.value], label='CVX TV') +plt.loglog(criterfbpdtv_denoise, label='FBPD TV') diff --git a/Wrappers/Python/wip/old_demos/demo_gradient_descent.py b/Wrappers/Python/wip/old_demos/demo_gradient_descent.py new file mode 100755 index 0000000..4d6647e --- /dev/null +++ b/Wrappers/Python/wip/old_demos/demo_gradient_descent.py @@ -0,0 +1,295 @@ + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, DataContainer +from ccpi.optimisation.algs import FISTA, FBPD, CGLS +from ccpi.optimisation.funcs import Norm2sq, ZeroFun, Norm1, TV2D, Norm2 + +from ccpi.optimisation.ops import LinearOperatorMatrix, TomoIdentity +from ccpi.optimisation.ops import Identity +from ccpi.optimisation.ops import FiniteDiff2D + +# Requires CVXPY, see http://www.cvxpy.org/ +# CVXPY can be installed in anaconda using +# conda install -c cvxgrp cvxpy libgcc + +# Whether to use or omit CVXPY + +import numpy as np +import matplotlib.pyplot as plt + +class Algorithm(object): + def __init__(self, *args, **kwargs): + pass + def set_up(self, *args, **kwargs): + raise NotImplementedError() + def update(self): + raise NotImplementedError() + + def should_stop(self): + raise NotImplementedError() + + def __iter__(self): + return self + + def __next__(self): + if self.should_stop(): + raise StopIteration() + else: + self.update() + +class GradientDescent(Algorithm): + x = None + rate = 0 + objective_function = None + regulariser = None + iteration = 0 + stop_cryterion = 'max_iter' + __max_iteration = 0 + __loss = [] + def __init__(self, **kwargs): + args = ['x_init', 'objective_function', 'rate'] + present = True + for k,v in kwargs.items(): + if k in args: + args.pop(args.index(k)) + if len(args) == 0: + return self.set_up(x_init=kwargs['x_init'], + objective_function=kwargs['objective_function'], + rate=kwargs['rate']) + + def should_stop(self): + return self.iteration >= self.max_iteration + + def set_up(self, x_init, objective_function, rate): + self.x = x_init.copy() + self.x_update = x_init.copy() + self.objective_function = objective_function + self.rate = rate + self.__loss.append(objective_function(x_init)) + + def update(self): + + self.objective_function.gradient(self.x, out=self.x_update) + self.x_update *= -self.rate + self.x += self.x_update + self.__loss.append(self.objective_function(self.x)) + self.iteration += 1 + + def get_output(self): + return self.x + def get_current_loss(self): + return self.__loss[-1] + @property + def loss(self): + return self.__loss + @property + def max_iteration(self): + return self.__max_iteration + @max_iteration.setter + def max_iteration(self, value): + assert isinstance(value, int) + self.__max_iteration = value + + + + + +# Problem data. +m = 30 +n = 20 +np.random.seed(1) +Amat = np.random.randn(m, n) +A = LinearOperatorMatrix(Amat) +bmat = np.random.randn(m) +bmat.shape = (bmat.shape[0],1) + +# A = Identity() +# Change n to equal to m. + +b = DataContainer(bmat) + +# Regularization parameter +lam = 10 +opt = {'memopt':True} +# Create object instances with the test data A and b. +f = Norm2sq(A,b,c=0.5, memopt=True) +g0 = ZeroFun() + +# Initial guess +x_init = DataContainer(np.zeros((n,1))) + +f.grad(x_init) + +# Run FISTA for least squares plus zero function. +x_fista0, it0, timing0, criter0 = FISTA(x_init, f, g0 , opt=opt) + +# Print solution and final objective/criterion value for comparison +print("FISTA least squares plus zero function solution and objective value:") +print(x_fista0.array) +print(criter0[-1]) + +gd = GradientDescent(x_init=x_init, objective_function=f, rate=0.001) +gd.max_iteration = 5000 + +for i,el in enumerate(gd): + if i%100 == 0: + print ("\rIteration {} Loss: {}".format(gd.iteration, + gd.get_current_loss())) + + +#%% + + +# +#if use_cvxpy: +# # Compare to CVXPY +# +# # Construct the problem. +# x0 = Variable(n) +# objective0 = Minimize(0.5*sum_squares(Amat*x0 - bmat.T[0]) ) +# prob0 = Problem(objective0) +# +# # The optimal objective is returned by prob.solve(). +# result0 = prob0.solve(verbose=False,solver=SCS,eps=1e-9) +# +# # The optimal solution for x is stored in x.value and optimal objective value +# # is in result as well as in objective.value +# print("CVXPY least squares plus zero function solution and objective value:") +# print(x0.value) +# print(objective0.value) +# +## Plot criterion curve to see FISTA converge to same value as CVX. +#iternum = np.arange(1,1001) +#plt.figure() +#plt.loglog(iternum[[0,-1]],[objective0.value, objective0.value], label='CVX LS') +#plt.loglog(iternum,criter0,label='FISTA LS') +#plt.legend() +#plt.show() +# +## Create 1-norm object instance +#g1 = Norm1(lam) +# +#g1(x_init) +#x_rand = DataContainer(np.reshape(np.random.rand(n),(n,1))) +#x_rand2 = DataContainer(np.reshape(np.random.rand(n-1),(n-1,1))) +#v = g1.prox(x_rand,0.02) +##vv = g1.prox(x_rand2,0.02) +#vv = v.copy() +#vv *= 0 +#print (">>>>>>>>>>vv" , vv.as_array()) +#vv.fill(v) +#print (">>>>>>>>>>fill" , vv.as_array()) +#g1.proximal(x_rand, 0.02, out=vv) +#print (">>>>>>>>>>v" , v.as_array()) +#print (">>>>>>>>>>gradient" , vv.as_array()) +# +#print (">>>>>>>>>>" , (v-vv).as_array()) +#import sys +##sys.exit(0) +## Combine with least squares and solve using generic FISTA implementation +#x_fista1, it1, timing1, criter1 = FISTA(x_init, f, g1,opt=opt) +# +## Print for comparison +#print("FISTA least squares plus 1-norm solution and objective value:") +#print(x_fista1) +#print(criter1[-1]) +# +#if use_cvxpy: +# # Compare to CVXPY +# +# # Construct the problem. +# x1 = Variable(n) +# objective1 = Minimize(0.5*sum_squares(Amat*x1 - bmat.T[0]) + lam*norm(x1,1) ) +# prob1 = Problem(objective1) +# +# # The optimal objective is returned by prob.solve(). +# result1 = prob1.solve(verbose=False,solver=SCS,eps=1e-9) +# +# # The optimal solution for x is stored in x.value and optimal objective value +# # is in result as well as in objective.value +# print("CVXPY least squares plus 1-norm solution and objective value:") +# print(x1.value) +# print(objective1.value) +# +## Now try another algorithm FBPD for same problem: +#x_fbpd1, itfbpd1, timingfbpd1, criterfbpd1 = FBPD(x_init,Identity(), None, f, g1) +#print(x_fbpd1) +#print(criterfbpd1[-1]) +# +## Plot criterion curve to see both FISTA and FBPD converge to same value. +## Note that FISTA is very efficient for 1-norm minimization so it beats +## FBPD in this test by a lot. But FBPD can handle a larger class of problems +## than FISTA can. +#plt.figure() +#plt.loglog(iternum[[0,-1]],[objective1.value, objective1.value], label='CVX LS+1') +#plt.loglog(iternum,criter1,label='FISTA LS+1') +#plt.legend() +#plt.show() +# +#plt.figure() +#plt.loglog(iternum[[0,-1]],[objective1.value, objective1.value], label='CVX LS+1') +#plt.loglog(iternum,criter1,label='FISTA LS+1') +#plt.loglog(iternum,criterfbpd1,label='FBPD LS+1') +#plt.legend() +#plt.show() + +# Now try 1-norm and TV denoising with FBPD, first 1-norm. + +# Set up phantom size NxN by creating ImageGeometry, initialising the +# ImageData object with this geometry and empty array and finally put some +# data into its array, and display as image. +N = 64 +ig = ImageGeometry(voxel_num_x=N,voxel_num_y=N) +Phantom = ImageData(geometry=ig) + +x = Phantom.as_array() +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +plt.imshow(x) +plt.title('Phantom image') +plt.show() + +# Identity operator for denoising +I = TomoIdentity(ig) + +# Data and add noise +y = I.direct(Phantom) +y.array = y.array + 0.1*np.random.randn(N, N) + +plt.imshow(y.array) +plt.title('Noisy image') +plt.show() + + +################### +# Data fidelity term +f_denoise = Norm2sq(I,y,c=0.5,memopt=True) + +# 1-norm regulariser +lam1_denoise = 1.0 +g1_denoise = Norm1(lam1_denoise) + +# Initial guess +x_init_denoise = ImageData(np.zeros((N,N))) + +# Combine with least squares and solve using generic FISTA implementation +x_fista1_denoise, it1_denoise, timing1_denoise, criter1_denoise = \ + FISTA(x_init_denoise, f_denoise, g1_denoise, opt=opt) + +print(x_fista1_denoise) +print(criter1_denoise[-1]) + +f_2 = +gd = GradientDescent(x_init=x_init_denoise, + objective_function=f, rate=0.001) +gd.max_iteration = 5000 + +for i,el in enumerate(gd): + if i%100 == 0: + print ("\rIteration {} Loss: {}".format(gd.iteration, + gd.get_current_loss())) + +plt.imshow(gd.get_output().as_array()) +plt.title('GD image') +plt.show() + diff --git a/Wrappers/Python/wip/old_demos/demo_imat_multichan_RGLTK.py b/Wrappers/Python/wip/old_demos/demo_imat_multichan_RGLTK.py new file mode 100644 index 0000000..8370c78 --- /dev/null +++ b/Wrappers/Python/wip/old_demos/demo_imat_multichan_RGLTK.py @@ -0,0 +1,151 @@ +# This script demonstrates how to load IMAT fits data +# into the CIL optimisation framework and run reconstruction methods. +# +# Demo to reconstruct energy-discretized channels of IMAT data + +# needs dxchange: conda install -c conda-forge dxchange +# needs astropy: conda install astropy + + +# All third-party imports. +import numpy as np +import matplotlib.pyplot as plt +from dxchange.reader import read_fits +from astropy.io import fits + +# All own imports. +from ccpi.framework import AcquisitionData, AcquisitionGeometry, ImageGeometry, ImageData, DataContainer +from ccpi.astra.ops import AstraProjectorSimple, AstraProjector3DSimple +from ccpi.optimisation.algs import CGLS, FISTA +from ccpi.optimisation.funcs import Norm2sq, Norm1 +from ccpi.plugins.regularisers import FGP_TV + +# set main parameters here +n = 512 +totalAngles = 250 # total number of projection angles +# spectral discretization parameter +num_average = 145 # channel discretization frequency (total number of averaged channels) +numChannels = 2970 # 2970 +totChannels = round(numChannels/num_average) # the resulting number of channels +Projections_stack = np.zeros((num_average,n,n),dtype='uint16') +ProjAngleChannels = np.zeros((totalAngles,totChannels,n,n),dtype='float32') + +######################################################################### +print ("Loading the data...") +MainPath = '/media/jakob/050d8d45-fab3-4285-935f-260e6c5f162c1/Data/neutrondata/' # path to data +pathname0 = '{!s}{!s}'.format(MainPath,'PSI_phantom_IMAT/DATA/Sample/') +counterFileName = 4675 +# A main loop over all available angles +for ll in range(0,totalAngles,1): + pathnameData = '{!s}{!s}{!s}/'.format(pathname0,'angle',str(ll)) + filenameCurr = '{!s}{!s}{!s}'.format('IMAT0000',str(counterFileName),'_Tomo_test_000_') + counterT = 0 + # loop over reduced channels (discretized) + for i in range(0,totChannels,1): + sumCount = 0 + # loop over actual channels to obtain averaged one + for j in range(0,num_average,1): + if counterT < 10: + outfile = '{!s}{!s}{!s}{!s}.fits'.format(pathnameData,filenameCurr,'0000',str(counterT)) + if ((counterT >= 10) & (counterT < 100)): + outfile = '{!s}{!s}{!s}{!s}.fits'.format(pathnameData,filenameCurr,'000',str(counterT)) + if ((counterT >= 100) & (counterT < 1000)): + outfile = '{!s}{!s}{!s}{!s}.fits'.format(pathnameData,filenameCurr,'00',str(counterT)) + if ((counterT >= 1000) & (counterT < 10000)): + outfile = '{!s}{!s}{!s}{!s}.fits'.format(pathnameData,filenameCurr,'0',str(counterT)) + try: + Projections_stack[j,:,:] = read_fits(outfile) + except: + print("Fits is corrupted, skipping no.", counterT) + sumCount -= 1 + counterT += 1 + sumCount += 1 + AverageProj=np.sum(Projections_stack,axis=0)/sumCount # averaged projection over "num_average" channels + ProjAngleChannels[ll,i,:,:] = AverageProj + print("Angle is processed", ll) + counterFileName += 1 +######################################################################### + +flat1 = read_fits('{!s}{!s}{!s}'.format(MainPath,'PSI_phantom_IMAT/DATA/','OpenBeam_aft1/IMAT00004932_Tomo_test_000_SummedImg.fits')) +nonzero = flat1 > 0 +# Apply flat field and take negative log +for ll in range(0,totalAngles,1): + for i in range(0,totChannels,1): + ProjAngleChannels[ll,i,nonzero] = ProjAngleChannels[ll,i,nonzero]/flat1[nonzero] + +eqzero = ProjAngleChannels == 0 +ProjAngleChannels[eqzero] = 1 +ProjAngleChannels_NormLog = -np.log(ProjAngleChannels) # normalised and neg-log data + +# extact sinogram over energy channels +selectedVertical_slice = 256 +sino_all_channels = ProjAngleChannels_NormLog[:,:,:,selectedVertical_slice] +# Set angles to use +angles = np.linspace(-np.pi,np.pi,totalAngles,endpoint=False) + +# set the geometry +ig = ImageGeometry(n,n) +ag = AcquisitionGeometry('parallel', + '2D', + angles, + n,1) +Aop = AstraProjectorSimple(ig, ag, 'gpu') + + +# loop to reconstruct energy channels +REC_chan = np.zeros((totChannels, n, n), 'float32') +for i in range(0,totChannels,1): + sino_channel = sino_all_channels[:,i,:] # extract a sinogram for i-th channel + + print ("Initial guess") + x_init = ImageData(geometry=ig) + + # Create least squares object instance with projector and data. + print ("Create least squares object instance with projector and data.") + f = Norm2sq(Aop,DataContainer(sino_channel),c=0.5) + + print ("Run FISTA-TV for least squares") + lamtv = 5 + opt = {'tol': 1e-4, 'iter': 200} + g_fgp = FGP_TV(lambdaReg = lamtv, + iterationsTV=50, + tolerance=1e-6, + methodTV=0, + nonnegativity=0, + printing=0, + device='gpu') + + x_fista_fgp, it1, timing1, criter_fgp = FISTA(x_init, f, g_fgp, opt) + REC_chan[i,:,:] = x_fista_fgp.array + """ + plt.figure() + plt.subplot(121) + plt.imshow(x_fista_fgp.array, vmin=0, vmax=0.05) + plt.title('FISTA FGP TV') + plt.subplot(122) + plt.semilogy(criter_fgp) + plt.show() + """ + """ + print ("Run CGLS for least squares") + opt = {'tol': 1e-4, 'iter': 20} + x_init = ImageData(geometry=ig) + x_CGLS, it_CGLS, timing_CGLS, criter_CGLS = CGLS(x_init, Aop, DataContainer(sino_channel), opt=opt) + + plt.figure() + plt.imshow(x_CGLS.array,vmin=0, vmax=0.05) + plt.title('CGLS') + plt.show() + """ +# Saving images into fits using astrapy if required +counter = 0 +filename = 'FISTA_TV_imat_slice' +for i in range(totChannels): + im = REC_chan[i,:,:] + add_val = np.min(im[:]) + im += abs(add_val) + im = im/np.max(im[:])*65535 + outfile = '{!s}_{!s}_{!s}.fits'.format(filename,str(selectedVertical_slice),str(counter)) + hdu = fits.PrimaryHDU(np.uint16(im)) + hdu.writeto(outfile, overwrite=True) + counter += 1 \ No newline at end of file diff --git a/Wrappers/Python/wip/old_demos/demo_imat_whitebeam.py b/Wrappers/Python/wip/old_demos/demo_imat_whitebeam.py new file mode 100644 index 0000000..e0d213e --- /dev/null +++ b/Wrappers/Python/wip/old_demos/demo_imat_whitebeam.py @@ -0,0 +1,138 @@ +# This script demonstrates how to load IMAT fits data +# into the CIL optimisation framework and run reconstruction methods. +# +# This demo loads the summedImg files which are the non-spectral images +# resulting from summing projections over all spectral channels. + +# needs dxchange: conda install -c conda-forge dxchange +# needs astropy: conda install astropy + + +# All third-party imports. +import numpy +from scipy.io import loadmat +import matplotlib.pyplot as plt +from dxchange.reader import read_fits + +# All own imports. +from ccpi.framework import AcquisitionData, AcquisitionGeometry, ImageGeometry, ImageData +from ccpi.astra.ops import AstraProjectorSimple, AstraProjector3DSimple +from ccpi.optimisation.algs import CGLS, FISTA +from ccpi.optimisation.funcs import Norm2sq, Norm1 + +# Load and display a couple of summed projection as examples +pathname0 = '/media/newhd/shared/Data/neutrondata/PSI_phantom_IMAT/DATA/Sample/angle0/' +filename0 = 'IMAT00004675_Tomo_test_000_SummedImg.fits' + +data0 = read_fits(pathname0 + filename0) + +pathname10 = '/media/newhd/shared/Data/neutrondata/PSI_phantom_IMAT/DATA/Sample/angle10/' +filename10 = 'IMAT00004685_Tomo_test_000_SummedImg.fits' + +data10 = read_fits(pathname10 + filename10) + +# Load a flat field (more are available, should we average over them?) +flat1 = read_fits('/media/newhd/shared/Data/neutrondata/PSI_phantom_IMAT/DATA/OpenBeam_aft1/IMAT00004932_Tomo_test_000_SummedImg.fits') + +# Apply flat field and display after flat-field correction and negative log +data0_rel = numpy.zeros(numpy.shape(flat1), dtype = float) +nonzero = flat1 > 0 +data0_rel[nonzero] = data0[nonzero] / flat1[nonzero] +data10_rel = numpy.zeros(numpy.shape(flat1), dtype = float) +data10_rel[nonzero] = data10[nonzero] / flat1[nonzero] + +plt.imshow(data0_rel) +plt.colorbar() +plt.show() + +plt.imshow(-numpy.log(data0_rel)) +plt.colorbar() +plt.show() + +plt.imshow(data10_rel) +plt.colorbar() +plt.show() + +plt.imshow(-numpy.log(data10_rel)) +plt.colorbar() +plt.show() + +# Set up for loading all summed images at 250 angles. +pathname = '/media/newhd/shared/Data/neutrondata/PSI_phantom_IMAT/DATA/Sample/angle{}/' +filename = 'IMAT0000{}_Tomo_test_000_SummedImg.fits' + +# Dimensions +num_angles = 250 +imsize = 512 + +# Initialise array +data = numpy.zeros((num_angles,imsize,imsize)) + +# Load only 0-249, as 250 is at repetition of zero degrees just like image 0 +for i in range(0,250): + curimfile = (pathname + filename).format(i, i+4675) + data[i,:,:] = read_fits(curimfile) + +# Apply flat field and take negative log +nonzero = flat1 > 0 +for i in range(0,250): + data[i,nonzero] = data[i,nonzero]/flat1[nonzero] + +eqzero = data == 0 +data[eqzero] = 1 + +data_rel = -numpy.log(data) + +# Permute order to get: angles, vertical, horizontal, as default in framework. +data_rel = numpy.transpose(data_rel,(0,2,1)) + +# Set angles to use +angles = numpy.linspace(-numpy.pi,numpy.pi,num_angles,endpoint=False) + +# Create 3D acquisition geometry and acquisition data +ag = AcquisitionGeometry('parallel', + '3D', + angles, + pixel_num_h=imsize, + pixel_num_v=imsize) +b = AcquisitionData(data_rel, geometry=ag) + +# Reduce to single (noncentral) slice by extracting relevant parameters from data and its +# geometry. Perhaps create function to extract central slice automatically? +b2d = b.subset(vertical=128) +ag2d = AcquisitionGeometry('parallel', + '2D', + ag.angles, + pixel_num_h=ag.pixel_num_h) +b2d.geometry = ag2d + +# Create 2D image geometry +ig2d = ImageGeometry(voxel_num_x=ag2d.pixel_num_h, + voxel_num_y=ag2d.pixel_num_h) + +# Create GPU projector/backprojector operator with ASTRA. +Aop = AstraProjectorSimple(ig2d,ag2d,'gpu') + +# Demonstrate operator is working by applying simple backprojection. +z = Aop.adjoint(b2d) +plt.imshow(z.array) +plt.title('Simple backprojection') +plt.colorbar() +plt.show() + +# Set initial guess ImageData with zeros for algorithms, and algorithm options. +x_init = ImageData(numpy.zeros((imsize,imsize)), + geometry=ig2d) +opt_CGLS = {'tol': 1e-4, 'iter': 20} + +# Run CGLS algorithm and display reconstruction. +x_CGLS, it_CGLS, timing_CGLS, criter_CGLS = CGLS(x_init, Aop, b2d, opt_CGLS) + +plt.imshow(x_CGLS.array) +plt.title('CGLS') +plt.colorbar() +plt.show() + +plt.semilogy(criter_CGLS) +plt.title('CGLS Criterion vs iterations') +plt.show() \ No newline at end of file diff --git a/Wrappers/Python/wip/old_demos/demo_memhandle.py b/Wrappers/Python/wip/old_demos/demo_memhandle.py new file mode 100755 index 0000000..db48d73 --- /dev/null +++ b/Wrappers/Python/wip/old_demos/demo_memhandle.py @@ -0,0 +1,193 @@ + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, DataContainer +from ccpi.optimisation.algs import FISTA, FBPD, CGLS +from ccpi.optimisation.funcs import Norm2sq, ZeroFun, Norm1, TV2D + +from ccpi.optimisation.ops import LinearOperatorMatrix, Identity +from ccpi.optimisation.ops import TomoIdentity + +# Requires CVXPY, see http://www.cvxpy.org/ +# CVXPY can be installed in anaconda using +# conda install -c cvxgrp cvxpy libgcc + + +import numpy as np +import matplotlib.pyplot as plt + +# Problem data. +m = 30 +n = 20 +np.random.seed(1) +Amat = np.random.randn(m, n) +A = LinearOperatorMatrix(Amat) +bmat = np.random.randn(m) +bmat.shape = (bmat.shape[0],1) + + + +# A = Identity() +# Change n to equal to m. + +b = DataContainer(bmat) + +# Regularization parameter +lam = 10 + +# Create object instances with the test data A and b. +f = Norm2sq(A,b,c=0.5, memopt=True) +g0 = ZeroFun() + +# Initial guess +x_init = DataContainer(np.zeros((n,1))) + +f.grad(x_init) +opt = {'memopt': True} +# Run FISTA for least squares plus zero function. +x_fista0, it0, timing0, criter0 = FISTA(x_init, f, g0) +x_fista0_m, it0_m, timing0_m, criter0_m = FISTA(x_init, f, g0, opt=opt) + +iternum = [i for i in range(len(criter0))] +# Print solution and final objective/criterion value for comparison +print("FISTA least squares plus zero function solution and objective value:") +print(x_fista0.array) +print(criter0[-1]) + + +# Plot criterion curve to see FISTA converge to same value as CVX. +#iternum = np.arange(1,1001) +plt.figure() +plt.loglog(iternum,criter0,label='FISTA LS') +plt.loglog(iternum,criter0_m,label='FISTA LS memopt') +plt.legend() +plt.show() +#%% +# Create 1-norm object instance +g1 = Norm1(lam) + +g1(x_init) +g1.prox(x_init,0.02) + +# Combine with least squares and solve using generic FISTA implementation +x_fista1, it1, timing1, criter1 = FISTA(x_init, f, g1) +x_fista1_m, it1_m, timing1_m, criter1_m = FISTA(x_init, f, g1, opt=opt) +iternum = [i for i in range(len(criter1))] +# Print for comparison +print("FISTA least squares plus 1-norm solution and objective value:") +print(x_fista1) +print(criter1[-1]) + + +# Now try another algorithm FBPD for same problem: +x_fbpd1, itfbpd1, timingfbpd1, criterfbpd1 = FBPD(x_init, None, f, g1) +iternum = [i for i in range(len(criterfbpd1))] +print(x_fbpd1) +print(criterfbpd1[-1]) + +# Plot criterion curve to see both FISTA and FBPD converge to same value. +# Note that FISTA is very efficient for 1-norm minimization so it beats +# FBPD in this test by a lot. But FBPD can handle a larger class of problems +# than FISTA can. +plt.figure() +plt.loglog(iternum,criter1,label='FISTA LS+1') +plt.loglog(iternum,criter1_m,label='FISTA LS+1 memopt') +plt.legend() +plt.show() + +plt.figure() +plt.loglog(iternum,criter1,label='FISTA LS+1') +plt.loglog(iternum,criterfbpd1,label='FBPD LS+1') +plt.legend() +plt.show() +#%% +# Now try 1-norm and TV denoising with FBPD, first 1-norm. + +# Set up phantom size NxN by creating ImageGeometry, initialising the +# ImageData object with this geometry and empty array and finally put some +# data into its array, and display as image. +N = 1000 +ig = ImageGeometry(voxel_num_x=N,voxel_num_y=N) +Phantom = ImageData(geometry=ig) + +x = Phantom.as_array() +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +plt.imshow(x) +plt.title('Phantom image') +plt.show() + +# Identity operator for denoising +I = TomoIdentity(ig) + +# Data and add noise +y = I.direct(Phantom) +y.array += 0.1*np.random.randn(N, N) + +plt.figure() +plt.imshow(y.array) +plt.title('Noisy image') +plt.show() + +# Data fidelity term +f_denoise = Norm2sq(I,y,c=0.5, memopt=True) + +# 1-norm regulariser +lam1_denoise = 1.0 +g1_denoise = Norm1(lam1_denoise) + +# Initial guess +x_init_denoise = ImageData(np.zeros((N,N))) +opt = {'memopt': False, 'iter' : 50} +# Combine with least squares and solve using generic FISTA implementation +print ("no memopt") +x_fista1_denoise, it1_denoise, timing1_denoise, \ + criter1_denoise = FISTA(x_init_denoise, f_denoise, g1_denoise, opt=opt) +opt = {'memopt': True, 'iter' : 50} +print ("yes memopt") +x_fista1_denoise_m, it1_denoise_m, timing1_denoise_m, \ + criter1_denoise_m = FISTA(x_init_denoise, f_denoise, g1_denoise, opt=opt) + +print(x_fista1_denoise) +print(criter1_denoise[-1]) + +plt.figure() +plt.imshow(x_fista1_denoise.as_array()) +plt.title('FISTA LS+1') +plt.show() + +plt.figure() +plt.imshow(x_fista1_denoise_m.as_array()) +plt.title('FISTA LS+1 memopt') +plt.show() + +plt.figure() +plt.loglog(iternum,criter1_denoise,label='FISTA LS+1') +plt.loglog(iternum,criter1_denoise_m,label='FISTA LS+1 memopt') +plt.legend() +plt.show() +#%% +# Now denoise LS + 1-norm with FBPD +x_fbpd1_denoise, itfbpd1_denoise, timingfbpd1_denoise, criterfbpd1_denoise = FBPD(x_init_denoise, None, f_denoise, g1_denoise) +print(x_fbpd1_denoise) +print(criterfbpd1_denoise[-1]) + +plt.figure() +plt.imshow(x_fbpd1_denoise.as_array()) +plt.title('FBPD LS+1') +plt.show() + + +# Now TV with FBPD +lam_tv = 0.1 +gtv = TV2D(lam_tv) +gtv(gtv.op.direct(x_init_denoise)) + +opt_tv = {'tol': 1e-4, 'iter': 10000} + +x_fbpdtv_denoise, itfbpdtv_denoise, timingfbpdtv_denoise, criterfbpdtv_denoise = FBPD(x_init_denoise, None, f_denoise, gtv,opt=opt_tv) +print(x_fbpdtv_denoise) +print(criterfbpdtv_denoise[-1]) + +plt.imshow(x_fbpdtv_denoise.as_array()) +plt.title('FBPD TV') +plt.show() diff --git a/Wrappers/Python/wip/old_demos/demo_test_sirt.py b/Wrappers/Python/wip/old_demos/demo_test_sirt.py new file mode 100644 index 0000000..6f5a44d --- /dev/null +++ b/Wrappers/Python/wip/old_demos/demo_test_sirt.py @@ -0,0 +1,176 @@ +# This demo illustrates how to use the SIRT algorithm without and with +# nonnegativity and box constraints. The ASTRA 2D projectors are used. + +# First make all imports +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, \ + AcquisitionData +from ccpi.optimisation.algs import FISTA, FBPD, CGLS, SIRT +from ccpi.optimisation.funcs import Norm2sq, Norm1, TV2D, IndicatorBox +from ccpi.astra.ops import AstraProjectorSimple + +import numpy as np +import matplotlib.pyplot as plt + +# Choose either a parallel-beam (1=parallel2D) or fan-beam (2=cone2D) test case +test_case = 1 + +# Set up phantom size NxN by creating ImageGeometry, initialising the +# ImageData object with this geometry and empty array and finally put some +# data into its array, and display as image. +N = 128 +ig = ImageGeometry(voxel_num_x=N,voxel_num_y=N) +Phantom = ImageData(geometry=ig) + +x = Phantom.as_array() +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +plt.imshow(x) +plt.title('Phantom image') +plt.show() + +# Set up AcquisitionGeometry object to hold the parameters of the measurement +# setup geometry: # Number of angles, the actual angles from 0 to +# pi for parallel beam and 0 to 2pi for fanbeam, set the width of a detector +# pixel relative to an object pixel, the number of detector pixels, and the +# source-origin and origin-detector distance (here the origin-detector distance +# set to 0 to simulate a "virtual detector" with same detector pixel size as +# object pixel size). +angles_num = 20 +det_w = 1.0 +det_num = N +SourceOrig = 200 +OrigDetec = 0 + +if test_case==1: + angles = np.linspace(0,np.pi,angles_num,endpoint=False) + ag = AcquisitionGeometry('parallel', + '2D', + angles, + det_num,det_w) +elif test_case==2: + angles = np.linspace(0,2*np.pi,angles_num,endpoint=False) + ag = AcquisitionGeometry('cone', + '2D', + angles, + det_num, + det_w, + dist_source_center=SourceOrig, + dist_center_detector=OrigDetec) +else: + NotImplemented + +# Set up Operator object combining the ImageGeometry and AcquisitionGeometry +# wrapping calls to ASTRA as well as specifying whether to use CPU or GPU. +Aop = AstraProjectorSimple(ig, ag, 'gpu') + +# Forward and backprojection are available as methods direct and adjoint. Here +# generate test data b and do simple backprojection to obtain z. +b = Aop.direct(Phantom) +z = Aop.adjoint(b) + +plt.imshow(b.array) +plt.title('Simulated data') +plt.show() + +plt.imshow(z.array) +plt.title('Backprojected data') +plt.show() + +# Using the test data b, different reconstruction methods can now be set up as +# demonstrated in the rest of this file. In general all methods need an initial +# guess and some algorithm options to be set: +x_init = ImageData(np.zeros(x.shape),geometry=ig) +opt = {'tol': 1e-4, 'iter': 1000} + +# First a CGLS reconstruction can be done: +x_CGLS, it_CGLS, timing_CGLS, criter_CGLS = CGLS(x_init, Aop, b, opt) + +plt.imshow(x_CGLS.array) +plt.title('CGLS') +plt.colorbar() +plt.show() + +plt.semilogy(criter_CGLS) +plt.title('CGLS criterion') +plt.show() + +# A SIRT unconstrained reconstruction can be done: similarly: +x_SIRT, it_SIRT, timing_SIRT, criter_SIRT = SIRT(x_init, Aop, b, opt) + +plt.imshow(x_SIRT.array) +plt.title('SIRT unconstrained') +plt.colorbar() +plt.show() + +plt.semilogy(criter_SIRT) +plt.title('SIRT unconstrained criterion') +plt.show() + +# A SIRT nonnegativity constrained reconstruction can be done using the +# additional input "constraint" set to a box indicator function with 0 as the +# lower bound and the default upper bound of infinity: +x_SIRT0, it_SIRT0, timing_SIRT0, criter_SIRT0 = SIRT(x_init, Aop, b, opt, + constraint=IndicatorBox(lower=0)) + +plt.imshow(x_SIRT0.array) +plt.title('SIRT nonneg') +plt.colorbar() +plt.show() + +plt.semilogy(criter_SIRT0) +plt.title('SIRT nonneg criterion') +plt.show() + +# A SIRT reconstruction with box constraints on [0,1] can also be done: +x_SIRT01, it_SIRT01, timing_SIRT01, criter_SIRT01 = SIRT(x_init, Aop, b, opt, + constraint=IndicatorBox(lower=0,upper=1)) + +plt.imshow(x_SIRT01.array) +plt.title('SIRT box(0,1)') +plt.colorbar() +plt.show() + +plt.semilogy(criter_SIRT01) +plt.title('SIRT box(0,1) criterion') +plt.show() + +# The indicator function can also be used with the FISTA algorithm to do +# least squares with nonnegativity constraint. + +# Create least squares object instance with projector, test data and a constant +# coefficient of 0.5: +f = Norm2sq(Aop,b,c=0.5) + +# Run FISTA for least squares without constraints +x_fista, it, timing, criter = FISTA(x_init, f, None,opt) + +plt.imshow(x_fista.array) +plt.title('FISTA Least squares') +plt.show() + +plt.semilogy(criter) +plt.title('FISTA Least squares criterion') +plt.show() + +# Run FISTA for least squares with nonnegativity constraint +x_fista0, it0, timing0, criter0 = FISTA(x_init, f, IndicatorBox(lower=0),opt) + +plt.imshow(x_fista0.array) +plt.title('FISTA Least squares nonneg') +plt.show() + +plt.semilogy(criter0) +plt.title('FISTA Least squares nonneg criterion') +plt.show() + +# Run FISTA for least squares with box constraint [0,1] +x_fista01, it01, timing01, criter01 = FISTA(x_init, f, IndicatorBox(lower=0,upper=1),opt) + +plt.imshow(x_fista01.array) +plt.title('FISTA Least squares box(0,1)') +plt.show() + +plt.semilogy(criter01) +plt.title('FISTA Least squares box(0,1) criterion') +plt.show() \ No newline at end of file diff --git a/Wrappers/Python/wip/old_demos/multifile_nexus.py b/Wrappers/Python/wip/old_demos/multifile_nexus.py new file mode 100755 index 0000000..d1ad438 --- /dev/null +++ b/Wrappers/Python/wip/old_demos/multifile_nexus.py @@ -0,0 +1,307 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Aug 15 16:00:53 2018 + +@author: ofn77899 +""" + +import os +from ccpi.io.reader import NexusReader + +from sys import getsizeof + +import matplotlib.pyplot as plt + +from ccpi.framework import DataProcessor, DataContainer +from ccpi.processors import Normalizer +from ccpi.processors import CenterOfRotationFinder +import numpy + + +class averager(object): + def __init__(self): + self.reset() + + def reset(self): + self.N = 0 + self.avg = 0 + self.min = 0 + self.max = 0 + self.var = 0 + self.ske = 0 + self.kur = 0 + + def add_reading(self, val): + + if (self.N == 0): + self.avg = val; + self.min = val; + self.max = val; + elif (self.N == 1): + #//set min/max + self.max = val if val > self.max else self.max + self.min = val if val < self.min else self.min + + + thisavg = (self.avg + val)/2 + #// initial value is in avg + self.var = (self.avg - thisavg)*(self.avg-thisavg) + (val - thisavg) * (val-thisavg) + self.ske = self.var * (self.avg - thisavg) + self.kur = self.var * self.var + self.avg = thisavg + else: + self.max = val if val > self.max else self.max + self.min = val if val < self.min else self.min + + M = self.N + + #// b-factor =(_N + v_(N+1)) / (N+1) + #float b = (val -avg) / (M+1); + b = (val -self.avg) / (M+1) + + self.kur = self.kur + (M *(b*b*b*b) * (1+M*M*M))- (4* b * self.ske) + (6 * b *b * self.var * (M-1)) + + self.ske = self.ske + (M * b*b*b *(M-1)*(M+1)) - (3 * b * self.var * (M-1)) + + #//var = var * ((M-1)/M) + ((val - avg)*(val - avg)/(M+1)) ; + self.var = self.var * ((M-1)/M) + (b * b * (M+1)) + + self.avg = self.avg * (M/(M+1)) + val / (M+1) + + self.N += 1 + + def stats(self, vector): + i = 0 + while i < vector.size: + self.add_reading(vector[i]) + i+=1 + +avg = averager() +a = numpy.linspace(0,39,40) +avg.stats(a) +print ("average" , avg.avg, a.mean()) +print ("variance" , avg.var, a.var()) +b = a - a.mean() +b *= b +b = numpy.sqrt(sum(b)/(a.size-1)) +print ("std" , numpy.sqrt(avg.var), b) +#%% + +class DataStatMoments(DataProcessor): + '''Normalization based on flat and dark + + This processor read in a AcquisitionData and normalises it based on + the instrument reading with and without incident photons or neutrons. + + Input: AcquisitionData + Parameter: 2D projection with flat field (or stack) + 2D projection with dark field (or stack) + Output: AcquisitionDataSetn + ''' + + def __init__(self, axis, skewness=False, kurtosis=False, offset=0): + kwargs = { + 'axis' : axis, + 'skewness' : skewness, + 'kurtosis' : kurtosis, + 'offset' : offset, + } + #DataProcessor.__init__(self, **kwargs) + super(DataStatMoments, self).__init__(**kwargs) + + + def check_input(self, dataset): + #self.N = dataset.get_dimension_size(self.axis) + return True + + @staticmethod + def add_sample(dataset, N, axis, stats=None, offset=0): + #dataset = self.get_input() + if (N == 0): + # get the axis number along to calculate the stats + + + #axs = dataset.get_dimension_size(self.axis) + # create a placeholder for the output + if stats is None: + ax = dataset.get_dimension_axis(axis) + shape = [dataset.shape[i] for i in range(len(dataset.shape)) if i != ax] + # output has 4 components avg, std, skewness and kurtosis + last avg+ (val-thisavg) + shape.insert(0, 4+2) + stats = numpy.zeros(shape) + + stats[0] = dataset.subset(**{axis:N-offset}).array[:] + + #avg = val + elif (N == 1): + # val + stats[5] = dataset.subset(**{axis:N-offset}).array + stats[4] = stats[0] + stats[5] + stats[4] /= 2 # thisavg + stats[5] -= stats[4] # (val - thisavg) + + #float thisavg = (avg + val)/2; + + #// initial value is in avg + #var = (avg - thisavg)*(avg-thisavg) + (val - thisavg) * (val-thisavg); + stats[1] = stats[5] * stats[5] + stats[5] * stats[5] + #skewness = var * (avg - thisavg); + stats[2] = stats[1] * stats[5] + #kurtosis = var * var; + stats[3] = stats[1] * stats[1] + #avg = thisavg; + stats[0] = stats[4] + else: + + #float M = (float)N; + M = N + #val + stats[4] = dataset.subset(**{axis:N-offset}).array + #// b-factor =(_N + v_(N+1)) / (N+1) + stats[5] = stats[4] - stats[0] + stats[5] /= (M+1) # b factor + #float b = (val -avg) / (M+1); + + #kurtosis = kurtosis + (M *(b*b*b*b) * (1+M*M*M))- (4* b * skewness) + (6 * b *b * var * (M-1)); + #if self.kurtosis: + # stats[3] += (M * stats[5] * stats[5] * stats[5] * stats[5]) - \ + # (4 * stats[5] * stats[2]) + \ + # (6 * stats[5] * stats[5] * stats[1] * (M-1)) + + #skewness = skewness + (M * b*b*b *(M-1)*(M+1)) - (3 * b * var * (M-1)); + #if self.skewness: + # stats[2] = stats[2] + (M * stats[5]* stats[5] * stats[5] * (M-1)*(M-1) ) -\ + # 3 * stats[5] * stats[1] * (M-1) + #//var = var * ((M-1)/M) + ((val - avg)*(val - avg)/(M+1)) ; + #var = var * ((M-1)/M) + (b * b * (M+1)); + stats[1] = ((M-1)/M) * stats[1] + (stats[5] * stats[5] * (M+1)) + + #avg = avg * (M/(M+1)) + val / (M+1) + stats[0] = stats[0] * (M/(M+1)) + stats[4] / (M+1) + + N += 1 + return stats , N + + + def process(self): + + data = self.get_input() + + #stats, i = add_sample(0) + N = data.get_dimension_size(self.axis) + ax = data.get_dimension_axis(self.axis) + stats = None + i = 0 + while i < N: + stats , i = DataStatMoments.add_sample(data, i, self.axis, stats, offset=self.offset) + self.offset += N + labels = ['StatisticalMoments'] + + labels += [data.dimension_labels[i] \ + for i in range(len(data.dimension_labels)) if i != ax] + y = DataContainer( stats[:4] , False, + dimension_labels=labels) + return y + +directory = r'E:\Documents\Dataset\CCPi\Nexus_test' +data_path="entry1/instrument/pco1_hw_hdf_nochunking/data" + +reader = NexusReader(os.path.join( os.path.abspath(directory) , '74331.nxs')) + +print ("read flat") +read_flat = NexusReader(os.path.join( os.path.abspath(directory) , '74240.nxs')) +read_flat.data_path = data_path +flatsslice = read_flat.get_acquisition_data_whole() +avg = DataStatMoments('angle') + +avg.set_input(flatsslice) +flats = avg.get_output() + +ave = averager() +ave.stats(flatsslice.array[:,0,0]) + +print ("avg" , ave.avg, flats.array[0][0][0]) +print ("var" , ave.var, flats.array[1][0][0]) + +print ("read dark") +read_dark = NexusReader(os.path.join( os.path.abspath(directory) , '74243.nxs')) +read_dark.data_path = data_path + +## darks are very many so we proceed in batches +total_size = read_dark.get_projection_dimensions()[0] +print ("total_size", total_size) + +batchsize = 40 +if batchsize > total_size: + batchlimits = [batchsize * (i+1) for i in range(int(total_size/batchsize))] + [total_size-1] +else: + batchlimits = [total_size] +#avg.N = 0 +avg.offset = 0 +N = 0 +for batch in range(len(batchlimits)): + print ("running batch " , batch) + bmax = batchlimits[batch] + bmin = bmax-batchsize + + darksslice = read_dark.get_acquisition_data_batch(bmin,bmax) + if batch == 0: + #create stats + ax = darksslice.get_dimension_axis('angle') + shape = [darksslice.shape[i] for i in range(len(darksslice.shape)) if i != ax] + # output has 4 components avg, std, skewness and kurtosis + last avg+ (val-thisavg) + shape.insert(0, 4+2) + print ("create stats shape ", shape) + stats = numpy.zeros(shape) + print ("N" , N) + #avg.set_input(darksslice) + i = bmin + while i < bmax: + stats , i = DataStatMoments.add_sample(darksslice, i, 'angle', stats, bmin) + print ("{0}-{1}-{2}".format(bmin, i , bmax ) ) + +darks = stats +#%% + +fig = plt.subplot(2,2,1) +fig.imshow(flats.subset(StatisticalMoments=0).array) +fig = plt.subplot(2,2,2) +fig.imshow(numpy.sqrt(flats.subset(StatisticalMoments=1).array)) +fig = plt.subplot(2,2,3) +fig.imshow(darks[0]) +fig = plt.subplot(2,2,4) +fig.imshow(numpy.sqrt(darks[1])) +plt.show() + + +#%% +norm = Normalizer(flat_field=flats.array[0,200,:], dark_field=darks[0,200,:]) +#norm.set_flat_field(flats.array[0,200,:]) +#norm.set_dark_field(darks.array[0,200,:]) +norm.set_input(reader.get_acquisition_data_slice(200)) + +n = Normalizer.normalize_projection(norm.get_input().as_array(), flats.array[0,200,:], darks[0,200,:], 1e-5) +#dn_n= Normalizer.estimate_normalised_error(norm.get_input().as_array(), flats.array[0,200,:], darks[0,200,:], +# numpy.sqrt(flats.array[1,200,:]), numpy.sqrt(darks[1,200,:])) +#%% + + +#%% +fig = plt.subplot(2,1,1) + + +fig.imshow(norm.get_input().as_array()) +fig = plt.subplot(2,1,2) +fig.imshow(n) + +#fig = plt.subplot(3,1,3) +#fig.imshow(dn_n) + + +plt.show() + + + + + + -- cgit v1.2.3 From 6b9fb1a220dc56075deb5645312c202771e54a09 Mon Sep 17 00:00:00 2001 From: Vaggelis Papoutsellis <22398586+epapoutsellis@users.noreply.github.com> Date: Tue, 7 May 2019 10:07:24 +0100 Subject: Delete test_pdhg_gap.py --- Wrappers/Python/wip/test_pdhg_gap.py | 140 ----------------------------------- 1 file changed, 140 deletions(-) delete mode 100644 Wrappers/Python/wip/test_pdhg_gap.py diff --git a/Wrappers/Python/wip/test_pdhg_gap.py b/Wrappers/Python/wip/test_pdhg_gap.py deleted file mode 100644 index 6c7ccc9..0000000 --- a/Wrappers/Python/wip/test_pdhg_gap.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Tue Apr 2 12:26:24 2019 - -@author: vaggelis -""" - - -from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer, AcquisitionGeometry, AcquisitionData - -import numpy as np -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, PDHG_old - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFun, L2NormSquared, \ - MixedL21Norm, BlockFunction, ScaledFunction - -from ccpi.astra.ops import AstraProjectorSimple -from skimage.util import random_noise - - -#%%############################################################################### -# Create phantom for TV tomography - -#import os -#import tomophantom -#from tomophantom import TomoP2D -#from tomophantom.supp.qualitymetrics import QualityTools - -#model = 1 # select a model number from the library -#N = 150 # set dimension of the phantom -## one can specify an exact path to the parameters file -## path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' -#path = os.path.dirname(tomophantom.__file__) -#path_library2D = os.path.join(path, "Phantom2DLibrary.dat") -##This will generate a N_size x N_size phantom (2D) -#phantom_2D = TomoP2D.Model(model, N, path_library2D) -#ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -#data = ImageData(phantom_2D, geometry=ig) - -N = 150 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - - -detectors = 150 -angles = np.linspace(0,np.pi,100) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -plt.imshow(sin.as_array()) -plt.title('Sinogram') -plt.colorbar() -plt.show() - -# Add Gaussian noise to the sinogram data -np.random.seed(10) -n1 = np.random.random(sin.shape) - -noisy_data = sin + ImageData(5*n1) - -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Sinogram') -plt.colorbar() -plt.show() - - -#%% Works only with Composite Operator Structure of PDHG - -#ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Form Composite Operator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -alpha = 50 -f = BlockFunction( alpha * MixedL21Norm(), \ - 0.5 * L2NormSquared(b = noisy_data) ) -g = ZeroFun() - -# Compute operator Norm -normK = operator.norm() - -## Primal & dual stepsizes - -sigma = 10 -tau = 1/(sigma*normK**2) - -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 100 - -pdhg.run(5000) -#%% - -opt = {'niter':2000} - -res = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) - -#%% -sol = pdhg.get_output().as_array() -sol_old = res[0].as_array() -fig = plt.figure(figsize=(20,10)) -plt.subplot(1,3,1) -plt.imshow(noisy_data.as_array()) -#plt.colorbar() -plt.subplot(1,3,2) -plt.imshow(sol) -#plt.colorbar() - -plt.subplot(1,3,3) -plt.imshow(sol_old) -plt.show() - -plt.imshow(np.abs(sol-sol_old)) -plt.colorbar() -plt.show() - - -# -# -##%% -#plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -#plt.plot(np.linspace(0,N,N), sol[int(N/2),:], label = 'Recon') -#plt.legend() -#plt.show() - - -- cgit v1.2.3 From 5ba708aee8dd9e69c9302bb2704c991a9721941b Mon Sep 17 00:00:00 2001 From: Vaggelis Papoutsellis <22398586+epapoutsellis@users.noreply.github.com> Date: Tue, 7 May 2019 10:07:34 +0100 Subject: Delete test_profile.py --- Wrappers/Python/wip/test_profile.py | 84 ------------------------------------- 1 file changed, 84 deletions(-) delete mode 100644 Wrappers/Python/wip/test_profile.py diff --git a/Wrappers/Python/wip/test_profile.py b/Wrappers/Python/wip/test_profile.py deleted file mode 100644 index f14c0c3..0000000 --- a/Wrappers/Python/wip/test_profile.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Mon Apr 8 13:57:46 2019 - -@author: evangelos -""" - -# profile direct, adjoint, gradient - -from ccpi.framework import ImageGeometry -from ccpi.optimisation.operators import Gradient, BlockOperator, Identity -from ccpi.optimisation.functions import MixedL21Norm, L2NormSquared, BlockFunction -import numpy - -N, M, K = 2, 3, 2 - -ig = ImageGeometry(N, M) -b = ig.allocate('random_int') - -G = Gradient(ig) -Id = Identity(ig) - -#operator = BlockOperator(G, Id) -operator = G - -f1 = MixedL21Norm() -f2 = L2NormSquared(b = b) - -f = BlockFunction( f1, f2) - - -x_old = operator.domain_geometry().allocate() -y_old = operator.range_geometry().allocate('random_int') - - -xbar = operator.domain_geometry().allocate('random_int') - -x_tmp = x_old.copy() -x = x_old.copy() - -y_tmp = operator.range_geometry().allocate() -y = y_old.copy() - -y1 = y.copy() - -sigma = 20 - -for i in range(100): - - operator.direct(xbar, out = y_tmp) - y_tmp *= sigma - y_tmp += y_old - - - y_tmp1 = sigma * operator.direct(xbar) + y_old - - print(i) - print(" y_old :", y_old[0].as_array(), "\n") - print(" y_tmp[0] :", y_tmp[0].as_array(),"\n") - print(" y_tmp1[0] :", y_tmp1[0].as_array()) - - - numpy.testing.assert_array_equal(y_tmp[0].as_array(), \ - y_tmp1[0].as_array()) - - numpy.testing.assert_array_equal(y_tmp[1].as_array(), \ - y_tmp1[1].as_array()) - - - y1 = f.proximal_conjugate(y_tmp1, sigma) - f.proximal_conjugate(y_tmp, sigma, y) - - - - - - - - - - - - \ No newline at end of file -- cgit v1.2.3 From 640feb256a172c22ef4ab78f792e214f62ef5beb Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 7 May 2019 10:10:10 +0100 Subject: allows passing update_objective_interval in creator --- Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py index c923a30..a14378c 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py @@ -52,7 +52,7 @@ class Algorithm(object): self.__loss = [] self.memopt = False self.timing = [] - self.update_objective_interval = 1 + self.update_objective_interval = kwargs.get('update_objective_interval', 1) def set_up(self, *args, **kwargs): '''Set up the algorithm''' raise NotImplementedError() -- cgit v1.2.3 From 6a8e5a212679dea33e767a2f30bac4e88bce21fe Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 7 May 2019 11:17:00 +0100 Subject: update astra calls --- Wrappers/Python/wip/pdhg_TV_tomography2D.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Wrappers/Python/wip/pdhg_TV_tomography2D.py b/Wrappers/Python/wip/pdhg_TV_tomography2D.py index cd91409..1be3dfa 100644 --- a/Wrappers/Python/wip/pdhg_TV_tomography2D.py +++ b/Wrappers/Python/wip/pdhg_TV_tomography2D.py @@ -19,8 +19,7 @@ from ccpi.optimisation.operators import BlockOperator, Gradient from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ MixedL21Norm, BlockFunction -from ccpi.astra.ops import AstraProjectorSimple -from skimage.util import random_noise +from ccpi.astra.operators import AstraProjectorSimple from timeit import default_timer as timer -- cgit v1.2.3 From d1fbb8b98862eeaddcda29ebf76e590212103ad8 Mon Sep 17 00:00:00 2001 From: Vaggelis Papoutsellis <22398586+epapoutsellis@users.noreply.github.com> Date: Tue, 7 May 2019 11:48:47 +0100 Subject: Update yaml Add matplotlib --- Wrappers/Python/conda-recipe/meta.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Wrappers/Python/conda-recipe/meta.yaml b/Wrappers/Python/conda-recipe/meta.yaml index ac5f763..6564014 100644 --- a/Wrappers/Python/conda-recipe/meta.yaml +++ b/Wrappers/Python/conda-recipe/meta.yaml @@ -33,6 +33,7 @@ requirements: - python - numpy - scipy + - matplotlib - h5py about: -- cgit v1.2.3 From 4dde7da625de12ccf0e319ae8887f60d61d19de8 Mon Sep 17 00:00:00 2001 From: Vaggelis Papoutsellis <22398586+epapoutsellis@users.noreply.github.com> Date: Tue, 7 May 2019 11:59:12 +0100 Subject: Delete __init__.py --- Wrappers/Python/build/lib/ccpi/__init__.py | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 Wrappers/Python/build/lib/ccpi/__init__.py diff --git a/Wrappers/Python/build/lib/ccpi/__init__.py b/Wrappers/Python/build/lib/ccpi/__init__.py deleted file mode 100644 index cf2d93d..0000000 --- a/Wrappers/Python/build/lib/ccpi/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. \ No newline at end of file -- cgit v1.2.3 From 938c68e441a767b188a39b631daab0d2bbf65784 Mon Sep 17 00:00:00 2001 From: Vaggelis Papoutsellis <22398586+epapoutsellis@users.noreply.github.com> Date: Tue, 7 May 2019 12:00:42 +0100 Subject: Delete __init__.py --- Wrappers/Python/build/lib/ccpi/contrib/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Wrappers/Python/build/lib/ccpi/contrib/__init__.py diff --git a/Wrappers/Python/build/lib/ccpi/contrib/__init__.py b/Wrappers/Python/build/lib/ccpi/contrib/__init__.py deleted file mode 100644 index e69de29..0000000 -- cgit v1.2.3 From 80eace33506a53e5a46099dc341941d5d3aab341 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 7 May 2019 12:06:00 +0100 Subject: add cvx script --- Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py | 203 ++++++++++++++-------------- 1 file changed, 101 insertions(+), 102 deletions(-) diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py b/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py index 75286e5..87d5328 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Tomo2D.py @@ -110,7 +110,6 @@ else: tau = 1/(sigma*normK**2) - # Setup and run the PDHG algorithm pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) pdhg.max_iteration = 2000 @@ -143,104 +142,104 @@ plt.show() #%% Check with CVX solution -#from ccpi.optimisation.operators import SparseFiniteDiff -#import astra -#import numpy -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -# -#if cvx_not_installable: -# -# -# ##Construct problem -# u = Variable(N*N) -# #q = Variable() -# -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) -# -# # create matrix representation for Astra operator -# -# vol_geom = astra.create_vol_geom(N, N) -# proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) -# -# proj_id = astra.create_projector('strip', proj_geom, vol_geom) -# -# matrix_id = astra.projector.matrix(proj_id) -# -# ProjMat = astra.matrix.get(matrix_id) -# -# fidelity = sum( ProjMat * u - noisy_data.as_array().ravel() * log(ProjMat * u)) -# #constraints = [q>= fidelity, u>=0] -# constraints = [u>=0] -# -# solver = SCS -# obj = Minimize( regulariser + fidelity) -# prob = Problem(obj, constraints) -# result = prob.solve(verbose = True, solver = solver) -# -# -###%% Check with CVX solution -# -#from ccpi.optimisation.operators import SparseFiniteDiff -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -# -#if cvx_not_installable: -# -# ##Construct problem -# u = Variable(ig.shape) -# -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# # Define Total Variation as a regulariser -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) -# fidelity = pnorm( u - noisy_data.as_array(),1) -# -# # choose solver -# if 'MOSEK' in installed_solvers(): -# solver = MOSEK -# else: -# solver = SCS -# -# obj = Minimize( regulariser + fidelity) -# prob = Problem(obj) -# result = prob.solve(verbose = True, solver = solver) -# -# -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(pdhg.get_output().as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# plt.subplot(3,1,2) -# plt.imshow(np.reshape(u.value, (N, N))) -# plt.title('CVX solution') -# plt.colorbar() -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') -# plt.legend() -# plt.title('Middle Line Profiles') -# plt.show() -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file +from ccpi.optimisation.operators import SparseFiniteDiff +import astra +import numpy + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + + ##Construct problem + u = Variable(N*N) + #q = Variable() + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + + # create matrix representation for Astra operator + + vol_geom = astra.create_vol_geom(N, N) + proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + + proj_id = astra.create_projector('strip', proj_geom, vol_geom) + + matrix_id = astra.projector.matrix(proj_id) + + ProjMat = astra.matrix.get(matrix_id) + + fidelity = sum( ProjMat * u - noisy_data.as_array().ravel() * log(ProjMat * u)) + #constraints = [q>= fidelity, u>=0] + constraints = [u>=0] + + solver = SCS + obj = Minimize( regulariser + fidelity) + prob = Problem(obj, constraints) + result = prob.solve(verbose = True, solver = solver) + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = pnorm( u - noisy_data.as_array(),1) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(np.reshape(u.value, (N, N))) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file -- cgit v1.2.3 From 08d6eb85c8c42e54e5cd24cf574dac5edcf68c7c Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 7 May 2019 12:07:09 +0100 Subject: try precond --- .../PDHG_TV_Denoising_Gaussian_DiagPrecond.py | 208 +++++++++++++++++++++ .../wip/Demos/check_blockOperator_sum_row_cols.py | 89 +++++++++ Wrappers/Python/wip/Demos/check_precond.py | 182 ++++++++++++++++++ 3 files changed, 479 insertions(+) create mode 100644 Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian_DiagPrecond.py create mode 100644 Wrappers/Python/wip/Demos/check_blockOperator_sum_row_cols.py create mode 100644 Wrappers/Python/wip/Demos/check_precond.py diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian_DiagPrecond.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian_DiagPrecond.py new file mode 100644 index 0000000..d65478c --- /dev/null +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian_DiagPrecond.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" +Total Variation Denoising using PDHG algorithm + +Problem: min_x \alpha * ||\nabla x||_{1} + || x - g ||_{2}^{2} + + \nabla: Gradient operator + g: Noisy Data with Gaussian Noise + \alpha: Regularization parameter + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + + +# Create phantom for TV Gaussian denoising +N = 400 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Add Gaussian noise +np.random.seed(10) +noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.05, size=ig.shape) ) + +# Regularisation Parameter +alpha = 2 + +method = '1' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = 0.5 * L2NormSquared(b = noisy_data) + + +diag_precon = False + + +if diag_precon: + + def tau_sigma_precond(operator): + + tau = 1/operator.sum_abs_col() + sigma = 1/operator.sum_abs_row() + + sigma[0].as_array()[sigma[0].as_array()==np.inf]=0 + sigma[1].as_array()[sigma[1].as_array()==np.inf]=0 + + return tau, sigma + + tau, sigma = tau_sigma_precond(operator) + +else: + # Compute operator Norm + normK = operator.norm() + + # Primal & dual stepsizes + sigma = 1 + tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 3000 +pdhg.update_objective_interval = 200 +pdhg.run(3000, verbose=False) + +#%% +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = MOSEK) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'Truth') + + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/wip/Demos/check_blockOperator_sum_row_cols.py b/Wrappers/Python/wip/Demos/check_blockOperator_sum_row_cols.py new file mode 100644 index 0000000..bdb2c38 --- /dev/null +++ b/Wrappers/Python/wip/Demos/check_blockOperator_sum_row_cols.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri May 3 13:10:09 2019 + +@author: evangelos +""" + +from ccpi.optimisation.operators import FiniteDiff, SparseFiniteDiff, BlockOperator, Gradient +from ccpi.framework import ImageGeometry, AcquisitionGeometry, BlockDataContainer, ImageData +from ccpi.astra.ops import AstraProjectorSimple + +from scipy import sparse +import numpy as np + +N = 3 +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +u = ig.allocate('random_int') + +# Compare FiniteDiff with SparseFiniteDiff + +DY = FiniteDiff(ig, direction = 0, bnd_cond = 'Neumann') +DX = FiniteDiff(ig, direction = 1, bnd_cond = 'Neumann') + +DXu = DX.direct(u) +DYu = DY.direct(u) + +DX_sparse = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +DY_sparse = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + +DXu_sparse = DX_sparse.direct(u) +DYu_sparse = DY_sparse.direct(u) + +#np.testing.assert_array_almost_equal(DYu.as_array(), DYu_sparse.as_array(), decimal=4) +#np.testing.assert_array_almost_equal(DXu.as_array(), DXu_sparse.as_array(), decimal=4) + +#%% Tau/ Sigma + +A1 = DY_sparse.matrix() +A2 = DX_sparse.matrix() +A3 = sparse.eye(np.prod(ig.shape)) + +sum_rows1 = np.array(np.sum(abs(A1), axis=1)) +sum_rows2 = np.array(np.sum(abs(A2), axis=1)) +sum_rows3 = np.array(np.sum(abs(A3), axis=1)) + +sum_cols1 = np.array(np.sum(abs(A1), axis=0)) +sum_cols2 = np.array(np.sum(abs(A2), axis=0)) +sum_cols3 = np.array(np.sum(abs(A2), axis=0)) + +# Check if Grad sum row/cols is OK +Grad = Gradient(ig) + +Sum_Block_row = Grad.sum_abs_row() +Sum_Block_col = Grad.sum_abs_col() + +tmp1 = BlockDataContainer( ImageData(np.reshape(sum_rows1, ig.shape, order='F')),\ + ImageData(np.reshape(sum_rows2, ig.shape, order='F'))) + + +#np.testing.assert_array_almost_equal(tmp1[0].as_array(), Sum_Block_row[0].as_array(), decimal=4) +#np.testing.assert_array_almost_equal(tmp1[1].as_array(), Sum_Block_row[1].as_array(), decimal=4) + +tmp2 = ImageData(np.reshape(sum_cols1 + sum_cols2, ig.shape, order='F')) + +#np.testing.assert_array_almost_equal(tmp2.as_array(), Sum_Block_col.as_array(), decimal=4) + + +#%% BlockOperator with Gradient, Identity + +Id = Identity(ig) +Block_GrId = BlockOperator(Grad, Id, shape=(2,1)) + + +Sum_Block_GrId_row = Block_GrId.sum_abs_row() + + + + + + + + + + + + + + diff --git a/Wrappers/Python/wip/Demos/check_precond.py b/Wrappers/Python/wip/Demos/check_precond.py new file mode 100644 index 0000000..8cf95fa --- /dev/null +++ b/Wrappers/Python/wip/Demos/check_precond.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple + +# Create phantom for TV 2D tomography +N = 75 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +# Create noisy data +np.random.seed(10) +n1 = np.random.random(sin.shape) +noisy_data = sin + ImageData(5*n1) + +#%% + +# Regularisation Parameter +alpha = 50 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + + + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = L2NormSquared(b=noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +diag_precon = True + +if diag_precon: + + def tau_sigma_precond(operator): + + tau = 1/operator.sum_abs_row() + sigma = 1/ operator.sum_abs_col() + + return tau, sigma + + tau, sigma = tau_sigma_precond(operator) + +else: + # Compute operator Norm + normK = operator.norm() + # Primal & dual stepsizes + sigma = 10 + tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 1000 +pdhg.update_objective_interval = 200 +pdhg.run(1000) + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff +import astra +import numpy + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(N*N) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + + # create matrix representation for Astra operator + + vol_geom = astra.create_vol_geom(N, N) + proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + + proj_id = astra.create_projector('line', proj_geom, vol_geom) + + matrix_id = astra.projector.matrix(proj_id) + + ProjMat = astra.matrix.get(matrix_id) + + fidelity = sum_squares( ProjMat * u - noisy_data.as_array().ravel()) + #constraints = [q>=fidelity] +# constraints = [u>=0] + + solver = MOSEK + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + +#%% + +plt.figure(figsize=(15,15)) +plt.subplot(2,2,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') + +plt.subplot(2,2,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') + +plt.subplot(2,2,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('PDHG Reconstruction') + +plt.subplot(2,2,4) +plt.imshow(np.reshape(u.value, ig.shape)) +plt.title('CVX Reconstruction') + +plt.show() + +#%% +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') +plt.plot(np.linspace(0,N,N), np.reshape(u.value, ig.shape)[int(N/2),:], label = 'CVX') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + + + + + + + -- cgit v1.2.3 From 7b7b23087660bfb1519b6dedf927ec8407808823 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 7 May 2019 23:00:02 +0100 Subject: add ccpi.data --- Wrappers/Python/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/setup.py b/Wrappers/Python/setup.py index 95c0dea..bceea46 100644 --- a/Wrappers/Python/setup.py +++ b/Wrappers/Python/setup.py @@ -31,7 +31,7 @@ if cil_version == '': setup( name="ccpi-framework", version=cil_version, - packages=['ccpi' , 'ccpi.io', + packages=['ccpi' , 'ccpi.io', 'ccpi.data', 'ccpi.framework', 'ccpi.optimisation', 'ccpi.optimisation.operators', 'ccpi.optimisation.algorithms', -- cgit v1.2.3 From df028207491f76c03519a22eb5211102b88889da Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 7 May 2019 23:00:29 +0100 Subject: add demos from wip dir --- Wrappers/Python/build/lib/ccpi/__init__.py | 18 + Wrappers/Python/build/lib/ccpi/contrib/__init__.py | 0 Wrappers/Python/build/lib/ccpi/data/__init__.py | 67 + .../Python/build/lib/ccpi/framework/__init__.py | 26 + .../Python/build/lib/ccpi/framework/framework.py | 1437 ++++++++++++++++++++ Wrappers/Python/build/lib/ccpi/io/__init__.py | 18 + Wrappers/Python/build/lib/ccpi/io/reader.py | 511 +++++++ .../Python/build/lib/ccpi/optimisation/__init__.py | 18 + .../lib/ccpi/optimisation/algorithms/Algorithm.py | 44 +- .../build/lib/ccpi/optimisation/algorithms/CGLS.py | 87 ++ .../build/lib/ccpi/optimisation/algorithms/FBPD.py | 86 ++ .../optimisation/algorithms/GradientDescent.py | 76 ++ .../build/lib/ccpi/optimisation/algorithms/PDHG.py | 188 +-- .../build/lib/ccpi/optimisation/algorithms/SIRT.py | 74 + .../lib/ccpi/optimisation/algorithms/__init__.py | 33 + .../Python/build/lib/ccpi/optimisation/algs.py | 307 +++++ .../lib/ccpi/optimisation/functions/Function.py | 69 + .../ccpi/optimisation/functions/IndicatorBox.py | 65 + .../ccpi/optimisation/functions/KullbackLeibler.py | 53 +- .../lib/ccpi/optimisation/functions/L1Norm.py | 234 ++++ .../ccpi/optimisation/functions/L2NormSquared.py | 27 +- .../lib/ccpi/optimisation/functions/Norm2Sq.py | 98 ++ .../lib/ccpi/optimisation/functions/__init__.py | 13 + .../optimisation/operators/BlockScaledOperator.py | 67 + .../operators/FiniteDifferenceOperator_old.py | 374 +++++ .../optimisation/operators/GradientOperator.py | 4 +- .../optimisation/operators/IdentityOperator.py | 79 ++ .../lib/ccpi/optimisation/operators/Operator.py | 30 + .../ccpi/optimisation/operators/ScaledOperator.py | 51 + .../optimisation/operators/ShrinkageOperator.py | 19 + .../optimisation/operators/SparseFiniteDiff.py | 144 ++ .../lib/ccpi/processors/CenterOfRotationFinder.py | 408 ++++++ .../Python/build/lib/ccpi/processors/Normalizer.py | 124 ++ .../Python/build/lib/ccpi/processors/__init__.py | 9 + Wrappers/Python/ccpi/data/__init__.py | 66 + Wrappers/Python/ccpi/data/boat.tiff | Bin 0 -> 262278 bytes Wrappers/Python/ccpi/data/camera.png | Bin 0 -> 114228 bytes Wrappers/Python/ccpi/data/peppers.tiff | Bin 0 -> 786572 bytes Wrappers/Python/ccpi/data/test_show_data.py | 30 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 193 bytes .../__pycache__/Algorithm.cpython-36.pyc | Bin 0 -> 6306 bytes .../algorithms/__pycache__/CGLS.cpython-36.pyc | Bin 0 -> 1800 bytes .../algorithms/__pycache__/FBPD.cpython-36.pyc | Bin 0 -> 1698 bytes .../algorithms/__pycache__/FISTA.cpython-36.pyc | Bin 0 -> 2808 bytes .../__pycache__/GradientDescent.cpython-36.pyc | Bin 0 -> 2193 bytes .../algorithms/__pycache__/PDHG.cpython-36.pyc | Bin 0 -> 3820 bytes .../algorithms/__pycache__/SIRT.cpython-36.pyc | Bin 0 -> 2009 bytes .../algorithms/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 514 bytes .../__pycache__/BlockFunction.cpython-36.pyc | Bin 0 -> 4382 bytes .../functions/__pycache__/Function.cpython-36.pyc | Bin 0 -> 2591 bytes .../FunctionOperatorComposition.cpython-36.pyc | Bin 0 -> 1982 bytes .../__pycache__/IndicatorBox.cpython-36.pyc | Bin 0 -> 1813 bytes .../__pycache__/KullbackLeibler.cpython-36.pyc | Bin 0 -> 3347 bytes .../functions/__pycache__/L1Norm.cpython-36.pyc | Bin 0 -> 3138 bytes .../__pycache__/L2NormSquared.cpython-36.pyc | Bin 0 -> 5327 bytes .../__pycache__/MixedL21Norm.cpython-36.pyc | Bin 0 -> 3888 bytes .../functions/__pycache__/Norm2Sq.cpython-36.pyc | Bin 0 -> 2013 bytes .../__pycache__/ScaledFunction.cpython-36.pyc | Bin 0 -> 3993 bytes .../__pycache__/ZeroFunction.cpython-36.pyc | Bin 0 -> 1547 bytes .../functions/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 604 bytes .../__pycache__/BlockOperator.cpython-36.pyc | Bin 0 -> 11183 bytes .../__pycache__/BlockScaledOperator.cpython-36.pyc | Bin 0 -> 3177 bytes .../FiniteDifferenceOperator.cpython-36.pyc | Bin 0 -> 8321 bytes .../__pycache__/GradientOperator.cpython-36.pyc | Bin 0 -> 5532 bytes .../__pycache__/IdentityOperator.cpython-36.pyc | Bin 0 -> 2266 bytes .../__pycache__/LinearOperator.cpython-36.pyc | Bin 0 -> 2403 bytes .../LinearOperatorMatrix.cpython-36.pyc | Bin 0 -> 2364 bytes .../operators/__pycache__/Operator.cpython-36.pyc | Bin 0 -> 1632 bytes .../__pycache__/ScaledOperator.cpython-36.pyc | Bin 0 -> 2629 bytes .../__pycache__/ShrinkageOperator.cpython-36.pyc | Bin 0 -> 807 bytes .../__pycache__/SparseFiniteDiff.cpython-36.pyc | Bin 0 -> 4126 bytes .../SymmetrizedGradientOperator.cpython-36.pyc | Bin 0 -> 4810 bytes .../__pycache__/ZeroOperator.cpython-36.pyc | Bin 0 -> 1580 bytes .../operators/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 844 bytes .../Python/demos/PDHG_TGV_Denoising_SaltPepper.py | 194 +++ Wrappers/Python/demos/PDHG_TGV_Tomo2D.py | 124 ++ .../Python/demos/PDHG_TV_Denoising_Gaussian.py | 211 +++ .../Python/demos/PDHG_TV_Denoising_Gaussian_3D.py | 155 +++ Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py | 207 +++ .../Python/demos/PDHG_TV_Denoising_SaltPepper.py | 198 +++ Wrappers/Python/demos/PDHG_TV_Tomo2D.py | 245 ++++ Wrappers/Python/demos/PDHG_TV_Tomo2D_time.py | 169 +++ Wrappers/Python/demos/PDHG_Tikhonov_Denoising.py | 176 +++ Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py | 108 ++ Wrappers/Python/environment.yml | 11 + Wrappers/Python/wip/.DS_Store | Bin 0 -> 12292 bytes .../wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py | 1 - Wrappers/Python/wip/Demos/.DS_Store | Bin 0 -> 6148 bytes .../Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py | 102 +- .../Python/wip/Demos/PDHG_TV_Denoising_Poisson.py | 4 +- 90 files changed, 6594 insertions(+), 265 deletions(-) create mode 100644 Wrappers/Python/build/lib/ccpi/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/contrib/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/data/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/framework/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/framework/framework.py create mode 100644 Wrappers/Python/build/lib/ccpi/io/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/io/reader.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/CGLS.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FBPD.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/GradientDescent.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/SIRT.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algs.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/Function.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/IndicatorBox.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/L1Norm.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/Norm2Sq.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/__init__.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockScaledOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/IdentityOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/Operator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/ScaledOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/ShrinkageOperator.py create mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/SparseFiniteDiff.py create mode 100644 Wrappers/Python/build/lib/ccpi/processors/CenterOfRotationFinder.py create mode 100644 Wrappers/Python/build/lib/ccpi/processors/Normalizer.py create mode 100644 Wrappers/Python/build/lib/ccpi/processors/__init__.py create mode 100644 Wrappers/Python/ccpi/data/__init__.py create mode 100644 Wrappers/Python/ccpi/data/boat.tiff create mode 100644 Wrappers/Python/ccpi/data/camera.png create mode 100644 Wrappers/Python/ccpi/data/peppers.tiff create mode 100644 Wrappers/Python/ccpi/data/test_show_data.py create mode 100644 Wrappers/Python/ccpi/optimisation/__pycache__/__init__.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/Algorithm.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/CGLS.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FBPD.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FISTA.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/GradientDescent.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/PDHG.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/SIRT.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/__init__.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/BlockFunction.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/Function.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/FunctionOperatorComposition.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/IndicatorBox.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/KullbackLeibler.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/L1Norm.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/L2NormSquared.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/MixedL21Norm.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/Norm2Sq.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/ScaledFunction.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/ZeroFunction.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/__init__.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockOperator.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockScaledOperator.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/FiniteDifferenceOperator.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/GradientOperator.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/IdentityOperator.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperator.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperatorMatrix.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/Operator.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/ScaledOperator.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/ShrinkageOperator.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/SparseFiniteDiff.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/SymmetrizedGradientOperator.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/ZeroOperator.cpython-36.pyc create mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/__init__.cpython-36.pyc create mode 100644 Wrappers/Python/demos/PDHG_TGV_Denoising_SaltPepper.py create mode 100644 Wrappers/Python/demos/PDHG_TGV_Tomo2D.py create mode 100644 Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian.py create mode 100644 Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian_3D.py create mode 100644 Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py create mode 100644 Wrappers/Python/demos/PDHG_TV_Denoising_SaltPepper.py create mode 100644 Wrappers/Python/demos/PDHG_TV_Tomo2D.py create mode 100644 Wrappers/Python/demos/PDHG_TV_Tomo2D_time.py create mode 100644 Wrappers/Python/demos/PDHG_Tikhonov_Denoising.py create mode 100644 Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py create mode 100644 Wrappers/Python/environment.yml create mode 100644 Wrappers/Python/wip/.DS_Store create mode 100644 Wrappers/Python/wip/Demos/.DS_Store diff --git a/Wrappers/Python/build/lib/ccpi/__init__.py b/Wrappers/Python/build/lib/ccpi/__init__.py new file mode 100644 index 0000000..cf2d93d --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/contrib/__init__.py b/Wrappers/Python/build/lib/ccpi/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Wrappers/Python/build/lib/ccpi/data/__init__.py b/Wrappers/Python/build/lib/ccpi/data/__init__.py new file mode 100644 index 0000000..af10536 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/data/__init__.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from ccpi.framework import ImageData +import numpy +from PIL import Image +import os +import os.path + +data_dir = os.path.abspath(os.path.dirname(__file__)) + + +def camera(**kwargs): + + tmp = Image.open(os.path.join(data_dir, 'camera.png')) + + size = kwargs.get('size',(512, 512)) + + data = numpy.array(tmp.resize(size)) + + data = data/data.max() + + return ImageData(data) + + +def boat(**kwargs): + + tmp = Image.open(os.path.join(data_dir, 'boat.tiff')) + + size = kwargs.get('size',(512, 512)) + + data = numpy.array(tmp.resize(size)) + + data = data/data.max() + + return ImageData(data) + + +def peppers(**kwargs): + + tmp = Image.open(os.path.join(data_dir, 'peppers.tiff')) + + size = kwargs.get('size',(512, 512)) + + data = numpy.array(tmp.resize(size)) + + data = data/data.max() + + return ImageData(data) + diff --git a/Wrappers/Python/build/lib/ccpi/framework/__init__.py b/Wrappers/Python/build/lib/ccpi/framework/__init__.py new file mode 100644 index 0000000..229edb5 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/framework/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Mar 5 16:00:18 2019 + +@author: ofn77899 +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy +import sys +from datetime import timedelta, datetime +import warnings +from functools import reduce + + +from .framework import DataContainer +from .framework import ImageData, AcquisitionData +from .framework import ImageGeometry, AcquisitionGeometry +from .framework import find_key, message +from .framework import DataProcessor +from .framework import AX, PixelByPixelDataProcessor, CastDataContainer +from .BlockDataContainer import BlockDataContainer +from .BlockGeometry import BlockGeometry diff --git a/Wrappers/Python/build/lib/ccpi/framework/framework.py b/Wrappers/Python/build/lib/ccpi/framework/framework.py new file mode 100644 index 0000000..dbe7d0a --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/framework/framework.py @@ -0,0 +1,1437 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy +import sys +from datetime import timedelta, datetime +import warnings +from functools import reduce +from numbers import Number + + +def find_key(dic, val): + """return the key of dictionary dic given the value""" + return [k for k, v in dic.items() if v == val][0] + +def message(cls, msg, *args): + msg = "{0}: " + msg + for i in range(len(args)): + msg += " {%d}" %(i+1) + args = list(args) + args.insert(0, cls.__name__ ) + + return msg.format(*args ) + + +class ImageGeometry(object): + RANDOM = 'random' + RANDOM_INT = 'random_int' + CHANNEL = 'channel' + ANGLE = 'angle' + VERTICAL = 'vertical' + HORIZONTAL_X = 'horizontal_x' + HORIZONTAL_Y = 'horizontal_y' + + def __init__(self, + voxel_num_x=0, + voxel_num_y=0, + voxel_num_z=0, + voxel_size_x=1, + voxel_size_y=1, + voxel_size_z=1, + center_x=0, + center_y=0, + center_z=0, + channels=1): + + self.voxel_num_x = voxel_num_x + self.voxel_num_y = voxel_num_y + self.voxel_num_z = voxel_num_z + self.voxel_size_x = voxel_size_x + self.voxel_size_y = voxel_size_y + self.voxel_size_z = voxel_size_z + self.center_x = center_x + self.center_y = center_y + self.center_z = center_z + self.channels = channels + + # this is some code repetition + if self.channels > 1: + if self.voxel_num_z>1: + self.length = 4 + self.shape = (self.channels, self.voxel_num_z, self.voxel_num_y, self.voxel_num_x) + dim_labels = [ImageGeometry.CHANNEL, ImageGeometry.VERTICAL, + ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] + else: + self.length = 3 + self.shape = (self.channels, self.voxel_num_y, self.voxel_num_x) + dim_labels = [ImageGeometry.CHANNEL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] + else: + if self.voxel_num_z>1: + self.length = 3 + self.shape = (self.voxel_num_z, self.voxel_num_y, self.voxel_num_x) + dim_labels = [ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + else: + self.length = 2 + self.shape = (self.voxel_num_y, self.voxel_num_x) + dim_labels = [ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] + + self.dimension_labels = dim_labels + + def get_min_x(self): + return self.center_x - 0.5*self.voxel_num_x*self.voxel_size_x + + def get_max_x(self): + return self.center_x + 0.5*self.voxel_num_x*self.voxel_size_x + + def get_min_y(self): + return self.center_y - 0.5*self.voxel_num_y*self.voxel_size_y + + def get_max_y(self): + return self.center_y + 0.5*self.voxel_num_y*self.voxel_size_y + + def get_min_z(self): + if not self.voxel_num_z == 0: + return self.center_z - 0.5*self.voxel_num_z*self.voxel_size_z + else: + return 0 + + def get_max_z(self): + if not self.voxel_num_z == 0: + return self.center_z + 0.5*self.voxel_num_z*self.voxel_size_z + else: + return 0 + + def clone(self): + '''returns a copy of ImageGeometry''' + return ImageGeometry( + self.voxel_num_x, + self.voxel_num_y, + self.voxel_num_z, + self.voxel_size_x, + self.voxel_size_y, + self.voxel_size_z, + self.center_x, + self.center_y, + self.center_z, + self.channels) + def __str__ (self): + repres = "" + repres += "Number of channels: {0}\n".format(self.channels) + repres += "voxel_num : x{0},y{1},z{2}\n".format(self.voxel_num_x, self.voxel_num_y, self.voxel_num_z) + repres += "voxel_size : x{0},y{1},z{2}\n".format(self.voxel_size_x, self.voxel_size_y, self.voxel_size_z) + repres += "center : x{0},y{1},z{2}\n".format(self.center_x, self.center_y, self.center_z) + return repres + def allocate(self, value=0, dimension_labels=None, **kwargs): + '''allocates an ImageData according to the size expressed in the instance''' + out = ImageData(geometry=self) + if isinstance(value, Number): + if value != 0: + out += value + else: + if value == ImageGeometry.RANDOM: + seed = kwargs.get('seed', None) + if seed is not None: + numpy.random.seed(seed) + out.fill(numpy.random.random_sample(self.shape)) + elif value == ImageGeometry.RANDOM_INT: + seed = kwargs.get('seed', None) + if seed is not None: + numpy.random.seed(seed) + max_value = kwargs.get('max_value', 100) + out.fill(numpy.random.randint(max_value,size=self.shape)) + else: + raise ValueError('Value {} unknown'.format(value)) + if dimension_labels is not None: + if dimension_labels != self.dimension_labels: + return out.subset(dimensions=dimension_labels) + return out + # The following methods return 2 members of the class, therefore I + # don't think we need to implement them. + # Additionally using __len__ is confusing as one would think this is + # an iterable. + #def __len__(self): + # '''returns the length of the geometry''' + # return self.length + #def shape(self): + # '''Returns the shape of the array of the ImageData it describes''' + # return self.shape + +class AcquisitionGeometry(object): + RANDOM = 'random' + RANDOM_INT = 'random_int' + ANGLE_UNIT = 'angle_unit' + DEGREE = 'degree' + RADIAN = 'radian' + CHANNEL = 'channel' + ANGLE = 'angle' + VERTICAL = 'vertical' + HORIZONTAL = 'horizontal' + def __init__(self, + geom_type, + dimension, + angles, + pixel_num_h=0, + pixel_size_h=1, + pixel_num_v=0, + pixel_size_v=1, + dist_source_center=None, + dist_center_detector=None, + channels=1, + **kwargs + ): + """ + General inputs for standard type projection geometries + detectorDomain or detectorpixelSize: + If 2D + If scalar: Width of detector or single detector pixel + If 2-vec: Error + If 3D + If scalar: Width in both dimensions + If 2-vec: Vertical then horizontal size + grid + If 2D + If scalar: number of detectors + If 2-vec: error + If 3D + If scalar: Square grid that size + If 2-vec vertical then horizontal size + cone or parallel + 2D or 3D + parallel_parameters: ? + cone_parameters: + source_to_center_dist (if parallel: NaN) + center_to_detector_dist (if parallel: NaN) + standard or nonstandard (vec) geometry + angles + angles_format radians or degrees + """ + self.geom_type = geom_type # 'parallel' or 'cone' + self.dimension = dimension # 2D or 3D + self.angles = angles + num_of_angles = len (angles) + + self.dist_source_center = dist_source_center + self.dist_center_detector = dist_center_detector + + self.pixel_num_h = pixel_num_h + self.pixel_size_h = pixel_size_h + self.pixel_num_v = pixel_num_v + self.pixel_size_v = pixel_size_v + + self.channels = channels + self.angle_unit=kwargs.get(AcquisitionGeometry.ANGLE_UNIT, + AcquisitionGeometry.DEGREE) + if channels > 1: + if pixel_num_v > 1: + shape = (channels, num_of_angles , pixel_num_v, pixel_num_h) + dim_labels = [AcquisitionGeometry.CHANNEL , + AcquisitionGeometry.ANGLE , AcquisitionGeometry.VERTICAL , + AcquisitionGeometry.HORIZONTAL] + else: + shape = (channels , num_of_angles, pixel_num_h) + dim_labels = [AcquisitionGeometry.CHANNEL , + AcquisitionGeometry.ANGLE, AcquisitionGeometry.HORIZONTAL] + else: + if pixel_num_v > 1: + shape = (num_of_angles, pixel_num_v, pixel_num_h) + dim_labels = [AcquisitionGeometry.ANGLE , AcquisitionGeometry.VERTICAL , + AcquisitionGeometry.HORIZONTAL] + else: + shape = (num_of_angles, pixel_num_h) + dim_labels = [AcquisitionGeometry.ANGLE, AcquisitionGeometry.HORIZONTAL] + self.shape = shape + + self.dimension_labels = dim_labels + + def clone(self): + '''returns a copy of the AcquisitionGeometry''' + return AcquisitionGeometry(self.geom_type, + self.dimension, + self.angles, + self.pixel_num_h, + self.pixel_size_h, + self.pixel_num_v, + self.pixel_size_v, + self.dist_source_center, + self.dist_center_detector, + self.channels) + + def __str__ (self): + repres = "" + repres += "Number of dimensions: {0}\n".format(self.dimension) + repres += "angles: {0}\n".format(self.angles) + repres += "voxel_num : h{0},v{1}\n".format(self.pixel_num_h, self.pixel_num_v) + repres += "voxel size: h{0},v{1}\n".format(self.pixel_size_h, self.pixel_size_v) + repres += "geometry type: {0}\n".format(self.geom_type) + repres += "distance source-detector: {0}\n".format(self.dist_source_center) + repres += "distance center-detector: {0}\n".format(self.dist_source_center) + repres += "number of channels: {0}\n".format(self.channels) + return repres + def allocate(self, value=0, dimension_labels=None): + '''allocates an AcquisitionData according to the size expressed in the instance''' + out = AcquisitionData(geometry=self) + if isinstance(value, Number): + if value != 0: + out += value + else: + if value == AcquisitionData.RANDOM: + seed = kwargs.get('seed', None) + if seed is not None: + numpy.random.seed(seed) + out.fill(numpy.random.random_sample(self.shape)) + elif value == AcquisitionData.RANDOM_INT: + seed = kwargs.get('seed', None) + if seed is not None: + numpy.random.seed(seed) + max_value = kwargs.get('max_value', 100) + out.fill(numpy.random.randint(max_value,size=self.shape)) + else: + raise ValueError('Value {} unknown'.format(value)) + if dimension_labels is not None: + if dimension_labels != self.dimension_labels: + return out.subset(dimensions=dimension_labels) + return out + +class DataContainer(object): + '''Generic class to hold data + + Data is currently held in a numpy arrays''' + + __container_priority__ = 1 + def __init__ (self, array, deep_copy=True, dimension_labels=None, + **kwargs): + '''Holds the data''' + + self.shape = numpy.shape(array) + self.number_of_dimensions = len (self.shape) + self.dimension_labels = {} + self.geometry = None # Only relevant for AcquisitionData and ImageData + + if dimension_labels is not None and \ + len (dimension_labels) == self.number_of_dimensions: + for i in range(self.number_of_dimensions): + self.dimension_labels[i] = dimension_labels[i] + else: + for i in range(self.number_of_dimensions): + self.dimension_labels[i] = 'dimension_{0:02}'.format(i) + + if type(array) == numpy.ndarray: + if deep_copy: + self.array = array.copy() + else: + self.array = array + else: + raise TypeError('Array must be NumpyArray, passed {0}'\ + .format(type(array))) + + # finally copy the geometry + if 'geometry' in kwargs.keys(): + self.geometry = kwargs['geometry'] + else: + # assume it is parallel beam + pass + + def get_dimension_size(self, dimension_label): + if dimension_label in self.dimension_labels.values(): + acq_size = -1 + for k,v in self.dimension_labels.items(): + if v == dimension_label: + acq_size = self.shape[k] + return acq_size + else: + raise ValueError('Unknown dimension {0}. Should be one of'.format(dimension_label, + self.dimension_labels)) + def get_dimension_axis(self, dimension_label): + if dimension_label in self.dimension_labels.values(): + for k,v in self.dimension_labels.items(): + if v == dimension_label: + return k + else: + raise ValueError('Unknown dimension {0}. Should be one of'.format(dimension_label, + self.dimension_labels.values())) + + + def as_array(self, dimensions=None): + '''Returns the DataContainer as Numpy Array + + Returns the pointer to the array if dimensions is not set. + If dimensions is set, it first creates a new DataContainer with the subset + and then it returns the pointer to the array''' + if dimensions is not None: + return self.subset(dimensions).as_array() + return self.array + + + def subset(self, dimensions=None, **kw): + '''Creates a DataContainer containing a subset of self according to the + labels in dimensions''' + if dimensions is None: + if kw == {}: + return self.array.copy() + else: + reduced_dims = [v for k,v in self.dimension_labels.items()] + for dim_l, dim_v in kw.items(): + for k,v in self.dimension_labels.items(): + if v == dim_l: + reduced_dims.pop(k) + return self.subset(dimensions=reduced_dims, **kw) + else: + # check that all the requested dimensions are in the array + # this is done by checking the dimension_labels + proceed = True + unknown_key = '' + # axis_order contains the order of the axis that the user wants + # in the output DataContainer + axis_order = [] + if type(dimensions) == list: + for dl in dimensions: + if dl not in self.dimension_labels.values(): + proceed = False + unknown_key = dl + break + else: + axis_order.append(find_key(self.dimension_labels, dl)) + if not proceed: + raise KeyError('Subset error: Unknown key specified {0}'.format(dl)) + + # slice away the unwanted data from the array + unwanted_dimensions = self.dimension_labels.copy() + left_dimensions = [] + for ax in sorted(axis_order): + this_dimension = unwanted_dimensions.pop(ax) + left_dimensions.append(this_dimension) + #print ("unwanted_dimensions {0}".format(unwanted_dimensions)) + #print ("left_dimensions {0}".format(left_dimensions)) + #new_shape = [self.shape[ax] for ax in axis_order] + #print ("new_shape {0}".format(new_shape)) + command = "self.array[" + for i in range(self.number_of_dimensions): + if self.dimension_labels[i] in unwanted_dimensions.values(): + value = 0 + for k,v in kw.items(): + if k == self.dimension_labels[i]: + value = v + + command = command + str(value) + else: + command = command + ":" + if i < self.number_of_dimensions -1: + command = command + ',' + command = command + ']' + + cleaned = eval(command) + # cleaned has collapsed dimensions in the same order of + # self.array, but we want it in the order stated in the + # "dimensions". + # create axes order for numpy.transpose + axes = [] + for key in dimensions: + #print ("key {0}".format( key)) + for i in range(len( left_dimensions )): + ld = left_dimensions[i] + #print ("ld {0}".format( ld)) + if ld == key: + axes.append(i) + #print ("axes {0}".format(axes)) + + cleaned = numpy.transpose(cleaned, axes).copy() + + return type(self)(cleaned , True, dimensions) + + def fill(self, array, **dimension): + '''fills the internal numpy array with the one provided''' + if dimension == {}: + if issubclass(type(array), DataContainer) or\ + issubclass(type(array), numpy.ndarray): + if array.shape != self.shape: + raise ValueError('Cannot fill with the provided array.' + \ + 'Expecting {0} got {1}'.format( + self.shape,array.shape)) + if issubclass(type(array), DataContainer): + numpy.copyto(self.array, array.array) + else: + #self.array[:] = array + numpy.copyto(self.array, array) + else: + + command = 'self.array[' + i = 0 + for k,v in self.dimension_labels.items(): + for dim_label, dim_value in dimension.items(): + if dim_label == v: + command = command + str(dim_value) + else: + command = command + ":" + if i < self.number_of_dimensions -1: + command = command + ',' + i += 1 + command = command + "] = array[:]" + exec(command) + + + def check_dimensions(self, other): + return self.shape == other.shape + + ## algebra + + def __add__(self, other): + return self.add(other) + def __mul__(self, other): + return self.multiply(other) + def __sub__(self, other): + return self.subtract(other) + def __div__(self, other): + return self.divide(other) + def __truediv__(self, other): + return self.divide(other) + def __pow__(self, other): + return self.power(other) + + + + # reverse operand + def __radd__(self, other): + return self + other + # __radd__ + + def __rsub__(self, other): + return (-1 * self) + other + # __rsub__ + + def __rmul__(self, other): + return self * other + # __rmul__ + + def __rdiv__(self, other): + print ("call __rdiv__") + return pow(self / other, -1) + # __rdiv__ + def __rtruediv__(self, other): + return self.__rdiv__(other) + + def __rpow__(self, other): + if isinstance(other, (int, float)) : + fother = numpy.ones(numpy.shape(self.array)) * other + return type(self)(fother ** self.array , + dimension_labels=self.dimension_labels, + geometry=self.geometry) + elif issubclass(type(other), DataContainer): + if self.check_dimensions(other): + return type(self)(other.as_array() ** self.array , + dimension_labels=self.dimension_labels, + geometry=self.geometry) + else: + raise ValueError('Dimensions do not match') + # __rpow__ + + # in-place arithmetic operators: + # (+=, -=, *=, /= , //=, + # must return self + + def __iadd__(self, other): + kw = {'out':self} + return self.add(other, **kw) + + def __imul__(self, other): + kw = {'out':self} + return self.multiply(other, **kw) + + def __isub__(self, other): + kw = {'out':self} + return self.subtract(other, **kw) + + def __idiv__(self, other): + kw = {'out':self} + return self.divide(other, **kw) + + def __itruediv__(self, other): + kw = {'out':self} + return self.divide(other, **kw) + + + + def __str__ (self, representation=False): + repres = "" + repres += "Number of dimensions: {0}\n".format(self.number_of_dimensions) + repres += "Shape: {0}\n".format(self.shape) + repres += "Axis labels: {0}\n".format(self.dimension_labels) + if representation: + repres += "Representation: \n{0}\n".format(self.array) + return repres + + def clone(self): + '''returns a copy of itself''' + + return type(self)(self.array, + dimension_labels=self.dimension_labels, + deep_copy=True, + geometry=self.geometry ) + + def get_data_axes_order(self,new_order=None): + '''returns the axes label of self as a list + + if new_order is None returns the labels of the axes as a sorted-by-key list + if new_order is a list of length number_of_dimensions, returns a list + with the indices of the axes in new_order with respect to those in + self.dimension_labels: i.e. + self.dimension_labels = {0:'horizontal',1:'vertical'} + new_order = ['vertical','horizontal'] + returns [1,0] + ''' + if new_order is None: + + axes_order = [i for i in range(len(self.shape))] + for k,v in self.dimension_labels.items(): + axes_order[k] = v + return axes_order + else: + if len(new_order) == self.number_of_dimensions: + axes_order = [i for i in range(self.number_of_dimensions)] + + for i in range(len(self.shape)): + found = False + for k,v in self.dimension_labels.items(): + if new_order[i] == v: + axes_order[i] = k + found = True + if not found: + raise ValueError('Axis label {0} not found.'.format(new_order[i])) + return axes_order + else: + raise ValueError('Expecting {0} axes, got {2}'\ + .format(len(self.shape),len(new_order))) + + + def copy(self): + '''alias of clone''' + return self.clone() + + ## binary operations + + def pixel_wise_binary(self, pwop, x2, *args, **kwargs): + out = kwargs.get('out', None) + if out is None: + if isinstance(x2, (int, float, complex)): + out = pwop(self.as_array() , x2 , *args, **kwargs ) + elif isinstance(x2, (numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.int64,\ + numpy.float, numpy.float16, numpy.float32, numpy.float64, \ + numpy.complex)): + out = pwop(self.as_array() , x2 , *args, **kwargs ) + elif issubclass(type(x2) , DataContainer): + out = pwop(self.as_array() , x2.as_array() , *args, **kwargs ) + return type(self)(out, + deep_copy=False, + dimension_labels=self.dimension_labels, + geometry=self.geometry) + + + elif issubclass(type(out), DataContainer) and issubclass(type(x2), DataContainer): + if self.check_dimensions(out) and self.check_dimensions(x2): + kwargs['out'] = out.as_array() + pwop(self.as_array(), x2.as_array(), *args, **kwargs ) + #return type(self)(out.as_array(), + # deep_copy=False, + # dimension_labels=self.dimension_labels, + # geometry=self.geometry) + return out + else: + raise ValueError(message(type(self),"Wrong size for data memory: ", out.shape,self.shape)) + elif issubclass(type(out), DataContainer) and isinstance(x2, (int,float,complex)): + if self.check_dimensions(out): + kwargs['out']=out.as_array() + pwop(self.as_array(), x2, *args, **kwargs ) + return out + else: + raise ValueError(message(type(self),"Wrong size for data memory: ", out.shape,self.shape)) + elif issubclass(type(out), numpy.ndarray): + if self.array.shape == out.shape and self.array.dtype == out.dtype: + kwargs['out'] = out + pwop(self.as_array(), x2, *args, **kwargs) + #return type(self)(out, + # deep_copy=False, + # dimension_labels=self.dimension_labels, + # geometry=self.geometry) + else: + raise ValueError (message(type(self), "incompatible class:" , pwop.__name__, type(out))) + + def add(self, other, *args, **kwargs): + if hasattr(other, '__container_priority__') and \ + self.__class__.__container_priority__ < other.__class__.__container_priority__: + return other.add(self, *args, **kwargs) + return self.pixel_wise_binary(numpy.add, other, *args, **kwargs) + + def subtract(self, other, *args, **kwargs): + if hasattr(other, '__container_priority__') and \ + self.__class__.__container_priority__ < other.__class__.__container_priority__: + return other.subtract(self, *args, **kwargs) + return self.pixel_wise_binary(numpy.subtract, other, *args, **kwargs) + + def multiply(self, other, *args, **kwargs): + if hasattr(other, '__container_priority__') and \ + self.__class__.__container_priority__ < other.__class__.__container_priority__: + return other.multiply(self, *args, **kwargs) + return self.pixel_wise_binary(numpy.multiply, other, *args, **kwargs) + + def divide(self, other, *args, **kwargs): + if hasattr(other, '__container_priority__') and \ + self.__class__.__container_priority__ < other.__class__.__container_priority__: + return other.divide(self, *args, **kwargs) + return self.pixel_wise_binary(numpy.divide, other, *args, **kwargs) + + def power(self, other, *args, **kwargs): + return self.pixel_wise_binary(numpy.power, other, *args, **kwargs) + + def maximum(self, x2, *args, **kwargs): + return self.pixel_wise_binary(numpy.maximum, x2, *args, **kwargs) + + def minimum(self,x2, out=None, *args, **kwargs): + return self.pixel_wise_binary(numpy.minimum, x2=x2, out=out, *args, **kwargs) + + + ## unary operations + def pixel_wise_unary(self, pwop, *args, **kwargs): + out = kwargs.get('out', None) + if out is None: + out = pwop(self.as_array() , *args, **kwargs ) + return type(self)(out, + deep_copy=False, + dimension_labels=self.dimension_labels, + geometry=self.geometry) + elif issubclass(type(out), DataContainer): + if self.check_dimensions(out): + kwargs['out'] = out.as_array() + pwop(self.as_array(), *args, **kwargs ) + else: + raise ValueError(message(type(self),"Wrong size for data memory: ", out.shape,self.shape)) + elif issubclass(type(out), numpy.ndarray): + if self.array.shape == out.shape and self.array.dtype == out.dtype: + kwargs['out'] = out + pwop(self.as_array(), *args, **kwargs) + else: + raise ValueError (message(type(self), "incompatible class:" , pwop.__name__, type(out))) + + def abs(self, *args, **kwargs): + return self.pixel_wise_unary(numpy.abs, *args, **kwargs) + + def sign(self, *args, **kwargs): + return self.pixel_wise_unary(numpy.sign, *args, **kwargs) + + def sqrt(self, *args, **kwargs): + return self.pixel_wise_unary(numpy.sqrt, *args, **kwargs) + + def conjugate(self, *args, **kwargs): + return self.pixel_wise_unary(numpy.conjugate, *args, **kwargs) + #def __abs__(self): + # operation = FM.OPERATION.ABS + # return self.callFieldMath(operation, None, self.mask, self.maskOnValue) + # __abs__ + + ## reductions + def sum(self, *args, **kwargs): + return self.as_array().sum(*args, **kwargs) + def squared_norm(self): + '''return the squared euclidean norm of the DataContainer viewed as a vector''' + #shape = self.shape + #size = reduce(lambda x,y:x*y, shape, 1) + #y = numpy.reshape(self.as_array(), (size, )) + return self.dot(self.conjugate()) + #return self.dot(self) + def norm(self): + '''return the euclidean norm of the DataContainer viewed as a vector''' + return numpy.sqrt(self.squared_norm()) + + + def dot(self, other, *args, **kwargs): + '''return the inner product of 2 DataContainers viewed as vectors''' + method = kwargs.get('method', 'reduce') + + if method not in ['numpy','reduce']: + raise ValueError('dot: specified method not valid. Expecting numpy or reduce got {} '.format( + method)) + + if self.shape == other.shape: + # return (self*other).sum() + if method == 'numpy': + return numpy.dot(self.as_array().ravel(), other.as_array()) + elif method == 'reduce': + # see https://github.com/vais-ral/CCPi-Framework/pull/273 + # notice that Python seems to be smart enough to use + # the appropriate type to hold the result of the reduction + sf = reduce(lambda x,y: x + y[0]*y[1], + zip(self.as_array().ravel(), + other.as_array().ravel()), + 0) + return sf + else: + raise ValueError('Shapes are not aligned: {} != {}'.format(self.shape, other.shape)) + + + + + +class ImageData(DataContainer): + '''DataContainer for holding 2D or 3D DataContainer''' + __container_priority__ = 1 + + + def __init__(self, + array = None, + deep_copy=False, + dimension_labels=None, + **kwargs): + + self.geometry = kwargs.get('geometry', None) + if array is None: + if self.geometry is not None: + shape, dimension_labels = self.get_shape_labels(self.geometry) + + array = numpy.zeros( shape , dtype=numpy.float32) + super(ImageData, self).__init__(array, deep_copy, + dimension_labels, **kwargs) + + else: + raise ValueError('Please pass either a DataContainer, ' +\ + 'a numpy array or a geometry') + else: + if self.geometry is not None: + shape, labels = self.get_shape_labels(self.geometry, dimension_labels) + if array.shape != shape: + raise ValueError('Shape mismatch {} {}'.format(shape, array.shape)) + + if issubclass(type(array) , DataContainer): + # if the array is a DataContainer get the info from there + if not ( array.number_of_dimensions == 2 or \ + array.number_of_dimensions == 3 or \ + array.number_of_dimensions == 4): + raise ValueError('Number of dimensions are not 2 or 3 or 4: {0}'\ + .format(array.number_of_dimensions)) + + #DataContainer.__init__(self, array.as_array(), deep_copy, + # array.dimension_labels, **kwargs) + super(ImageData, self).__init__(array.as_array(), deep_copy, + array.dimension_labels, **kwargs) + elif issubclass(type(array) , numpy.ndarray): + if not ( array.ndim == 2 or array.ndim == 3 or array.ndim == 4 ): + raise ValueError( + 'Number of dimensions are not 2 or 3 or 4 : {0}'\ + .format(array.ndim)) + + if dimension_labels is None: + if array.ndim == 4: + dimension_labels = [ImageGeometry.CHANNEL, + ImageGeometry.VERTICAL, + ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + elif array.ndim == 3: + dimension_labels = [ImageGeometry.VERTICAL, + ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + else: + dimension_labels = [ ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + + #DataContainer.__init__(self, array, deep_copy, dimension_labels, **kwargs) + super(ImageData, self).__init__(array, deep_copy, + dimension_labels, **kwargs) + + # load metadata from kwargs if present + for key, value in kwargs.items(): + if (type(value) == list or type(value) == tuple) and \ + ( len (value) == 3 and len (value) == 2) : + if key == 'origin' : + self.origin = value + if key == 'spacing' : + self.spacing = value + + def subset(self, dimensions=None, **kw): + # FIXME: this is clearly not rigth + # it should be something like + # out = DataContainer.subset(self, dimensions, **kw) + # followed by regeneration of the proper geometry. + out = super(ImageData, self).subset(dimensions, **kw) + #out.geometry = self.recalculate_geometry(dimensions , **kw) + out.geometry = self.geometry + return out + + def get_shape_labels(self, geometry, dimension_labels=None): + channels = geometry.channels + horiz_x = geometry.voxel_num_x + horiz_y = geometry.voxel_num_y + vert = 1 if geometry.voxel_num_z is None\ + else geometry.voxel_num_z # this should be 1 for 2D + if dimension_labels is None: + if channels > 1: + if vert > 1: + shape = (channels, vert, horiz_y, horiz_x) + dim_labels = [ImageGeometry.CHANNEL, + ImageGeometry.VERTICAL, + ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + else: + shape = (channels , horiz_y, horiz_x) + dim_labels = [ImageGeometry.CHANNEL, + ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + else: + if vert > 1: + shape = (vert, horiz_y, horiz_x) + dim_labels = [ImageGeometry.VERTICAL, + ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + else: + shape = (horiz_y, horiz_x) + dim_labels = [ImageGeometry.HORIZONTAL_Y, + ImageGeometry.HORIZONTAL_X] + dimension_labels = dim_labels + else: + shape = [] + for i in range(len(dimension_labels)): + dim = dimension_labels[i] + if dim == ImageGeometry.CHANNEL: + shape.append(channels) + elif dim == ImageGeometry.HORIZONTAL_Y: + shape.append(horiz_y) + elif dim == ImageGeometry.VERTICAL: + shape.append(vert) + elif dim == ImageGeometry.HORIZONTAL_X: + shape.append(horiz_x) + if len(shape) != len(dimension_labels): + raise ValueError('Missing {0} axes {1} shape {2}'.format( + len(dimension_labels) - len(shape), dimension_labels, shape)) + shape = tuple(shape) + + return (shape, dimension_labels) + + +class AcquisitionData(DataContainer): + '''DataContainer for holding 2D or 3D sinogram''' + __container_priority__ = 1 + + + def __init__(self, + array = None, + deep_copy=True, + dimension_labels=None, + **kwargs): + self.geometry = kwargs.get('geometry', None) + if array is None: + if 'geometry' in kwargs.keys(): + geometry = kwargs['geometry'] + self.geometry = geometry + + shape, dimension_labels = self.get_shape_labels(geometry, dimension_labels) + + + array = numpy.zeros( shape , dtype=numpy.float32) + super(AcquisitionData, self).__init__(array, deep_copy, + dimension_labels, **kwargs) + else: + if self.geometry is not None: + shape, labels = self.get_shape_labels(self.geometry, dimension_labels) + if array.shape != shape: + raise ValueError('Shape mismatch {} {}'.format(shape, array.shape)) + + if issubclass(type(array) ,DataContainer): + # if the array is a DataContainer get the info from there + if not ( array.number_of_dimensions == 2 or \ + array.number_of_dimensions == 3 or \ + array.number_of_dimensions == 4): + raise ValueError('Number of dimensions are not 2 or 3 or 4: {0}'\ + .format(array.number_of_dimensions)) + + #DataContainer.__init__(self, array.as_array(), deep_copy, + # array.dimension_labels, **kwargs) + super(AcquisitionData, self).__init__(array.as_array(), deep_copy, + array.dimension_labels, **kwargs) + elif issubclass(type(array) ,numpy.ndarray): + if not ( array.ndim == 2 or array.ndim == 3 or array.ndim == 4 ): + raise ValueError( + 'Number of dimensions are not 2 or 3 or 4 : {0}'\ + .format(array.ndim)) + + if dimension_labels is None: + if array.ndim == 4: + dimension_labels = [AcquisitionGeometry.CHANNEL, + AcquisitionGeometry.ANGLE, + AcquisitionGeometry.VERTICAL, + AcquisitionGeometry.HORIZONTAL] + elif array.ndim == 3: + dimension_labels = [AcquisitionGeometry.ANGLE, + AcquisitionGeometry.VERTICAL, + AcquisitionGeometry.HORIZONTAL] + else: + dimension_labels = [AcquisitionGeometry.ANGLE, + AcquisitionGeometry.HORIZONTAL] + + super(AcquisitionData, self).__init__(array, deep_copy, + dimension_labels, **kwargs) + + def get_shape_labels(self, geometry, dimension_labels=None): + channels = geometry.channels + horiz = geometry.pixel_num_h + vert = geometry.pixel_num_v + angles = geometry.angles + num_of_angles = numpy.shape(angles)[0] + + if dimension_labels is None: + if channels > 1: + if vert > 1: + shape = (channels, num_of_angles , vert, horiz) + dim_labels = [AcquisitionGeometry.CHANNEL, + AcquisitionGeometry.ANGLE, + AcquisitionGeometry.VERTICAL, + AcquisitionGeometry.HORIZONTAL] + else: + shape = (channels , num_of_angles, horiz) + dim_labels = [AcquisitionGeometry.CHANNEL, + AcquisitionGeometry.ANGLE, + AcquisitionGeometry.HORIZONTAL] + else: + if vert > 1: + shape = (num_of_angles, vert, horiz) + dim_labels = [AcquisitionGeometry.ANGLE, + AcquisitionGeometry.VERTICAL, + AcquisitionGeometry.HORIZONTAL + ] + else: + shape = (num_of_angles, horiz) + dim_labels = [AcquisitionGeometry.ANGLE, + AcquisitionGeometry.HORIZONTAL + ] + + dimension_labels = dim_labels + else: + shape = [] + for i in range(len(dimension_labels)): + dim = dimension_labels[i] + + if dim == AcquisitionGeometry.CHANNEL: + shape.append(channels) + elif dim == AcquisitionGeometry.ANGLE: + shape.append(num_of_angles) + elif dim == AcquisitionGeometry.VERTICAL: + shape.append(vert) + elif dim == AcquisitionGeometry.HORIZONTAL: + shape.append(horiz) + if len(shape) != len(dimension_labels): + raise ValueError('Missing {0} axes.\nExpected{1} got {2}'\ + .format( + len(dimension_labels) - len(shape), + dimension_labels, shape) + ) + shape = tuple(shape) + return (shape, dimension_labels) + + + +class DataProcessor(object): + + '''Defines a generic DataContainer processor + + accepts DataContainer as inputs and + outputs DataContainer + additional attributes can be defined with __setattr__ + ''' + + def __init__(self, **attributes): + if not 'store_output' in attributes.keys(): + attributes['store_output'] = True + attributes['output'] = False + attributes['runTime'] = -1 + attributes['mTime'] = datetime.now() + attributes['input'] = None + for key, value in attributes.items(): + self.__dict__[key] = value + + + def __setattr__(self, name, value): + if name == 'input': + self.set_input(value) + elif name in self.__dict__.keys(): + self.__dict__[name] = value + self.__dict__['mTime'] = datetime.now() + else: + raise KeyError('Attribute {0} not found'.format(name)) + #pass + + def set_input(self, dataset): + if issubclass(type(dataset), DataContainer): + if self.check_input(dataset): + self.__dict__['input'] = dataset + else: + raise TypeError("Input type mismatch: got {0} expecting {1}"\ + .format(type(dataset), DataContainer)) + + def check_input(self, dataset): + '''Checks parameters of the input DataContainer + + Should raise an Error if the DataContainer does not match expectation, e.g. + if the expected input DataContainer is 3D and the Processor expects 2D. + ''' + raise NotImplementedError('Implement basic checks for input DataContainer') + + def get_output(self, out=None): + + for k,v in self.__dict__.items(): + if v is None and k != 'output': + raise ValueError('Key {0} is None'.format(k)) + shouldRun = False + if self.runTime == -1: + shouldRun = True + elif self.mTime > self.runTime: + shouldRun = True + + # CHECK this + if self.store_output and shouldRun: + self.runTime = datetime.now() + try: + self.output = self.process(out=out) + return self.output + except TypeError as te: + self.output = self.process() + return self.output + self.runTime = datetime.now() + try: + return self.process(out=out) + except TypeError as te: + return self.process() + + + def set_input_processor(self, processor): + if issubclass(type(processor), DataProcessor): + self.__dict__['input'] = processor + else: + raise TypeError("Input type mismatch: got {0} expecting {1}"\ + .format(type(processor), DataProcessor)) + + def get_input(self): + '''returns the input DataContainer + + It is useful in the case the user has provided a DataProcessor as + input + ''' + if issubclass(type(self.input), DataProcessor): + dsi = self.input.get_output() + else: + dsi = self.input + return dsi + + def process(self, out=None): + raise NotImplementedError('process must be implemented') + + + + +class DataProcessor23D(DataProcessor): + '''Regularizers DataProcessor + ''' + + def check_input(self, dataset): + '''Checks number of dimensions input DataContainer + + Expected input is 2D or 3D + ''' + if dataset.number_of_dimensions == 2 or \ + dataset.number_of_dimensions == 3: + return True + else: + raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ + .format(dataset.number_of_dimensions)) + +###### Example of DataProcessors + +class AX(DataProcessor): + '''Example DataProcessor + The AXPY routines perform a vector multiplication operation defined as + + y := a*x + where: + + a is a scalar + + x a DataContainer. + ''' + + def __init__(self): + kwargs = {'scalar':None, + 'input':None, + } + + #DataProcessor.__init__(self, **kwargs) + super(AX, self).__init__(**kwargs) + + def check_input(self, dataset): + return True + + def process(self, out=None): + + dsi = self.get_input() + a = self.scalar + if out is None: + y = DataContainer( a * dsi.as_array() , True, + dimension_labels=dsi.dimension_labels ) + #self.setParameter(output_dataset=y) + return y + else: + out.fill(a * dsi.as_array()) + + +###### Example of DataProcessors + +class CastDataContainer(DataProcessor): + '''Example DataProcessor + Cast a DataContainer array to a different type. + + y := a*x + where: + + a is a scalar + + x a DataContainer. + ''' + + def __init__(self, dtype=None): + kwargs = {'dtype':dtype, + 'input':None, + } + + #DataProcessor.__init__(self, **kwargs) + super(CastDataContainer, self).__init__(**kwargs) + + def check_input(self, dataset): + return True + + def process(self, out=None): + + dsi = self.get_input() + dtype = self.dtype + if out is None: + y = numpy.asarray(dsi.as_array(), dtype=dtype) + + return type(dsi)(numpy.asarray(dsi.as_array(), dtype=dtype), + dimension_labels=dsi.dimension_labels ) + else: + out.fill(numpy.asarray(dsi.as_array(), dtype=dtype)) + + + + + +class PixelByPixelDataProcessor(DataProcessor): + '''Example DataProcessor + + This processor applies a python function to each pixel of the DataContainer + + f is a python function + + x a DataSet. + ''' + + def __init__(self): + kwargs = {'pyfunc':None, + 'input':None, + } + #DataProcessor.__init__(self, **kwargs) + super(PixelByPixelDataProcessor, self).__init__(**kwargs) + + def check_input(self, dataset): + return True + + def process(self, out=None): + + pyfunc = self.pyfunc + dsi = self.get_input() + + eval_func = numpy.frompyfunc(pyfunc,1,1) + + + y = DataContainer( eval_func( dsi.as_array() ) , True, + dimension_labels=dsi.dimension_labels ) + return y + + + + +if __name__ == '__main__': + shape = (2,3,4,5) + size = shape[0] + for i in range(1, len(shape)): + size = size * shape[i] + #print("a refcount " , sys.getrefcount(a)) + a = numpy.asarray([i for i in range( size )]) + print("a refcount " , sys.getrefcount(a)) + a = numpy.reshape(a, shape) + print("a refcount " , sys.getrefcount(a)) + ds = DataContainer(a, False, ['X', 'Y','Z' ,'W']) + print("a refcount " , sys.getrefcount(a)) + print ("ds label {0}".format(ds.dimension_labels)) + subset = ['W' ,'X'] + b = ds.subset( subset ) + print("a refcount " , sys.getrefcount(a)) + print ("b label {0} shape {1}".format(b.dimension_labels, + numpy.shape(b.as_array()))) + c = ds.subset(['Z','W','X']) + print("a refcount " , sys.getrefcount(a)) + + # Create a ImageData sharing the array with c + volume0 = ImageData(c.as_array(), False, dimensions = c.dimension_labels) + volume1 = ImageData(c, False) + + print ("volume0 {0} volume1 {1}".format(id(volume0.array), + id(volume1.array))) + + # Create a ImageData copying the array from c + volume2 = ImageData(c.as_array(), dimensions = c.dimension_labels) + volume3 = ImageData(c) + + print ("volume2 {0} volume3 {1}".format(id(volume2.array), + id(volume3.array))) + + # single number DataSet + sn = DataContainer(numpy.asarray([1])) + + ax = AX() + ax.scalar = 2 + ax.set_input(c) + #ax.apply() + print ("ax in {0} out {1}".format(c.as_array().flatten(), + ax.get_output().as_array().flatten())) + + cast = CastDataContainer(dtype=numpy.float32) + cast.set_input(c) + out = cast.get_output() + out *= 0 + axm = AX() + axm.scalar = 0.5 + axm.set_input_processor(cast) + axm.get_output(out) + #axm.apply() + print ("axm in {0} out {1}".format(c.as_array(), axm.get_output().as_array())) + + # check out in DataSetProcessor + #a = numpy.asarray([i for i in range( size )]) + + + # create a PixelByPixelDataProcessor + + #define a python function which will take only one input (the pixel value) + pyfunc = lambda x: -x if x > 20 else x + clip = PixelByPixelDataProcessor() + clip.pyfunc = pyfunc + clip.set_input(c) + #clip.apply() + + print ("clip in {0} out {1}".format(c.as_array(), clip.get_output().as_array())) + + #dsp = DataProcessor() + #dsp.set_input(ds) + #dsp.input = a + # pipeline + + chain = AX() + chain.scalar = 0.5 + chain.set_input_processor(ax) + print ("chain in {0} out {1}".format(ax.get_output().as_array(), chain.get_output().as_array())) + + # testing arithmetic operations + + print (b) + print ((b+1)) + print ((1+b)) + + print (b) + print ((b*2)) + + print (b) + print ((2*b)) + + print (b) + print ((b/2)) + + print (b) + print ((2/b)) + + print (b) + print ((b**2)) + + print (b) + print ((2**b)) + + print (type(volume3 + 2)) + + s = [i for i in range(3 * 4 * 4)] + s = numpy.reshape(numpy.asarray(s), (3,4,4)) + sino = AcquisitionData( s ) + + shape = (4,3,2) + a = [i for i in range(2*3*4)] + a = numpy.asarray(a) + a = numpy.reshape(a, shape) + print (numpy.shape(a)) + ds = DataContainer(a, True, ['X', 'Y','Z']) + # this means that I expect the X to be of length 2 , + # y of length 3 and z of length 4 + subset = ['Y' ,'Z'] + b0 = ds.subset( subset ) + print ("shape b 3,2? {0}".format(numpy.shape(b0.as_array()))) + # expectation on b is that it is + # 3x2 cut at z = 0 + + subset = ['X' ,'Y'] + b1 = ds.subset( subset , Z=1) + print ("shape b 2,3? {0}".format(numpy.shape(b1.as_array()))) + + + + # create VolumeData from geometry + vgeometry = ImageGeometry(voxel_num_x=2, voxel_num_y=3, channels=2) + vol = ImageData(geometry=vgeometry) + + sgeometry = AcquisitionGeometry(dimension=2, angles=numpy.linspace(0, 180, num=20), + geom_type='parallel', pixel_num_v=3, + pixel_num_h=5 , channels=2) + sino = AcquisitionData(geometry=sgeometry) + sino2 = sino.clone() + + a0 = numpy.asarray([i for i in range(2*3*4)]) + a1 = numpy.asarray([2*i for i in range(2*3*4)]) + + + ds0 = DataContainer(numpy.reshape(a0,(2,3,4))) + ds1 = DataContainer(numpy.reshape(a1,(2,3,4))) + + numpy.testing.assert_equal(ds0.dot(ds1), a0.dot(a1)) + + a2 = numpy.asarray([2*i for i in range(2*3*5)]) + ds2 = DataContainer(numpy.reshape(a2,(2,3,5))) + +# # it should fail if the shape is wrong +# try: +# ds2.dot(ds0) +# self.assertTrue(False) +# except ValueError as ve: +# self.assertTrue(True) + diff --git a/Wrappers/Python/build/lib/ccpi/io/__init__.py b/Wrappers/Python/build/lib/ccpi/io/__init__.py new file mode 100644 index 0000000..9233d7a --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/io/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/io/reader.py b/Wrappers/Python/build/lib/ccpi/io/reader.py new file mode 100644 index 0000000..07e3bf9 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/io/reader.py @@ -0,0 +1,511 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Jakob Jorgensen, Daniil Kazantsev, Edoardo Pasca and Srikanth Nagella + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' +This is a reader module with classes for loading 3D datasets. + +@author: Mr. Srikanth Nagella +''' +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from ccpi.framework import AcquisitionGeometry +from ccpi.framework import AcquisitionData +import numpy as np +import os + +h5pyAvailable = True +try: + from h5py import File as NexusFile +except: + h5pyAvailable = False + +pilAvailable = True +try: + from PIL import Image +except: + pilAvailable = False + +class NexusReader(object): + ''' + Reader class for loading Nexus files. + ''' + + def __init__(self, nexus_filename=None): + ''' + This takes in input as filename and loads the data dataset. + ''' + self.flat = None + self.dark = None + self.angles = None + self.geometry = None + self.filename = nexus_filename + self.key_path = 'entry1/tomo_entry/instrument/detector/image_key' + self.data_path = 'entry1/tomo_entry/data/data' + self.angle_path = 'entry1/tomo_entry/data/rotation_angle' + + def get_image_keys(self): + try: + with NexusFile(self.filename,'r') as file: + return np.array(file[self.key_path]) + except KeyError as ke: + raise KeyError("get_image_keys: " , ke.args[0] , self.key_path) + + + def load(self, dimensions=None, image_key_id=0): + ''' + This is generic loading function of flat field, dark field and projection data. + ''' + if not h5pyAvailable: + raise Exception("Error: h5py is not installed") + if self.filename is None: + return + try: + with NexusFile(self.filename,'r') as file: + image_keys = np.array(file[self.key_path]) + projections = None + if dimensions == None: + projections = np.array(file[self.data_path]) + result = projections[image_keys==image_key_id] + return result + else: + #When dimensions are specified they need to be mapped to image_keys + index_array = np.where(image_keys==image_key_id) + projection_indexes = index_array[0][dimensions[0]] + new_dimensions = list(dimensions) + new_dimensions[0]= projection_indexes + new_dimensions = tuple(new_dimensions) + result = np.array(file[self.data_path][new_dimensions]) + return result + except: + print("Error reading nexus file") + raise + + def load_projection(self, dimensions=None): + ''' + Loads the projection data from the nexus file. + returns: numpy array with projection data + ''' + try: + if 0 not in self.get_image_keys(): + raise ValueError("Projections are not in the data. Data Path " , + self.data_path) + except KeyError as ke: + raise KeyError(ke.args[0] , self.data_path) + return self.load(dimensions, 0) + + def load_flat(self, dimensions=None): + ''' + Loads the flat field data from the nexus file. + returns: numpy array with flat field data + ''' + try: + if 1 not in self.get_image_keys(): + raise ValueError("Flats are not in the data. Data Path " , + self.data_path) + except KeyError as ke: + raise KeyError(ke.args[0] , self.data_path) + return self.load(dimensions, 1) + + def load_dark(self, dimensions=None): + ''' + Loads the Dark field data from the nexus file. + returns: numpy array with dark field data + ''' + try: + if 2 not in self.get_image_keys(): + raise ValueError("Darks are not in the data. Data Path " , + self.data_path) + except KeyError as ke: + raise KeyError(ke.args[0] , self.data_path) + return self.load(dimensions, 2) + + def get_projection_angles(self): + ''' + This function returns the projection angles + ''' + if not h5pyAvailable: + raise Exception("Error: h5py is not installed") + if self.filename is None: + return + try: + with NexusFile(self.filename,'r') as file: + angles = np.array(file[self.angle_path],np.float32) + image_keys = np.array(file[self.key_path]) + return angles[image_keys==0] + except: + print("get_projection_angles Error reading nexus file") + raise + + + def get_sinogram_dimensions(self): + ''' + Return the dimensions of the dataset + ''' + if not h5pyAvailable: + raise Exception("Error: h5py is not installed") + if self.filename is None: + return + try: + with NexusFile(self.filename,'r') as file: + projections = file[self.data_path] + image_keys = np.array(file[self.key_path]) + dims = list(projections.shape) + dims[0] = dims[1] + dims[1] = np.sum(image_keys==0) + return tuple(dims) + except: + print("Error reading nexus file") + raise + + def get_projection_dimensions(self): + ''' + Return the dimensions of the dataset + ''' + if not h5pyAvailable: + raise Exception("Error: h5py is not installed") + if self.filename is None: + return + try: + with NexusFile(self.filename,'r') as file: + try: + projections = file[self.data_path] + except KeyError as ke: + raise KeyError('Error: data path {0} not found\n{1}'\ + .format(self.data_path, + ke.args[0])) + #image_keys = np.array(file[self.key_path]) + image_keys = self.get_image_keys() + dims = list(projections.shape) + dims[0] = np.sum(image_keys==0) + return tuple(dims) + except: + print("Warning: Error reading image_keys trying accessing data on " , self.data_path) + with NexusFile(self.filename,'r') as file: + dims = file[self.data_path].shape + return tuple(dims) + + + + def get_acquisition_data(self, dimensions=None): + ''' + This method load the acquisition data and given dimension and returns an AcquisitionData Object + ''' + data = self.load_projection(dimensions) + dims = self.get_projection_dimensions() + geometry = AcquisitionGeometry('parallel', '3D', + self.get_projection_angles(), + pixel_num_h = dims[2], + pixel_size_h = 1 , + pixel_num_v = dims[1], + pixel_size_v = 1, + dist_source_center = None, + dist_center_detector = None, + channels = 1) + return AcquisitionData(data, geometry=geometry, + dimension_labels=['angle','vertical','horizontal']) + + def get_acquisition_data_subset(self, ymin=None, ymax=None): + ''' + This method load the acquisition data and given dimension and returns an AcquisitionData Object + ''' + if not h5pyAvailable: + raise Exception("Error: h5py is not installed") + if self.filename is None: + return + try: + + + with NexusFile(self.filename,'r') as file: + try: + dims = self.get_projection_dimensions() + except KeyError: + pass + dims = file[self.data_path].shape + if ymin is None and ymax is None: + + try: + image_keys = self.get_image_keys() + print ("image_keys", image_keys) + projections = np.array(file[self.data_path]) + data = projections[image_keys==0] + except KeyError as ke: + print (ke) + data = np.array(file[self.data_path]) + + else: + image_keys = self.get_image_keys() + print ("image_keys", image_keys) + projections = np.array(file[self.data_path])[image_keys==0] + if ymin is None: + ymin = 0 + if ymax > dims[1]: + raise ValueError('ymax out of range') + data = projections[:,:ymax,:] + elif ymax is None: + ymax = dims[1] + if ymin < 0: + raise ValueError('ymin out of range') + data = projections[:,ymin:,:] + else: + if ymax > dims[1]: + raise ValueError('ymax out of range') + if ymin < 0: + raise ValueError('ymin out of range') + + data = projections[: , ymin:ymax , :] + + except: + print("Error reading nexus file") + raise + + + try: + angles = self.get_projection_angles() + except KeyError as ke: + n = data.shape[0] + angles = np.linspace(0, n, n+1, dtype=np.float32) + + if ymax-ymin > 1: + + geometry = AcquisitionGeometry('parallel', '3D', + angles, + pixel_num_h = dims[2], + pixel_size_h = 1 , + pixel_num_v = ymax-ymin, + pixel_size_v = 1, + dist_source_center = None, + dist_center_detector = None, + channels = 1) + return AcquisitionData(data, False, geometry=geometry, + dimension_labels=['angle','vertical','horizontal']) + elif ymax-ymin == 1: + geometry = AcquisitionGeometry('parallel', '2D', + angles, + pixel_num_h = dims[2], + pixel_size_h = 1 , + dist_source_center = None, + dist_center_detector = None, + channels = 1) + return AcquisitionData(data.squeeze(), False, geometry=geometry, + dimension_labels=['angle','horizontal']) + def get_acquisition_data_slice(self, y_slice=0): + return self.get_acquisition_data_subset(ymin=y_slice , ymax=y_slice+1) + def get_acquisition_data_whole(self): + with NexusFile(self.filename,'r') as file: + try: + dims = self.get_projection_dimensions() + except KeyError: + print ("Warning: ") + dims = file[self.data_path].shape + + ymin = 0 + ymax = dims[1] - 1 + + return self.get_acquisition_data_subset(ymin=ymin, ymax=ymax) + + + + def list_file_content(self): + try: + with NexusFile(self.filename,'r') as file: + file.visit(print) + except: + print("Error reading nexus file") + raise + def get_acquisition_data_batch(self, bmin=None, bmax=None): + if not h5pyAvailable: + raise Exception("Error: h5py is not installed") + if self.filename is None: + return + try: + + + with NexusFile(self.filename,'r') as file: + try: + dims = self.get_projection_dimensions() + except KeyError: + dims = file[self.data_path].shape + if bmin is None or bmax is None: + raise ValueError('get_acquisition_data_batch: please specify fastest index batch limits') + + if bmin >= 0 and bmin < bmax and bmax <= dims[0]: + data = np.array(file[self.data_path][bmin:bmax]) + else: + raise ValueError('get_acquisition_data_batch: bmin {0}>0 bmax {1}<{2}'.format(bmin, bmax, dims[0])) + + except: + print("Error reading nexus file") + raise + + + try: + angles = self.get_projection_angles()[bmin:bmax] + except KeyError as ke: + n = data.shape[0] + angles = np.linspace(0, n, n+1, dtype=np.float32)[bmin:bmax] + + if bmax-bmin > 1: + + geometry = AcquisitionGeometry('parallel', '3D', + angles, + pixel_num_h = dims[2], + pixel_size_h = 1 , + pixel_num_v = bmax-bmin, + pixel_size_v = 1, + dist_source_center = None, + dist_center_detector = None, + channels = 1) + return AcquisitionData(data, False, geometry=geometry, + dimension_labels=['angle','vertical','horizontal']) + elif bmax-bmin == 1: + geometry = AcquisitionGeometry('parallel', '2D', + angles, + pixel_num_h = dims[2], + pixel_size_h = 1 , + dist_source_center = None, + dist_center_detector = None, + channels = 1) + return AcquisitionData(data.squeeze(), False, geometry=geometry, + dimension_labels=['angle','horizontal']) + + + +class XTEKReader(object): + ''' + Reader class for loading XTEK files + ''' + + def __init__(self, xtek_config_filename=None): + ''' + This takes in the xtek config filename and loads the dataset and the + required geometry parameters + ''' + self.projections = None + self.geometry = {} + self.filename = xtek_config_filename + self.load() + + def load(self): + pixel_num_h = 0 + pixel_num_v = 0 + xpixel_size = 0 + ypixel_size = 0 + source_x = 0 + detector_x = 0 + with open(self.filename) as f: + content = f.readlines() + content = [x.strip() for x in content] + for line in content: + if line.startswith("SrcToObject"): + source_x = float(line.split('=')[1]) + elif line.startswith("SrcToDetector"): + detector_x = float(line.split('=')[1]) + elif line.startswith("DetectorPixelsY"): + pixel_num_v = int(line.split('=')[1]) + #self.num_of_vertical_pixels = self.calc_v_alighment(self.num_of_vertical_pixels, self.pixels_per_voxel) + elif line.startswith("DetectorPixelsX"): + pixel_num_h = int(line.split('=')[1]) + elif line.startswith("DetectorPixelSizeX"): + xpixel_size = float(line.split('=')[1]) + elif line.startswith("DetectorPixelSizeY"): + ypixel_size = float(line.split('=')[1]) + elif line.startswith("Projections"): + self.num_projections = int(line.split('=')[1]) + elif line.startswith("InitialAngle"): + self.initial_angle = float(line.split('=')[1]) + elif line.startswith("Name"): + self.experiment_name = line.split('=')[1] + elif line.startswith("Scattering"): + self.scattering = float(line.split('=')[1]) + elif line.startswith("WhiteLevel"): + self.white_level = float(line.split('=')[1]) + elif line.startswith("MaskRadius"): + self.mask_radius = float(line.split('=')[1]) + + #Read Angles + angles = self.read_angles() + self.geometry = AcquisitionGeometry('cone', '3D', angles, pixel_num_h, xpixel_size, pixel_num_v, ypixel_size, -1 * source_x, + detector_x - source_x, + ) + + def read_angles(self): + """ + Read the angles file .ang or _ctdata.txt file and returns the angles + as an numpy array. + """ + input_path = os.path.dirname(self.filename) + angles_ctdata_file = os.path.join(input_path, '_ctdata.txt') + angles_named_file = os.path.join(input_path, self.experiment_name+'.ang') + angles = np.zeros(self.num_projections,dtype='f') + #look for _ctdata.txt + if os.path.exists(angles_ctdata_file): + #read txt file with angles + with open(angles_ctdata_file) as f: + content = f.readlines() + #skip firt three lines + #read the middle value of 3 values in each line as angles in degrees + index = 0 + for line in content[3:]: + self.angles[index]=float(line.split(' ')[1]) + index+=1 + angles = np.deg2rad(self.angles+self.initial_angle); + elif os.path.exists(angles_named_file): + #read the angles file which is text with first line as header + with open(angles_named_file) as f: + content = f.readlines() + #skip first line + index = 0 + for line in content[1:]: + angles[index] = float(line.split(':')[1]) + index+=1 + angles = np.flipud(angles+self.initial_angle) #angles are in the reverse order + else: + raise RuntimeError("Can't find angles file") + return angles + + def load_projection(self, dimensions=None): + ''' + This method reads the projection images from the directory and returns a numpy array + ''' + if not pilAvailable: + raise('Image library pillow is not installed') + if dimensions != None: + raise('Extracting subset of data is not implemented') + input_path = os.path.dirname(self.filename) + pixels = np.zeros((self.num_projections, self.geometry.pixel_num_h, self.geometry.pixel_num_v), dtype='float32') + for i in range(1, self.num_projections+1): + im = Image.open(os.path.join(input_path,self.experiment_name+"_%04d"%i+".tif")) + pixels[i-1,:,:] = np.fliplr(np.transpose(np.array(im))) ##Not sure this is the correct way to populate the image + + #normalising the data + #TODO: Move this to a processor + pixels = pixels - (self.white_level*self.scattering)/100.0 + pixels[pixels < 0.0] = 0.000001 # all negative values to approximately 0 as the std log of zero and non negative number is not defined + return pixels + + def get_acquisition_data(self, dimensions=None): + ''' + This method load the acquisition data and given dimension and returns an AcquisitionData Object + ''' + data = self.load_projection(dimensions) + return AcquisitionData(data, geometry=self.geometry) + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/__init__.py b/Wrappers/Python/build/lib/ccpi/optimisation/__init__.py new file mode 100644 index 0000000..cf2d93d --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py index 12cbabc..a14378c 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py @@ -34,7 +34,7 @@ class Algorithm(object): method will stop when the stopping cryterion is met. ''' - def __init__(self): + def __init__(self, **kwargs): '''Constructor Set the minimal number of parameters: @@ -48,11 +48,11 @@ class Algorithm(object): when evaluating the objective is computationally expensive. ''' self.iteration = 0 - self.__max_iteration = 0 + self.__max_iteration = kwargs.get('max_iteration', 0) self.__loss = [] self.memopt = False self.timing = [] - self.update_objective_interval = 1 + self.update_objective_interval = kwargs.get('update_objective_interval', 1) def set_up(self, *args, **kwargs): '''Set up the algorithm''' raise NotImplementedError() @@ -91,9 +91,11 @@ class Algorithm(object): if self.iteration % self.update_objective_interval == 0: self.update_objective() self.iteration += 1 + def get_output(self): '''Returns the solution found''' return self.x + def get_last_loss(self): '''Returns the last stored value of the loss function @@ -146,39 +148,13 @@ class Algorithm(object): print ("Stop cryterion has been reached.") i = 0 -# print("Iteration {:<5} Primal {:<5} Dual {:<5} PDgap".format('','','')) for _ in self: - - - if self.iteration % self.update_objective_interval == 0: - + if (self.iteration -1) % self.update_objective_interval == 0: + if verbose: + print ("Iteration {}/{}, = {}".format(self.iteration-1, + self.max_iteration, self.get_last_objective()) ) if callback is not None: - callback(self.iteration, self.get_last_objective(), self.x) - - else: - - if verbose: - -# if verbose and self.iteration % self.update_objective_interval == 0: - #pass - # \t for tab -# print( "{:04}/{:04} {:<5} {:.4f} {:<5} {:.4f} {:<5} {:.4f}".\ -# format(self.iteration, self.max_iteration,'', \ -# self.get_last_objective()[0],'',\ -# self.get_last_objective()[1],'',\ -# self.get_last_objective()[2])) - - - print ("Iteration {}/{}, {}".format(self.iteration, - self.max_iteration, self.get_last_objective()) ) - - #print ("Iteration {}/{}, Primal, Dual, PDgap = {}".format(self.iteration, - # self.max_iteration, self.get_last_objective()) ) - - -# else: -# if callback is not None: -# callback(self.iteration, self.get_last_objective(), self.x) + callback(self.iteration -1, self.get_last_objective(), self.x) i += 1 if i == iterations: break diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/CGLS.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/CGLS.py new file mode 100644 index 0000000..4d4843c --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/CGLS.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Created on Thu Feb 21 11:11:23 2019 + +@author: ofn77899 +""" + +from ccpi.optimisation.algorithms import Algorithm + +class CGLS(Algorithm): + + '''Conjugate Gradient Least Squares algorithm + + Parameters: + x_init: initial guess + operator: operator for forward/backward projections + data: data to operate on + ''' + def __init__(self, **kwargs): + super(CGLS, self).__init__() + self.x = kwargs.get('x_init', None) + self.operator = kwargs.get('operator', None) + self.data = kwargs.get('data', None) + if self.x is not None and self.operator is not None and \ + self.data is not None: + print ("Calling from creator") + self.set_up(x_init =kwargs['x_init'], + operator=kwargs['operator'], + data =kwargs['data']) + + def set_up(self, x_init, operator , data ): + + self.r = data.copy() + self.x = x_init.copy() + + self.operator = operator + self.d = operator.adjoint(self.r) + + + self.normr2 = self.d.squared_norm() + #if isinstance(self.normr2, Iterable): + # self.normr2 = sum(self.normr2) + #self.normr2 = numpy.sqrt(self.normr2) + #print ("set_up" , self.normr2) + + def update(self): + + Ad = self.operator.direct(self.d) + #norm = (Ad*Ad).sum() + #if isinstance(norm, Iterable): + # norm = sum(norm) + norm = Ad.squared_norm() + + alpha = self.normr2/norm + self.x += (self.d * alpha) + self.r -= (Ad * alpha) + s = self.operator.adjoint(self.r) + + normr2_new = s.squared_norm() + #if isinstance(normr2_new, Iterable): + # normr2_new = sum(normr2_new) + #normr2_new = numpy.sqrt(normr2_new) + #print (normr2_new) + + beta = normr2_new/self.normr2 + self.normr2 = normr2_new + self.d = s + beta*self.d + + def update_objective(self): + self.loss.append(self.r.squared_norm()) \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FBPD.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FBPD.py new file mode 100644 index 0000000..aa07359 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FBPD.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2019 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Created on Thu Feb 21 11:09:03 2019 + +@author: ofn77899 +""" + +from ccpi.optimisation.algorithms import Algorithm +from ccpi.optimisation.functions import ZeroFunction + +class FBPD(Algorithm): + '''FBPD Algorithm + + Parameters: + x_init: initial guess + f: constraint + g: data fidelity + h: regularizer + opt: additional algorithm + ''' + constraint = None + data_fidelity = None + regulariser = None + def __init__(self, **kwargs): + pass + def set_up(self, x_init, operator=None, constraint=None, data_fidelity=None,\ + regulariser=None, opt=None): + + # default inputs + if constraint is None: + self.constraint = ZeroFun() + else: + self.constraint = constraint + if data_fidelity is None: + data_fidelity = ZeroFun() + else: + self.data_fidelity = data_fidelity + if regulariser is None: + self.regulariser = ZeroFun() + else: + self.regulariser = regulariser + + # algorithmic parameters + + + # step-sizes + self.tau = 2 / (self.data_fidelity.L + 2) + self.sigma = (1/self.tau - self.data_fidelity.L/2) / self.regulariser.L + + self.inv_sigma = 1/self.sigma + + # initialization + self.x = x_init + self.y = operator.direct(self.x) + + + def update(self): + + # primal forward-backward step + x_old = self.x + self.x = self.x - self.tau * ( self.data_fidelity.grad(self.x) + self.operator.adjoint(self.y) ) + self.x = self.constraint.prox(self.x, self.tau); + + # dual forward-backward step + self.y = self.y + self.sigma * self.operator.direct(2*self.x - x_old); + self.y = self.y - self.sigma * self.regulariser.prox(self.inv_sigma*self.y, self.inv_sigma); + + # time and criterion + self.loss = self.constraint(self.x) + self.data_fidelity(self.x) + self.regulariser(self.operator.direct(self.x)) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/GradientDescent.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/GradientDescent.py new file mode 100644 index 0000000..14763c5 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/GradientDescent.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2019 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Created on Thu Feb 21 11:05:09 2019 + +@author: ofn77899 +""" +from ccpi.optimisation.algorithms import Algorithm + +class GradientDescent(Algorithm): + '''Implementation of Gradient Descent algorithm + ''' + + def __init__(self, **kwargs): + '''initialisation can be done at creation time if all + proper variables are passed or later with set_up''' + super(GradientDescent, self).__init__() + self.x = None + self.rate = 0 + self.objective_function = None + self.regulariser = None + args = ['x_init', 'objective_function', 'rate'] + for k,v in kwargs.items(): + if k in args: + args.pop(args.index(k)) + if len(args) == 0: + return self.set_up(x_init=kwargs['x_init'], + objective_function=kwargs['objective_function'], + rate=kwargs['rate']) + + def should_stop(self): + '''stopping cryterion, currently only based on number of iterations''' + return self.iteration >= self.max_iteration + + def set_up(self, x_init, objective_function, rate): + '''initialisation of the algorithm''' + self.x = x_init.copy() + self.objective_function = objective_function + self.rate = rate + self.loss.append(objective_function(x_init)) + self.iteration = 0 + try: + self.memopt = self.objective_function.memopt + except AttributeError as ae: + self.memopt = False + if self.memopt: + self.x_update = x_init.copy() + + def update(self): + '''Single iteration''' + if self.memopt: + self.objective_function.gradient(self.x, out=self.x_update) + self.x_update *= -self.rate + self.x += self.x_update + else: + self.x += -self.rate * self.objective_function.gradient(self.x) + + def update_objective(self): + self.loss.append(self.objective_function(self.x)) + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py index 0f5e8ef..39b092b 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py @@ -13,117 +13,79 @@ import time from ccpi.optimisation.operators import BlockOperator from ccpi.framework import BlockDataContainer from ccpi.optimisation.functions import FunctionOperatorComposition -import matplotlib.pyplot as plt class PDHG(Algorithm): '''Primal Dual Hybrid Gradient''' def __init__(self, **kwargs): - super(PDHG, self).__init__() + super(PDHG, self).__init__(max_iteration=kwargs.get('max_iteration',0)) self.f = kwargs.get('f', None) self.operator = kwargs.get('operator', None) self.g = kwargs.get('g', None) self.tau = kwargs.get('tau', None) self.sigma = kwargs.get('sigma', None) - self.memopt = kwargs.get('memopt', False) - + if self.f is not None and self.operator is not None and \ self.g is not None: print ("Calling from creator") self.set_up(self.f, + self.g, self.operator, - self.g, self.tau, self.sigma) def set_up(self, f, g, operator, tau = None, sigma = None, opt = None, **kwargs): # algorithmic parameters - + self.operator = operator + self.f = f + self.g = g + self.tau = tau + self.sigma = sigma + self.opt = opt if sigma is None and tau is None: raise ValueError('Need sigma*tau||K||^2<1') - - + self.x_old = self.operator.domain_geometry().allocate() - self.y_old = self.operator.range_geometry().allocate() - - self.xbar = self.x_old.copy() self.x_tmp = self.x_old.copy() self.x = self.x_old.copy() - - self.y_tmp = self.y_old.copy() - self.y = self.y_tmp.copy() - - - - #self.y = self.y_old.copy() - - - #if self.memopt: - # self.y_tmp = self.y_old.copy() - # self.x_tmp = self.x_old.copy() + + self.y_old = self.operator.range_geometry().allocate() + self.y_tmp = self.y_old.copy() + self.y = self.y_old.copy() + + self.xbar = self.x_old.copy() - # relaxation parameter self.theta = 1 def update(self): - if self.memopt: - # Gradient descent, Dual problem solution - # self.y_old += self.sigma * self.operator.direct(self.xbar) - self.operator.direct(self.xbar, out=self.y_tmp) - self.y_tmp *= self.sigma - self.y_tmp += self.y_old - - #self.y = self.f.proximal_conjugate(self.y_old, self.sigma) - self.f.proximal_conjugate(self.y_tmp, self.sigma, out=self.y) - - # Gradient ascent, Primal problem solution - self.operator.adjoint(self.y, out=self.x_tmp) - self.x_tmp *= -1*self.tau - self.x_tmp += self.x_old - - self.g.proximal(self.x_tmp, self.tau, out=self.x) - - #Update - self.x.subtract(self.x_old, out=self.xbar) - self.xbar *= self.theta - self.xbar += self.x - - self.x_old.fill(self.x) - self.y_old.fill(self.y) - - - else: - # Gradient descent, Dual problem solution - self.y_old += self.sigma * self.operator.direct(self.xbar) - self.y = self.f.proximal_conjugate(self.y_old, self.sigma) - - # Gradient ascent, Primal problem solution - self.x_old -= self.tau * self.operator.adjoint(self.y) - self.x = self.g.proximal(self.x_old, self.tau) + # Gradient descent, Dual problem solution + self.operator.direct(self.xbar, out=self.y_tmp) + self.y_tmp *= self.sigma + self.y_tmp += self.y_old - #Update - self.x.subtract(self.x_old, out=self.xbar) - self.xbar *= self.theta - self.xbar += self.x + #self.y = self.f.proximal_conjugate(self.y_old, self.sigma) + self.f.proximal_conjugate(self.y_tmp, self.sigma, out=self.y) + + # Gradient ascent, Primal problem solution + self.operator.adjoint(self.y, out=self.x_tmp) + self.x_tmp *= -1*self.tau + self.x_tmp += self.x_old - self.x_old.fill(self.x) - self.y_old.fill(self.y) - - #xbar = x + theta * (x - x_old) -# self.xbar.fill(self.x) -# self.xbar -= self.x_old -# self.xbar *= self.theta -# self.xbar += self.x - -# self.x_old.fill(self.x) -# self.y_old.fill(self.y) - + self.g.proximal(self.x_tmp, self.tau, out=self.x) + + #Update + self.x.subtract(self.x_old, out=self.xbar) + self.xbar *= self.theta + self.xbar += self.x + + self.x_old.fill(self.x) + self.y_old.fill(self.y) def update_objective(self): - + p1 = self.f(self.operator.direct(self.x)) + self.g(self.x) d1 = -(self.f.convex_conjugate(self.y) + self.g.convex_conjugate(-1*self.operator.adjoint(self.y))) @@ -169,64 +131,44 @@ def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): for i in range(niter): + - if not memopt: - - y_tmp = y_old + sigma * operator.direct(xbar) - y = f.proximal_conjugate(y_tmp, sigma) - - x_tmp = x_old - tau*operator.adjoint(y) - x = g.proximal(x_tmp, tau) - - x.subtract(x_old, out=xbar) - xbar *= theta - xbar += x - - if i%50==0: - - p1 = f(operator.direct(x)) + g(x) - d1 = - ( f.convex_conjugate(y) + g.convex_conjugate(-1*operator.adjoint(y)) ) - primal.append(p1) - dual.append(d1) - pdgap.append(p1-d1) - print(p1, d1, p1-d1) - - x_old.fill(x) - y_old.fill(y) - - - else: - + if memopt: operator.direct(xbar, out = y_tmp) y_tmp *= sigma - y_tmp += y_old - f.proximal_conjugate(y_tmp, sigma, out=y) - + y_tmp += y_old + else: + y_tmp = y_old + sigma * operator.direct(xbar) + + f.proximal_conjugate(y_tmp, sigma, out=y) + + if memopt: operator.adjoint(y, out = x_tmp) x_tmp *= -1*tau x_tmp += x_old - - g.proximal(x_tmp, tau, out = x) - - x.subtract(x_old, out=xbar) - xbar *= theta - xbar += x + else: + x_tmp = x_old - tau*operator.adjoint(y) - if i%50==0: - - p1 = f(operator.direct(x)) + g(x) - d1 = - ( f.convex_conjugate(y) + g.convex_conjugate(-1*operator.adjoint(y)) ) - primal.append(p1) - dual.append(d1) - pdgap.append(p1-d1) - print(p1, d1, p1-d1) - - x_old.fill(x) - y_old.fill(y) + g.proximal(x_tmp, tau, out=x) + + x.subtract(x_old, out=xbar) + xbar *= theta + xbar += x + + x_old.fill(x) + y_old.fill(y) - + if i%10==0: + + p1 = f(operator.direct(x)) + g(x) + d1 = - ( f.convex_conjugate(y) + g.convex_conjugate(-1*operator.adjoint(y)) ) + primal.append(p1) + dual.append(d1) + pdgap.append(p1-d1) + print(p1, d1, p1-d1) + t_end = time.time() diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/SIRT.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/SIRT.py new file mode 100644 index 0000000..30584d4 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/SIRT.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.optimisation.algorithms import Algorithm + +class SIRT(Algorithm): + + '''Simultaneous Iterative Reconstruction Technique + + Parameters: + x_init: initial guess + operator: operator for forward/backward projections + data: data to operate on + constraint: Function with prox-method, for example IndicatorBox to + enforce box constraints, default is None). + ''' + def __init__(self, **kwargs): + super(SIRT, self).__init__() + self.x = kwargs.get('x_init', None) + self.operator = kwargs.get('operator', None) + self.data = kwargs.get('data', None) + self.constraint = kwargs.get('constraint', None) + if self.x is not None and self.operator is not None and \ + self.data is not None: + print ("Calling from creator") + self.set_up(x_init=kwargs['x_init'], + operator=kwargs['operator'], + data=kwargs['data'], + constraint=kwargs['constraint']) + + def set_up(self, x_init, operator , data, constraint=None ): + + self.x = x_init.copy() + self.operator = operator + self.data = data + self.constraint = constraint + + self.r = data.copy() + + self.relax_par = 1.0 + + # Set up scaling matrices D and M. + self.M = 1/self.operator.direct(self.operator.domain_geometry().allocate(value=1.0)) + self.D = 1/self.operator.adjoint(self.operator.range_geometry().allocate(value=1.0)) + + + def update(self): + + self.r = self.data - self.operator.direct(self.x) + + self.x += self.relax_par * (self.D*self.operator.adjoint(self.M*self.r)) + + if self.constraint != None: + self.x = self.constraint.prox(self.x,None) + + def update_objective(self): + self.loss.append(self.r.squared_norm()) \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/__init__.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/__init__.py new file mode 100644 index 0000000..2dbacfc --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/__init__.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2019 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Created on Thu Feb 21 11:03:13 2019 + +@author: ofn77899 +""" + +from .Algorithm import Algorithm +from .CGLS import CGLS +from .SIRT import SIRT +from .GradientDescent import GradientDescent +from .FISTA import FISTA +from .FBPD import FBPD +from .PDHG import PDHG +from .PDHG import PDHG_old + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algs.py b/Wrappers/Python/build/lib/ccpi/optimisation/algs.py new file mode 100644 index 0000000..f5ba85e --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/algs.py @@ -0,0 +1,307 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy +import time + + + + +def FISTA(x_init, f=None, g=None, opt=None): + '''Fast Iterative Shrinkage-Thresholding Algorithm + + Beck, A. and Teboulle, M., 2009. A fast iterative shrinkage-thresholding + algorithm for linear inverse problems. + SIAM journal on imaging sciences,2(1), pp.183-202. + + Parameters: + x_init: initial guess + f: data fidelity + g: regularizer + h: + opt: additional algorithm + ''' + # default inputs + if f is None: f = ZeroFun() + if g is None: g = ZeroFun() + + # algorithmic parameters + if opt is None: + opt = {'tol': 1e-4, 'iter': 1000, 'memopt':False} + + max_iter = opt['iter'] if 'iter' in opt.keys() else 1000 + tol = opt['tol'] if 'tol' in opt.keys() else 1e-4 + memopt = opt['memopt'] if 'memopt' in opt.keys() else False + + + # initialization + if memopt: + y = x_init.clone() + x_old = x_init.clone() + x = x_init.clone() + u = x_init.clone() + else: + x_old = x_init + y = x_init; + + timing = numpy.zeros(max_iter) + criter = numpy.zeros(max_iter) + + invL = 1/f.L + + t_old = 1 + +# c = f(x_init) + g(x_init) + + # algorithm loop + for it in range(0, max_iter): + + time0 = time.time() + if memopt: + # u = y - invL*f.grad(y) + # store the result in x_old + f.gradient(y, out=u) + u.__imul__( -invL ) + u.__iadd__( y ) + # x = g.prox(u,invL) + g.proximal(u, invL, out=x) + + t = 0.5*(1 + numpy.sqrt(1 + 4*(t_old**2))) + + # y = x + (t_old-1)/t*(x-x_old) + x.subtract(x_old, out=y) + y.__imul__ ((t_old-1)/t) + y.__iadd__( x ) + + x_old.fill(x) + t_old = t + + + else: + u = y - invL*f.gradient(y) + + x = g.proximal(u,invL) + + t = 0.5*(1 + numpy.sqrt(1 + 4*(t_old**2))) + + y = x + (t_old-1)/t*(x-x_old) + + x_old = x.copy() + t_old = t + + # time and criterion +# timing[it] = time.time() - time0 +# criter[it] = f(x) + g(x); + + # stopping rule + #if np.linalg.norm(x - x_old) < tol * np.linalg.norm(x_old) and it > 10: + # break + + #print(it, 'out of', 10, 'iterations', end='\r'); + + #criter = criter[0:it+1]; +# timing = numpy.cumsum(timing[0:it+1]); + + return x #, it, timing, criter + +def FBPD(x_init, operator=None, constraint=None, data_fidelity=None,\ + regulariser=None, opt=None): + '''FBPD Algorithm + + Parameters: + x_init: initial guess + f: constraint + g: data fidelity + h: regularizer + opt: additional algorithm + ''' + # default inputs + if constraint is None: constraint = ZeroFun() + if data_fidelity is None: data_fidelity = ZeroFun() + if regulariser is None: regulariser = ZeroFun() + + # algorithmic parameters + if opt is None: + opt = {'tol': 1e-4, 'iter': 1000} + else: + try: + max_iter = opt['iter'] + except KeyError as ke: + opt[ke] = 1000 + try: + opt['tol'] = 1000 + except KeyError as ke: + opt[ke] = 1e-4 + tol = opt['tol'] + max_iter = opt['iter'] + memopt = opt['memopts'] if 'memopts' in opt.keys() else False + + # step-sizes + tau = 2 / (data_fidelity.L + 2) + sigma = (1/tau - data_fidelity.L/2) / regulariser.L + inv_sigma = 1/sigma + + # initialization + x = x_init + y = operator.direct(x); + + timing = numpy.zeros(max_iter) + criter = numpy.zeros(max_iter) + + + + + # algorithm loop + for it in range(0, max_iter): + + t = time.time() + + # primal forward-backward step + x_old = x; + x = x - tau * ( data_fidelity.grad(x) + operator.adjoint(y) ); + x = constraint.prox(x, tau); + + # dual forward-backward step + y = y + sigma * operator.direct(2*x - x_old); + y = y - sigma * regulariser.prox(inv_sigma*y, inv_sigma); + + # time and criterion + timing[it] = time.time() - t + criter[it] = constraint(x) + data_fidelity(x) + regulariser(operator.direct(x)) + + # stopping rule + #if np.linalg.norm(x - x_old) < tol * np.linalg.norm(x_old) and it > 10: + # break + + criter = criter[0:it+1] + timing = numpy.cumsum(timing[0:it+1]) + + return x, it, timing, criter + +def CGLS(x_init, operator , data , opt=None): + '''Conjugate Gradient Least Squares algorithm + + Parameters: + x_init: initial guess + operator: operator for forward/backward projections + data: data to operate on + opt: additional algorithm + ''' + + if opt is None: + opt = {'tol': 1e-4, 'iter': 1000} + else: + try: + max_iter = opt['iter'] + except KeyError as ke: + opt[ke] = 1000 + try: + opt['tol'] = 1000 + except KeyError as ke: + opt[ke] = 1e-4 + tol = opt['tol'] + max_iter = opt['iter'] + + r = data.copy() + x = x_init.copy() + + d = operator.adjoint(r) + + normr2 = (d**2).sum() + + timing = numpy.zeros(max_iter) + criter = numpy.zeros(max_iter) + + # algorithm loop + for it in range(0, max_iter): + + t = time.time() + + Ad = operator.direct(d) + alpha = normr2/( (Ad**2).sum() ) + x = x + alpha*d + r = r - alpha*Ad + s = operator.adjoint(r) + + normr2_new = (s**2).sum() + beta = normr2_new/normr2 + normr2 = normr2_new + d = s + beta*d + + # time and criterion + timing[it] = time.time() - t + criter[it] = (r**2).sum() + + return x, it, timing, criter + +def SIRT(x_init, operator , data , opt=None, constraint=None): + '''Simultaneous Iterative Reconstruction Technique + + Parameters: + x_init: initial guess + operator: operator for forward/backward projections + data: data to operate on + opt: additional algorithm + constraint: func of Indicator type specifying convex constraint. + ''' + + if opt is None: + opt = {'tol': 1e-4, 'iter': 1000} + else: + try: + max_iter = opt['iter'] + except KeyError as ke: + opt[ke] = 1000 + try: + opt['tol'] = 1000 + except KeyError as ke: + opt[ke] = 1e-4 + tol = opt['tol'] + max_iter = opt['iter'] + + x = x_init.clone() + + timing = numpy.zeros(max_iter) + criter = numpy.zeros(max_iter) + + # Relaxation parameter must be strictly between 0 and 2. For now fix at 1.0 + relax_par = 1.0 + + # Set up scaling matrices D and M. + M = 1/operator.direct(operator.domain_geometry().allocate(value=1.0)) + D = 1/operator.adjoint(operator.range_geometry().allocate(value=1.0)) + + # algorithm loop + for it in range(0, max_iter): + t = time.time() + r = data - operator.direct(x) + + x = x + relax_par * (D*operator.adjoint(M*r)) + + if constraint != None: + x = constraint.prox(x,None) + + timing[it] = time.time() - t + if it > 0: + criter[it-1] = (r**2).sum() + + r = data - operator.direct(x) + criter[it] = (r**2).sum() + return x, it, timing, criter + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/Function.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/Function.py new file mode 100644 index 0000000..ba33666 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/Function.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import warnings +from ccpi.optimisation.functions.ScaledFunction import ScaledFunction + +class Function(object): + '''Abstract class representing a function + + Members: + L is the Lipschitz constant of the gradient of the Function + ''' + def __init__(self): + self.L = None + + def __call__(self,x, out=None): + '''Evaluates the function at x ''' + raise NotImplementedError + + def gradient(self, x, out=None): + '''Returns the gradient of the function at x, if the function is differentiable''' + raise NotImplementedError + + def proximal(self, x, tau, out=None): + '''This returns the proximal operator for the function at x, tau''' + raise NotImplementedError + + def convex_conjugate(self, x, out=None): + '''This evaluates the convex conjugate of the function at x''' + raise NotImplementedError + + def proximal_conjugate(self, x, tau, out = None): + '''This returns the proximal operator for the convex conjugate of the function at x, tau''' + raise NotImplementedError + + def grad(self, x): + '''Alias of gradient(x,None)''' + warnings.warn('''This method will disappear in following + versions of the CIL. Use gradient instead''', DeprecationWarning) + return self.gradient(x, out=None) + + def prox(self, x, tau): + '''Alias of proximal(x, tau, None)''' + warnings.warn('''This method will disappear in following + versions of the CIL. Use proximal instead''', DeprecationWarning) + return self.proximal(x, tau, out=None) + + def __rmul__(self, scalar): + '''Defines the multiplication by a scalar on the left + + returns a ScaledFunction''' + return ScaledFunction(self, scalar) + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/IndicatorBox.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/IndicatorBox.py new file mode 100644 index 0000000..df8dc89 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/IndicatorBox.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from ccpi.optimisation.functions import Function +import numpy + +class IndicatorBox(Function): + '''Box constraints indicator function. + + Calling returns 0 if argument is within the box. The prox operator is projection onto the box. + Only implements one scalar lower and one upper as constraint on all elements. Should generalise + to vectors to allow different constraints one elements. +''' + + def __init__(self,lower=-numpy.inf,upper=numpy.inf): + # Do nothing + super(IndicatorBox, self).__init__() + self.lower = lower + self.upper = upper + + + def __call__(self,x): + + if (numpy.all(x.array>=self.lower) and + numpy.all(x.array <= self.upper) ): + val = 0 + else: + val = numpy.inf + return val + + def prox(self,x,tau=None): + return (x.maximum(self.lower)).minimum(self.upper) + + def proximal(self, x, tau, out=None): + if out is None: + return self.prox(x, tau) + else: + if not x.shape == out.shape: + raise ValueError('Norm1 Incompatible size:', + x.shape, out.shape) + #(x.abs() - tau*self.gamma).maximum(0) * x.sign() + x.abs(out = out) + out.__isub__(tau*self.gamma) + out.maximum(0, out=out) + if self.sign_x is None or not x.shape == self.sign_x.shape: + self.sign_x = x.sign() + else: + x.sign(out=self.sign_x) + + out.__imul__( self.sign_x ) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py index b53f669..cf1a244 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py @@ -62,6 +62,7 @@ class KullbackLeibler(Function): if out is None: return 1 - self.b/(x + self.bnoise) else: + x.add(self.bnoise, out=out) self.b.divide(out, out=out) out.subtract(1, out=out) @@ -105,15 +106,12 @@ class KullbackLeibler(Function): z = x + tau * self.bnoise return 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) else: -# z = x + tau * self.bnoise -# out.fill( 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) ) - - tmp1 = x + tau * self.bnoise - 1 - tmp2 = tmp1 + 2 - - self.b.multiply(4*tau, out=out) - tmp1.multiply(tmp1, out=tmp1) - out += tmp1 + + z_m = x + tau * self.bnoise -1 + self.b.multiply(4*tau, out=out) + z_m.multiply(z_m, out=z_m) + out += z_m + out.sqrt(out=out) out *= -1 @@ -133,43 +131,6 @@ class KullbackLeibler(Function): return ScaledFunction(self, scalar) - - -if __name__ == '__main__': - - - from ccpi.framework import ImageGeometry - import numpy - - N, M = 2,3 - ig = ImageGeometry(N, M) - data = ImageData(numpy.random.randint(-10, 10, size=(M, N))) - x = ImageData(numpy.random.randint(-10, 10, size=(M, N))) - - bnoise = ImageData(numpy.random.randint(-10, 10, size=(M, N))) - - f = KullbackLeibler(data) - - print(f(x)) - -# numpy.random.seed(10) -# -# -# x = numpy.random.randint(-10, 10, size = (2,3)) -# b = numpy.random.randint(1, 10, size = (2,3)) -# -# ind1 = x>0 -# -# res = x[ind1] - b * numpy.log(x[ind1]) -# -## ind = x>0 -# -## y = x[ind] -# -# -# -# -# diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/L1Norm.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/L1Norm.py new file mode 100644 index 0000000..4e53f2c --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/L1Norm.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from ccpi.optimisation.functions import Function +from ccpi.optimisation.functions.ScaledFunction import ScaledFunction +from ccpi.optimisation.operators import ShrinkageOperator + + +class L1Norm(Function): + + ''' + + Class: L1Norm + + Cases: a) f(x) = ||x||_{1} + + b) f(x) = ||x - b||_{1} + + ''' + + def __init__(self, **kwargs): + + super(L1Norm, self).__init__() + self.b = kwargs.get('b',None) + + def __call__(self, x): + + ''' Evaluate L1Norm at x: f(x) ''' + + y = x + if self.b is not None: + y = x - self.b + return y.abs().sum() + + def gradient(self,x): + #TODO implement subgradient??? + return ValueError('Not Differentiable') + + def convex_conjugate(self,x): + #TODO implement Indicator infty??? + + y = 0 + if self.b is not None: + y = 0 + (self.b * x).sum() + return y + + def proximal(self, x, tau, out=None): + + # TODO implement shrinkage operator, we will need it later e.g SplitBregman + + if out is None: + if self.b is not None: + return self.b + ShrinkageOperator.__call__(self, x - self.b, tau) + else: + return ShrinkageOperator.__call__(self, x, tau) + else: + if self.b is not None: + out.fill(self.b + ShrinkageOperator.__call__(self, x - self.b, tau)) + else: + out.fill(ShrinkageOperator.__call__(self, x, tau)) + + def proximal_conjugate(self, x, tau, out=None): + + if out is None: + if self.b is not None: + return (x - tau*self.b).divide((x - tau*self.b).abs().maximum(1.0)) + else: + return x.divide(x.abs().maximum(1.0)) + else: + if self.b is not None: + out.fill((x - tau*self.b).divide((x - tau*self.b).abs().maximum(1.0))) + else: + out.fill(x.divide(x.abs().maximum(1.0)) ) + + def __rmul__(self, scalar): + return ScaledFunction(self, scalar) + + +#import numpy as np +##from ccpi.optimisation.funcs import Function +#from ccpi.optimisation.functions import Function +#from ccpi.framework import DataContainer, ImageData +# +# +############################# L1NORM FUNCTIONS ############################# +#class SimpleL1Norm(Function): +# +# def __init__(self, alpha=1): +# +# super(SimpleL1Norm, self).__init__() +# self.alpha = alpha +# +# def __call__(self, x): +# return self.alpha * x.abs().sum() +# +# def gradient(self,x): +# return ValueError('Not Differentiable') +# +# def convex_conjugate(self,x): +# return 0 +# +# def proximal(self, x, tau): +# ''' Soft Threshold''' +# return x.sign() * (x.abs() - tau * self.alpha).maximum(0) +# +# def proximal_conjugate(self, x, tau): +# return x.divide((x.abs()/self.alpha).maximum(1.0)) + +#class L1Norm(SimpleL1Norm): +# +# def __init__(self, alpha=1, **kwargs): +# +# super(L1Norm, self).__init__() +# self.alpha = alpha +# self.b = kwargs.get('b',None) +# +# def __call__(self, x): +# +# if self.b is None: +# return SimpleL1Norm.__call__(self, x) +# else: +# return SimpleL1Norm.__call__(self, x - self.b) +# +# def gradient(self, x): +# return ValueError('Not Differentiable') +# +# def convex_conjugate(self,x): +# if self.b is None: +# return SimpleL1Norm.convex_conjugate(self, x) +# else: +# return SimpleL1Norm.convex_conjugate(self, x) + (self.b * x).sum() +# +# def proximal(self, x, tau): +# +# if self.b is None: +# return SimpleL1Norm.proximal(self, x, tau) +# else: +# return self.b + SimpleL1Norm.proximal(self, x - self.b , tau) +# +# def proximal_conjugate(self, x, tau): +# +# if self.b is None: +# return SimpleL1Norm.proximal_conjugate(self, x, tau) +# else: +# return SimpleL1Norm.proximal_conjugate(self, x - tau*self.b, tau) +# + +############################################################################### + + + + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + import numpy + N, M = 40,40 + ig = ImageGeometry(N, M) + scalar = 10 + b = ig.allocate('random_int') + u = ig.allocate('random_int') + + f = L1Norm() + f_scaled = scalar * L1Norm() + + f_b = L1Norm(b=b) + f_scaled_b = scalar * L1Norm(b=b) + + # call + + a1 = f(u) + a2 = f_scaled(u) + numpy.testing.assert_equal(scalar * a1, a2) + + a3 = f_b(u) + a4 = f_scaled_b(u) + numpy.testing.assert_equal(scalar * a3, a4) + + # proximal + tau = 0.4 + b1 = f.proximal(u, tau*scalar) + b2 = f_scaled.proximal(u, tau) + + numpy.testing.assert_array_almost_equal(b1.as_array(), b2.as_array(), decimal=4) + + b3 = f_b.proximal(u, tau*scalar) + b4 = f_scaled_b.proximal(u, tau) + + z1 = b + (u-b).sign() * ((u-b).abs() - tau * scalar).maximum(0) + + numpy.testing.assert_array_almost_equal(b3.as_array(), b4.as_array(), decimal=4) +# +# #proximal conjugate +# + c1 = f_scaled.proximal_conjugate(u, tau) + c2 = u.divide((u.abs()/scalar).maximum(1.0)) + + numpy.testing.assert_array_almost_equal(c1.as_array(), c2.as_array(), decimal=4) + + c3 = f_scaled_b.proximal_conjugate(u, tau) + c4 = (u - tau*b).divide( ((u-tau*b).abs()/scalar).maximum(1.0) ) + + numpy.testing.assert_array_almost_equal(c3.as_array(), c4.as_array(), decimal=4) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py index e73da93..b77d472 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py @@ -94,21 +94,18 @@ class L2NormSquared(Function): if self.b is None: return x/(1+2*tau) else: - tmp = x.subtract(self.b) tmp /= (1+2*tau) tmp += self.b return tmp else: - out.fill(x) - if self.b is not None: - out -= self.b - out /= (1+2*tau) if self.b is not None: - out += self.b - - + x.subtract(self.b, out=out) + out /= (1+2*tau) + out += self.b + else: + x.divide((1+2*tau), out=out) def proximal_conjugate(self, x, tau, out=None): @@ -287,17 +284,3 @@ if __name__ == '__main__': numpy.testing.assert_array_almost_equal(res1.as_array(), \ res2.as_array(), decimal=4) - - - - - - - - - - - - - - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/Norm2Sq.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/Norm2Sq.py new file mode 100644 index 0000000..b553d7c --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/Norm2Sq.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from ccpi.optimisation.functions import Function +import numpy +import warnings + +# Define a class for squared 2-norm +class Norm2sq(Function): + ''' + f(x) = c*||A*x-b||_2^2 + + which has + + grad[f](x) = 2*c*A^T*(A*x-b) + + and Lipschitz constant + + L = 2*c*||A||_2^2 = 2*s1(A)^2 + + where s1(A) is the largest singular value of A. + + ''' + + def __init__(self,A,b,c=1.0,memopt=False): + super(Norm2sq, self).__init__() + + self.A = A # Should be an operator, default identity + self.b = b # Default zero DataSet? + self.c = c # Default 1. + if memopt: + try: + self.range_tmp = A.range_geometry().allocate() + self.domain_tmp = A.domain_geometry().allocate() + self.memopt = True + except NameError as ne: + warnings.warn(str(ne)) + self.memopt = False + except NotImplementedError as nie: + print (nie) + warnings.warn(str(nie)) + self.memopt = False + else: + self.memopt = False + + # Compute the Lipschitz parameter from the operator if possible + # Leave it initialised to None otherwise + try: + self.L = 2.0*self.c*(self.A.norm()**2) + except AttributeError as ae: + pass + except NotImplementedError as noe: + pass + + #def grad(self,x): + # return self.gradient(x, out=None) + + def __call__(self,x): + #return self.c* np.sum(np.square((self.A.direct(x) - self.b).ravel())) + #if out is None: + # return self.c*( ( (self.A.direct(x)-self.b)**2).sum() ) + #else: + y = self.A.direct(x) + y.__isub__(self.b) + #y.__imul__(y) + #return y.sum() * self.c + try: + return y.squared_norm() * self.c + except AttributeError as ae: + # added for compatibility with SIRF + return (y.norm()**2) * self.c + + def gradient(self, x, out = None): + if self.memopt: + #return 2.0*self.c*self.A.adjoint( self.A.direct(x) - self.b ) + + self.A.direct(x, out=self.range_tmp) + self.range_tmp -= self.b + self.A.adjoint(self.range_tmp, out=out) + #self.direct_placehold.multiply(2.0*self.c, out=out) + out *= (self.c * 2.0) + else: + return (2.0*self.c)*self.A.adjoint( self.A.direct(x) - self.b ) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/__init__.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/__init__.py new file mode 100644 index 0000000..a82ee3e --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/functions/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +from .Function import Function +from .ZeroFunction import ZeroFunction +from .L1Norm import L1Norm +from .L2NormSquared import L2NormSquared +from .ScaledFunction import ScaledFunction +from .BlockFunction import BlockFunction +from .FunctionOperatorComposition import FunctionOperatorComposition +from .MixedL21Norm import MixedL21Norm +from .IndicatorBox import IndicatorBox +from .KullbackLeibler import KullbackLeibler +from .Norm2Sq import Norm2sq diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockScaledOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockScaledOperator.py new file mode 100644 index 0000000..aeb6c53 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockScaledOperator.py @@ -0,0 +1,67 @@ +from numbers import Number +import numpy +from ccpi.optimisation.operators import ScaledOperator +import functools + +class BlockScaledOperator(ScaledOperator): + '''ScaledOperator + + A class to represent the scalar multiplication of an Operator with a scalar. + It holds an operator and a scalar. Basically it returns the multiplication + of the result of direct and adjoint of the operator with the scalar. + For the rest it behaves like the operator it holds. + + Args: + operator (Operator): a Operator or LinearOperator + scalar (Number): a scalar multiplier + Example: + The scaled operator behaves like the following: + sop = ScaledOperator(operator, scalar) + sop.direct(x) = scalar * operator.direct(x) + sop.adjoint(x) = scalar * operator.adjoint(x) + sop.norm() = operator.norm() + sop.range_geometry() = operator.range_geometry() + sop.domain_geometry() = operator.domain_geometry() + ''' + def __init__(self, operator, scalar, shape=None): + if shape is None: + shape = operator.shape + + if isinstance(scalar, (list, tuple, numpy.ndarray)): + size = functools.reduce(lambda x,y:x*y, shape, 1) + if len(scalar) != size: + raise ValueError('Scalar and operators size do not match: {}!={}' + .format(len(scalar), len(operator))) + self.scalar = scalar[:] + print ("BlockScaledOperator ", self.scalar) + elif isinstance (scalar, Number): + self.scalar = scalar + else: + raise TypeError('expected scalar to be a number of an iterable: got {}'.format(type(scalar))) + self.operator = operator + self.shape = shape + def direct(self, x, out=None): + print ("BlockScaledOperator self.scalar", self.scalar) + #print ("self.scalar", self.scalar[0]* x.get_item(0).as_array()) + return self.scalar * (self.operator.direct(x, out=out)) + def adjoint(self, x, out=None): + if self.operator.is_linear(): + return self.scalar * self.operator.adjoint(x, out=out) + else: + raise TypeError('Operator is not linear') + def norm(self): + return numpy.abs(self.scalar) * self.operator.norm() + def range_geometry(self): + return self.operator.range_geometry() + def domain_geometry(self): + return self.operator.domain_geometry() + @property + def T(self): + '''Return the transposed of self''' + #print ("transpose before" , self.shape) + #shape = (self.shape[1], self.shape[0]) + ##self.shape = shape + ##self.operator.shape = shape + #print ("transpose" , shape) + #return self + return type(self)(self.operator.T, self.scalar) \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py new file mode 100644 index 0000000..387fb4b --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py @@ -0,0 +1,374 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 1 22:51:17 2019 + +@author: evangelos +""" + +from ccpi.optimisation.operators import LinearOperator +from ccpi.optimisation.ops import PowerMethodNonsquare +from ccpi.framework import ImageData, BlockDataContainer +import numpy as np + +class FiniteDiff(LinearOperator): + + # Works for Neum/Symmetric & periodic boundary conditions + # TODO add central differences??? + # TODO not very well optimised, too many conditions + # TODO add discretisation step, should get that from imageGeometry + + # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] + # Grad_order = ['channels', 'direction_y', 'direction_x'] + # Grad_order = ['direction_z', 'direction_y', 'direction_x'] + # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] + + def __init__(self, gm_domain, gm_range=None, direction=0, bnd_cond = 'Neumann'): + '''''' + super(FiniteDiff, self).__init__() + '''FIXME: domain and range should be geometries''' + self.gm_domain = gm_domain + self.gm_range = gm_range + + self.direction = direction + self.bnd_cond = bnd_cond + + # Domain Geometry = Range Geometry if not stated + if self.gm_range is None: + self.gm_range = self.gm_domain + # check direction and "length" of geometry + if self.direction + 1 > len(self.gm_domain.shape): + raise ValueError('Gradient directions more than geometry domain') + + #self.voxel_size = kwargs.get('voxel_size',1) + # this wrongly assumes a homogeneous voxel size + self.voxel_size = self.gm_domain.voxel_size_x + + + def direct(self, x, out=None): + + x_asarr = x.as_array() + x_sz = len(x.shape) + + if out is None: + out = np.zeros_like(x_asarr) + fd_arr = out + else: + fd_arr = out.as_array() +# fd_arr[:]=0 + +# if out is None: +# out = self.gm_domain.allocate().as_array() +# +# fd_arr = out.as_array() +# fd_arr = self.gm_domain.allocate().as_array() + + ######################## Direct for 2D ############################### + if x_sz == 2: + + if self.direction == 1: + + np.subtract( x_asarr[:,1:], x_asarr[:,0:-1], out = fd_arr[:,0:-1] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0], x_asarr[:,-1], out = fd_arr[:,-1] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 0: + + np.subtract( x_asarr[1:], x_asarr[0:-1], out = fd_arr[0:-1,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:], x_asarr[-1,:], out = fd_arr[-1,:] ) + else: + raise ValueError('No valid boundary conditions') + + ######################## Direct for 3D ############################### + elif x_sz == 3: + + if self.direction == 0: + + np.subtract( x_asarr[1:,:,:], x_asarr[0:-1,:,:], out = fd_arr[0:-1,:,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:,:], x_asarr[-1,:,:], out = fd_arr[-1,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 1: + + np.subtract( x_asarr[:,1:,:], x_asarr[:,0:-1,:], out = fd_arr[:,0:-1,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0,:], x_asarr[:,-1,:], out = fd_arr[:,-1,:] ) + else: + raise ValueError('No valid boundary conditions') + + + if self.direction == 2: + + np.subtract( x_asarr[:,:,1:], x_asarr[:,:,0:-1], out = fd_arr[:,:,0:-1] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,0], x_asarr[:,:,-1], out = fd_arr[:,:,-1] ) + else: + raise ValueError('No valid boundary conditions') + + ######################## Direct for 4D ############################### + elif x_sz == 4: + + if self.direction == 0: + np.subtract( x_asarr[1:,:,:,:], x_asarr[0:-1,:,:,:], out = fd_arr[0:-1,:,:,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:,:,:], x_asarr[-1,:,:,:], out = fd_arr[-1,:,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 1: + np.subtract( x_asarr[:,1:,:,:], x_asarr[:,0:-1,:,:], out = fd_arr[:,0:-1,:,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0,:,:], x_asarr[:,-1,:,:], out = fd_arr[:,-1,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 2: + np.subtract( x_asarr[:,:,1:,:], x_asarr[:,:,0:-1,:], out = fd_arr[:,:,0:-1,:] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,0,:], x_asarr[:,:,-1,:], out = fd_arr[:,:,-1,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 3: + np.subtract( x_asarr[:,:,:,1:], x_asarr[:,:,:,0:-1], out = fd_arr[:,:,:,0:-1] ) + + if self.bnd_cond == 'Neumann': + pass + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,:,0], x_asarr[:,:,:,-1], out = fd_arr[:,:,:,-1] ) + else: + raise ValueError('No valid boundary conditions') + + else: + raise NotImplementedError + +# res = out #/self.voxel_size + return type(x)(out) + + + def adjoint(self, x, out=None): + + x_asarr = x.as_array() + #x_asarr = x + x_sz = len(x.shape) + + if out is None: + out = np.zeros_like(x_asarr) + fd_arr = out + else: + fd_arr = out.as_array() + +# if out is None: +# out = self.gm_domain.allocate().as_array() +# fd_arr = out +# else: +# fd_arr = out.as_array() +## fd_arr = self.gm_domain.allocate().as_array() + + ######################## Adjoint for 2D ############################### + if x_sz == 2: + + if self.direction == 1: + + np.subtract( x_asarr[:,1:], x_asarr[:,0:-1], out = fd_arr[:,1:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,0], 0, out = fd_arr[:,0] ) + np.subtract( -x_asarr[:,-2], 0, out = fd_arr[:,-1] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0], x_asarr[:,-1], out = fd_arr[:,0] ) + + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 0: + + np.subtract( x_asarr[1:,:], x_asarr[0:-1,:], out = fd_arr[1:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[0,:], 0, out = fd_arr[0,:] ) + np.subtract( -x_asarr[-2,:], 0, out = fd_arr[-1,:] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:], x_asarr[-1,:], out = fd_arr[0,:] ) + + else: + raise ValueError('No valid boundary conditions') + + ######################## Adjoint for 3D ############################### + elif x_sz == 3: + + if self.direction == 0: + + np.subtract( x_asarr[1:,:,:], x_asarr[0:-1,:,:], out = fd_arr[1:,:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[0,:,:], 0, out = fd_arr[0,:,:] ) + np.subtract( -x_asarr[-2,:,:], 0, out = fd_arr[-1,:,:] ) + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:,:], x_asarr[-1,:,:], out = fd_arr[0,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 1: + np.subtract( x_asarr[:,1:,:], x_asarr[:,0:-1,:], out = fd_arr[:,1:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,0,:], 0, out = fd_arr[:,0,:] ) + np.subtract( -x_asarr[:,-2,:], 0, out = fd_arr[:,-1,:] ) + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0,:], x_asarr[:,-1,:], out = fd_arr[:,0,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 2: + np.subtract( x_asarr[:,:,1:], x_asarr[:,:,0:-1], out = fd_arr[:,:,1:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,:,0], 0, out = fd_arr[:,:,0] ) + np.subtract( -x_asarr[:,:,-2], 0, out = fd_arr[:,:,-1] ) + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,0], x_asarr[:,:,-1], out = fd_arr[:,:,0] ) + else: + raise ValueError('No valid boundary conditions') + + ######################## Adjoint for 4D ############################### + elif x_sz == 4: + + if self.direction == 0: + np.subtract( x_asarr[1:,:,:,:], x_asarr[0:-1,:,:,:], out = fd_arr[1:,:,:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[0,:,:,:], 0, out = fd_arr[0,:,:,:] ) + np.subtract( -x_asarr[-2,:,:,:], 0, out = fd_arr[-1,:,:,:] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[0,:,:,:], x_asarr[-1,:,:,:], out = fd_arr[0,:,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 1: + np.subtract( x_asarr[:,1:,:,:], x_asarr[:,0:-1,:,:], out = fd_arr[:,1:,:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,0,:,:], 0, out = fd_arr[:,0,:,:] ) + np.subtract( -x_asarr[:,-2,:,:], 0, out = fd_arr[:,-1,:,:] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,0,:,:], x_asarr[:,-1,:,:], out = fd_arr[:,0,:,:] ) + else: + raise ValueError('No valid boundary conditions') + + + if self.direction == 2: + np.subtract( x_asarr[:,:,1:,:], x_asarr[:,:,0:-1,:], out = fd_arr[:,:,1:,:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,:,0,:], 0, out = fd_arr[:,:,0,:] ) + np.subtract( -x_asarr[:,:,-2,:], 0, out = fd_arr[:,:,-1,:] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,0,:], x_asarr[:,:,-1,:], out = fd_arr[:,:,0,:] ) + else: + raise ValueError('No valid boundary conditions') + + if self.direction == 3: + np.subtract( x_asarr[:,:,:,1:], x_asarr[:,:,:,0:-1], out = fd_arr[:,:,:,1:] ) + + if self.bnd_cond == 'Neumann': + np.subtract( x_asarr[:,:,:,0], 0, out = fd_arr[:,:,:,0] ) + np.subtract( -x_asarr[:,:,:,-2], 0, out = fd_arr[:,:,:,-1] ) + + elif self.bnd_cond == 'Periodic': + np.subtract( x_asarr[:,:,:,0], x_asarr[:,:,:,-1], out = fd_arr[:,:,:,0] ) + else: + raise ValueError('No valid boundary conditions') + + else: + raise NotImplementedError + + out *= -1 #/self.voxel_size + return type(x)(out) + + def range_geometry(self): + '''Returns the range geometry''' + return self.gm_range + + def domain_geometry(self): + '''Returns the domain geometry''' + return self.gm_domain + + def norm(self): + x0 = self.gm_domain.allocate() + x0.fill( np.random.random_sample(x0.shape) ) + self.s1, sall, svec = PowerMethodNonsquare(self, 25, x0) + return self.s1 + + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + import numpy + + N, M = 2, 3 + + ig = ImageGeometry(N, M) + + + FD = FiniteDiff(ig, direction = 0, bnd_cond = 'Neumann') + u = FD.domain_geometry().allocate('random_int') + + + res = FD.domain_geometry().allocate() + FD.direct(u, out=res) + + z = FD.direct(u) + print(z.as_array(), res.as_array()) + + for i in range(10): + + z1 = FD.direct(u) + FD.direct(u, out=res) + numpy.testing.assert_array_almost_equal(z1.as_array(), \ + res.as_array(), decimal=4) + + + + + + +# w = G.range_geometry().allocate('random_int') + + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py index e0b8a32..6ffaf70 100644 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py @@ -111,7 +111,7 @@ class Gradient(LinearOperator): return BlockDataContainer(*mat) - def sum_abs_row(self): + def sum_abs_col(self): tmp = self.gm_range.allocate() res = self.gm_domain.allocate() @@ -120,7 +120,7 @@ class Gradient(LinearOperator): res += spMat.sum_abs_row() return res - def sum_abs_col(self): + def sum_abs_row(self): tmp = self.gm_range.allocate() res = [] diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/IdentityOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/IdentityOperator.py new file mode 100644 index 0000000..a853b8d --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/IdentityOperator.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 6 19:30:51 2019 + +@author: evangelos +""" + +from ccpi.optimisation.operators import LinearOperator +import scipy.sparse as sp +import numpy as np +from ccpi.framework import ImageData + + +class Identity(LinearOperator): + + def __init__(self, gm_domain, gm_range=None): + + self.gm_domain = gm_domain + self.gm_range = gm_range + if self.gm_range is None: + self.gm_range = self.gm_domain + + super(Identity, self).__init__() + + def direct(self,x,out=None): + if out is None: + return x.copy() + else: + out.fill(x) + + def adjoint(self,x, out=None): + if out is None: + return x.copy() + else: + out.fill(x) + + def norm(self): + return 1.0 + + def domain_geometry(self): + return self.gm_domain + + def range_geometry(self): + return self.gm_range + + def matrix(self): + + return sp.eye(np.prod(self.gm_domain.shape)) + + def sum_abs_row(self): + + return self.gm_range.allocate(1)#ImageData(np.array(np.reshape(abs(self.matrix()).sum(axis=0), self.gm_domain.shape, 'F'))) + + def sum_abs_col(self): + + return self.gm_domain.allocate(1)#ImageData(np.array(np.reshape(abs(self.matrix()).sum(axis=1), self.gm_domain.shape, 'F'))) + + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + + M, N = 2, 3 + ig = ImageGeometry(M, N) + arr = ig.allocate('random_int') + + Id = Identity(ig) + d = Id.matrix() + print(d.toarray()) + + d1 = Id.sum_abs_col() + print(d1.as_array()) + + + + + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/Operator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/Operator.py new file mode 100644 index 0000000..2d2089b --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/Operator.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Mar 5 15:55:56 2019 + +@author: ofn77899 +""" +from ccpi.optimisation.operators.ScaledOperator import ScaledOperator + +class Operator(object): + '''Operator that maps from a space X -> Y''' + def is_linear(self): + '''Returns if the operator is linear''' + return False + def direct(self,x, out=None): + '''Returns the application of the Operator on x''' + raise NotImplementedError + def norm(self): + '''Returns the norm of the Operator''' + raise NotImplementedError + def range_geometry(self): + '''Returns the range of the Operator: Y space''' + raise NotImplementedError + def domain_geometry(self): + '''Returns the domain of the Operator: X space''' + raise NotImplementedError + def __rmul__(self, scalar): + '''Defines the multiplication by a scalar on the left + + returns a ScaledOperator''' + return ScaledOperator(self, scalar) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ScaledOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ScaledOperator.py new file mode 100644 index 0000000..ba0049e --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ScaledOperator.py @@ -0,0 +1,51 @@ +from numbers import Number +import numpy + +class ScaledOperator(object): + '''ScaledOperator + A class to represent the scalar multiplication of an Operator with a scalar. + It holds an operator and a scalar. Basically it returns the multiplication + of the result of direct and adjoint of the operator with the scalar. + For the rest it behaves like the operator it holds. + Args: + operator (Operator): a Operator or LinearOperator + scalar (Number): a scalar multiplier + Example: + The scaled operator behaves like the following: + sop = ScaledOperator(operator, scalar) + sop.direct(x) = scalar * operator.direct(x) + sop.adjoint(x) = scalar * operator.adjoint(x) + sop.norm() = operator.norm() + sop.range_geometry() = operator.range_geometry() + sop.domain_geometry() = operator.domain_geometry() + ''' + def __init__(self, operator, scalar): + super(ScaledOperator, self).__init__() + if not isinstance (scalar, Number): + raise TypeError('expected scalar: got {}'.format(type(scalar))) + self.scalar = scalar + self.operator = operator + def direct(self, x, out=None): + if out is None: + return self.scalar * self.operator.direct(x, out=out) + else: + self.operator.direct(x, out=out) + out *= self.scalar + def adjoint(self, x, out=None): + if self.operator.is_linear(): + if out is None: + return self.scalar * self.operator.adjoint(x, out=out) + else: + self.operator.adjoint(x, out=out) + out *= self.scalar + else: + raise TypeError('Operator is not linear') + def norm(self): + return numpy.abs(self.scalar) * self.operator.norm() + def range_geometry(self): + return self.operator.range_geometry() + def domain_geometry(self): + return self.operator.domain_geometry() + def is_linear(self): + return self.operator.is_linear() + diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ShrinkageOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ShrinkageOperator.py new file mode 100644 index 0000000..f47c655 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ShrinkageOperator.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 6 19:30:51 2019 + +@author: evangelos +""" + +from ccpi.framework import DataContainer + +class ShrinkageOperator(): + + def __init__(self): + pass + + def __call__(self, x, tau, out=None): + + return x.sign() * (x.abs() - tau).maximum(0) + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/SparseFiniteDiff.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/SparseFiniteDiff.py new file mode 100644 index 0000000..c5c2ec9 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/optimisation/operators/SparseFiniteDiff.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Tue Apr 2 14:06:15 2019 + +@author: vaggelis +""" + +import scipy.sparse as sp +import numpy as np +from ccpi.framework import ImageData + +class SparseFiniteDiff(): + + def __init__(self, gm_domain, gm_range=None, direction=0, bnd_cond = 'Neumann'): + + super(SparseFiniteDiff, self).__init__() + self.gm_domain = gm_domain + self.gm_range = gm_range + self.direction = direction + self.bnd_cond = bnd_cond + + if self.gm_range is None: + self.gm_range = self.gm_domain + + self.get_dims = [i for i in gm_domain.shape] + + if self.direction + 1 > len(self.gm_domain.shape): + raise ValueError('Gradient directions more than geometry domain') + + def matrix(self): + + i = self.direction + + mat = sp.spdiags(np.vstack([-np.ones((1,self.get_dims[i])),np.ones((1,self.get_dims[i]))]), [0,1], self.get_dims[i], self.get_dims[i], format = 'lil') + + if self.bnd_cond == 'Neumann': + mat[-1,:] = 0 + elif self.bnd_cond == 'Periodic': + mat[-1,0] = 1 + + tmpGrad = mat if i == 0 else sp.eye(self.get_dims[0]) + + for j in range(1, self.gm_domain.length): + + tmpGrad = sp.kron(mat, tmpGrad ) if j == i else sp.kron(sp.eye(self.get_dims[j]), tmpGrad ) + + return tmpGrad + + def T(self): + return self.matrix().T + + def direct(self, x): + + x_asarr = x.as_array() + res = np.reshape( self.matrix() * x_asarr.flatten('F'), self.gm_domain.shape, 'F') + return type(x)(res) + + def adjoint(self, x): + + x_asarr = x.as_array() + res = np.reshape( self.matrix().T * x_asarr.flatten('F'), self.gm_domain.shape, 'F') + return type(x)(res) + + def sum_abs_row(self): + + res = np.array(np.reshape(abs(self.matrix()).sum(axis=0), self.gm_domain.shape, 'F')) + #res[res==0]=0 + return ImageData(res) + + def sum_abs_col(self): + + res = np.array(np.reshape(abs(self.matrix()).sum(axis=1), self.gm_domain.shape, 'F') ) + #res[res==0]=0 + return ImageData(res) + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + from ccpi.optimisation.operators import FiniteDiff + + # 2D + M, N= 2, 3 + ig = ImageGeometry(M, N) + arr = ig.allocate('random_int') + + for i in [0,1]: + + # Neumann + sFD_neum = SparseFiniteDiff(ig, direction=i, bnd_cond='Neumann') + G_neum = FiniteDiff(ig, direction=i, bnd_cond='Neumann') + + # Periodic + sFD_per = SparseFiniteDiff(ig, direction=i, bnd_cond='Periodic') + G_per = FiniteDiff(ig, direction=i, bnd_cond='Periodic') + + u_neum_direct = G_neum.direct(arr) + u_neum_sp_direct = sFD_neum.direct(arr) + np.testing.assert_array_almost_equal(u_neum_direct.as_array(), u_neum_sp_direct.as_array(), decimal=4) + + u_neum_adjoint = G_neum.adjoint(arr) + u_neum_sp_adjoint = sFD_neum.adjoint(arr) + np.testing.assert_array_almost_equal(u_neum_adjoint.as_array(), u_neum_sp_adjoint.as_array(), decimal=4) + + u_per_direct = G_neum.direct(arr) + u_per_sp_direct = sFD_neum.direct(arr) + np.testing.assert_array_almost_equal(u_per_direct.as_array(), u_per_sp_direct.as_array(), decimal=4) + + u_per_adjoint = G_per.adjoint(arr) + u_per_sp_adjoint = sFD_per.adjoint(arr) + np.testing.assert_array_almost_equal(u_per_adjoint.as_array(), u_per_sp_adjoint.as_array(), decimal=4) + + # 3D + M, N, K = 2, 3, 4 + ig3D = ImageGeometry(M, N, K) + arr3D = ig3D.allocate('random_int') + + for i in [0,1,2]: + + # Neumann + sFD_neum3D = SparseFiniteDiff(ig3D, direction=i, bnd_cond='Neumann') + G_neum3D = FiniteDiff(ig3D, direction=i, bnd_cond='Neumann') + + # Periodic + sFD_per3D = SparseFiniteDiff(ig3D, direction=i, bnd_cond='Periodic') + G_per3D = FiniteDiff(ig3D, direction=i, bnd_cond='Periodic') + + u_neum_direct3D = G_neum3D.direct(arr3D) + u_neum_sp_direct3D = sFD_neum3D.direct(arr3D) + np.testing.assert_array_almost_equal(u_neum_direct3D.as_array(), u_neum_sp_direct3D.as_array(), decimal=4) + + u_neum_adjoint3D = G_neum3D.adjoint(arr3D) + u_neum_sp_adjoint3D = sFD_neum3D.adjoint(arr3D) + np.testing.assert_array_almost_equal(u_neum_adjoint3D.as_array(), u_neum_sp_adjoint3D.as_array(), decimal=4) + + u_per_direct3D = G_neum3D.direct(arr3D) + u_per_sp_direct3D = sFD_neum3D.direct(arr3D) + np.testing.assert_array_almost_equal(u_per_direct3D.as_array(), u_per_sp_direct3D.as_array(), decimal=4) + + u_per_adjoint3D = G_per3D.adjoint(arr3D) + u_per_sp_adjoint3D = sFD_per3D.adjoint(arr3D) + np.testing.assert_array_almost_equal(u_per_adjoint3D.as_array(), u_per_sp_adjoint3D.as_array(), decimal=4) + + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/processors/CenterOfRotationFinder.py b/Wrappers/Python/build/lib/ccpi/processors/CenterOfRotationFinder.py new file mode 100644 index 0000000..936dc05 --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/processors/CenterOfRotationFinder.py @@ -0,0 +1,408 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +from ccpi.framework import DataProcessor, DataContainer, AcquisitionData,\ + AcquisitionGeometry, ImageGeometry, ImageData +import numpy +from scipy import ndimage + +class CenterOfRotationFinder(DataProcessor): + '''Processor to find the center of rotation in a parallel beam experiment + + This processor read in a AcquisitionDataSet and finds the center of rotation + based on Nghia Vo's method. https://doi.org/10.1364/OE.22.019078 + + Input: AcquisitionDataSet + + Output: float. center of rotation in pixel coordinate + ''' + + def __init__(self): + kwargs = { + + } + + #DataProcessor.__init__(self, **kwargs) + super(CenterOfRotationFinder, self).__init__(**kwargs) + + def check_input(self, dataset): + if dataset.number_of_dimensions == 3: + if dataset.geometry.geom_type == 'parallel': + return True + else: + raise ValueError('{0} is suitable only for parallel beam geometry'\ + .format(self.__class__.__name__)) + else: + raise ValueError("Expected input dimensions is 3, got {0}"\ + .format(dataset.number_of_dimensions)) + + + # ######################################################################### + # Copyright (c) 2015, UChicago Argonne, LLC. All rights reserved. # + # # + # Copyright 2015. UChicago Argonne, LLC. This software was produced # + # under U.S. Government contract DE-AC02-06CH11357 for Argonne National # + # Laboratory (ANL), which is operated by UChicago Argonne, LLC for the # + # U.S. Department of Energy. The U.S. Government has rights to use, # + # reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR # + # UChicago Argonne, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR # + # ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is # + # modified to produce derivative works, such modified software should # + # be clearly marked, so as not to confuse it with the version available # + # from ANL. # + # # + # Additionally, redistribution and use in source and binary forms, with # + # or without modification, are permitted provided that the following # + # conditions are met: # + # # + # * Redistributions of source code must retain the above copyright # + # notice, this list of conditions and the following disclaimer. # + # # + # * Redistributions in binary form must reproduce the above copyright # + # notice, this list of conditions and the following disclaimer in # + # the documentation and/or other materials provided with the # + # distribution. # + # # + # * Neither the name of UChicago Argonne, LLC, Argonne National # + # Laboratory, ANL, the U.S. Government, nor the names of its # + # contributors may be used to endorse or promote products derived # + # from this software without specific prior written permission. # + # # + # THIS SOFTWARE IS PROVIDED BY UChicago Argonne, LLC AND CONTRIBUTORS # + # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # + # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # + # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UChicago # + # Argonne, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # + # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # + # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # + # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # + # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # + # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # + # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # + # POSSIBILITY OF SUCH DAMAGE. # + # ######################################################################### + + @staticmethod + def as_ndarray(arr, dtype=None, copy=False): + if not isinstance(arr, numpy.ndarray): + arr = numpy.array(arr, dtype=dtype, copy=copy) + return arr + + @staticmethod + def as_dtype(arr, dtype, copy=False): + if not arr.dtype == dtype: + arr = numpy.array(arr, dtype=dtype, copy=copy) + return arr + + @staticmethod + def as_float32(arr): + arr = CenterOfRotationFinder.as_ndarray(arr, numpy.float32) + return CenterOfRotationFinder.as_dtype(arr, numpy.float32) + + + + + @staticmethod + def find_center_vo(tomo, ind=None, smin=-40, smax=40, srad=10, step=0.5, + ratio=2., drop=20): + """ + Find rotation axis location using Nghia Vo's method. :cite:`Vo:14`. + + Parameters + ---------- + tomo : ndarray + 3D tomographic data. + ind : int, optional + Index of the slice to be used for reconstruction. + smin, smax : int, optional + Reference to the horizontal center of the sinogram. + srad : float, optional + Fine search radius. + step : float, optional + Step of fine searching. + ratio : float, optional + The ratio between the FOV of the camera and the size of object. + It's used to generate the mask. + drop : int, optional + Drop lines around vertical center of the mask. + + Returns + ------- + float + Rotation axis location. + + Notes + ----- + The function may not yield a correct estimate, if: + + - the sample size is bigger than the field of view of the camera. + In this case the ``ratio`` argument need to be set larger + than the default of 2.0. + + - there is distortion in the imaging hardware. If there's + no correction applied, the center of the projection image may + yield a better estimate. + + - the sample contrast is weak. Paganin's filter need to be applied + to overcome this. + + - the sample was changed during the scan. + """ + tomo = CenterOfRotationFinder.as_float32(tomo) + + if ind is None: + ind = tomo.shape[1] // 2 + _tomo = tomo[:, ind, :] + + + + # Reduce noise by smooth filters. Use different filters for coarse and fine search + _tomo_cs = ndimage.filters.gaussian_filter(_tomo, (3, 1)) + _tomo_fs = ndimage.filters.median_filter(_tomo, (2, 2)) + + # Coarse and fine searches for finding the rotation center. + if _tomo.shape[0] * _tomo.shape[1] > 4e6: # If data is large (>2kx2k) + #_tomo_coarse = downsample(numpy.expand_dims(_tomo_cs,1), level=2)[:, 0, :] + #init_cen = _search_coarse(_tomo_coarse, smin, smax, ratio, drop) + #fine_cen = _search_fine(_tomo_fs, srad, step, init_cen*4, ratio, drop) + init_cen = CenterOfRotationFinder._search_coarse(_tomo_cs, smin, + smax, ratio, drop) + fine_cen = CenterOfRotationFinder._search_fine(_tomo_fs, srad, + step, init_cen, + ratio, drop) + else: + init_cen = CenterOfRotationFinder._search_coarse(_tomo_cs, + smin, smax, + ratio, drop) + fine_cen = CenterOfRotationFinder._search_fine(_tomo_fs, srad, + step, init_cen, + ratio, drop) + + #logger.debug('Rotation center search finished: %i', fine_cen) + return fine_cen + + + @staticmethod + def _search_coarse(sino, smin, smax, ratio, drop): + """ + Coarse search for finding the rotation center. + """ + (Nrow, Ncol) = sino.shape + centerfliplr = (Ncol - 1.0) / 2.0 + + # Copy the sinogram and flip left right, the purpose is to + # make a full [0;2Pi] sinogram + _copy_sino = numpy.fliplr(sino[1:]) + + # This image is used for compensating the shift of sinogram 2 + temp_img = numpy.zeros((Nrow - 1, Ncol), dtype='float32') + temp_img[:] = sino[-1] + + # Start coarse search in which the shift step is 1 + listshift = numpy.arange(smin, smax + 1) + listmetric = numpy.zeros(len(listshift), dtype='float32') + mask = CenterOfRotationFinder._create_mask(2 * Nrow - 1, Ncol, + 0.5 * ratio * Ncol, drop) + for i in listshift: + _sino = numpy.roll(_copy_sino, i, axis=1) + if i >= 0: + _sino[:, 0:i] = temp_img[:, 0:i] + else: + _sino[:, i:] = temp_img[:, i:] + listmetric[i - smin] = numpy.sum(numpy.abs(numpy.fft.fftshift( + #pyfftw.interfaces.numpy_fft.fft2( + # numpy.vstack((sino, _sino))) + numpy.fft.fft2(numpy.vstack((sino, _sino))) + )) * mask) + minpos = numpy.argmin(listmetric) + return centerfliplr + listshift[minpos] / 2.0 + + @staticmethod + def _search_fine(sino, srad, step, init_cen, ratio, drop): + """ + Fine search for finding the rotation center. + """ + Nrow, Ncol = sino.shape + centerfliplr = (Ncol + 1.0) / 2.0 - 1.0 + # Use to shift the sinogram 2 to the raw CoR. + shiftsino = numpy.int16(2 * (init_cen - centerfliplr)) + _copy_sino = numpy.roll(numpy.fliplr(sino[1:]), shiftsino, axis=1) + if init_cen <= centerfliplr: + lefttake = numpy.int16(numpy.ceil(srad + 1)) + righttake = numpy.int16(numpy.floor(2 * init_cen - srad - 1)) + else: + lefttake = numpy.int16(numpy.ceil( + init_cen - (Ncol - 1 - init_cen) + srad + 1)) + righttake = numpy.int16(numpy.floor(Ncol - 1 - srad - 1)) + Ncol1 = righttake - lefttake + 1 + mask = CenterOfRotationFinder._create_mask(2 * Nrow - 1, Ncol1, + 0.5 * ratio * Ncol, drop) + numshift = numpy.int16((2 * srad) / step) + 1 + listshift = numpy.linspace(-srad, srad, num=numshift) + listmetric = numpy.zeros(len(listshift), dtype='float32') + factor1 = numpy.mean(sino[-1, lefttake:righttake]) + num1 = 0 + for i in listshift: + _sino = ndimage.interpolation.shift( + _copy_sino, (0, i), prefilter=False) + factor2 = numpy.mean(_sino[0,lefttake:righttake]) + _sino = _sino * factor1 / factor2 + sinojoin = numpy.vstack((sino, _sino)) + listmetric[num1] = numpy.sum(numpy.abs(numpy.fft.fftshift( + #pyfftw.interfaces.numpy_fft.fft2( + # sinojoin[:, lefttake:righttake + 1]) + numpy.fft.fft2(sinojoin[:, lefttake:righttake + 1]) + )) * mask) + num1 = num1 + 1 + minpos = numpy.argmin(listmetric) + return init_cen + listshift[minpos] / 2.0 + + @staticmethod + def _create_mask(nrow, ncol, radius, drop): + du = 1.0 / ncol + dv = (nrow - 1.0) / (nrow * 2.0 * numpy.pi) + centerrow = numpy.ceil(nrow / 2) - 1 + centercol = numpy.ceil(ncol / 2) - 1 + # added by Edoardo Pasca + centerrow = int(centerrow) + centercol = int(centercol) + mask = numpy.zeros((nrow, ncol), dtype='float32') + for i in range(nrow): + num1 = numpy.round(((i - centerrow) * dv / radius) / du) + (p1, p2) = numpy.int16(numpy.clip(numpy.sort( + (-num1 + centercol, num1 + centercol)), 0, ncol - 1)) + mask[i, p1:p2 + 1] = numpy.ones(p2 - p1 + 1, dtype='float32') + if drop < centerrow: + mask[centerrow - drop:centerrow + drop + 1, + :] = numpy.zeros((2 * drop + 1, ncol), dtype='float32') + mask[:,centercol-1:centercol+2] = numpy.zeros((nrow, 3), dtype='float32') + return mask + + def process(self, out=None): + + projections = self.get_input() + + cor = CenterOfRotationFinder.find_center_vo(projections.as_array()) + + return cor + + +class AcquisitionDataPadder(DataProcessor): + '''Normalization based on flat and dark + + This processor read in a AcquisitionData and normalises it based on + the instrument reading with and without incident photons or neutrons. + + Input: AcquisitionData + Parameter: 2D projection with flat field (or stack) + 2D projection with dark field (or stack) + Output: AcquisitionDataSetn + ''' + + def __init__(self, + center_of_rotation = None, + acquisition_geometry = None, + pad_value = 1e-5): + kwargs = { + 'acquisition_geometry' : acquisition_geometry, + 'center_of_rotation' : center_of_rotation, + 'pad_value' : pad_value + } + + super(AcquisitionDataPadder, self).__init__(**kwargs) + + def check_input(self, dataset): + if self.acquisition_geometry is None: + self.acquisition_geometry = dataset.geometry + if dataset.number_of_dimensions == 3: + return True + else: + raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ + .format(dataset.number_of_dimensions)) + + def process(self, out=None): + projections = self.get_input() + w = projections.get_dimension_size('horizontal') + delta = w - 2 * self.center_of_rotation + + padded_width = int ( + numpy.ceil(abs(delta)) + w + ) + delta_pix = padded_width - w + + voxel_per_pixel = 1 + geom = pbalg.pb_setup_geometry_from_acquisition(projections.as_array(), + self.acquisition_geometry.angles, + self.center_of_rotation, + voxel_per_pixel ) + + padded_geometry = self.acquisition_geometry.clone() + + padded_geometry.pixel_num_h = geom['n_h'] + padded_geometry.pixel_num_v = geom['n_v'] + + delta_pix_h = padded_geometry.pixel_num_h - self.acquisition_geometry.pixel_num_h + delta_pix_v = padded_geometry.pixel_num_v - self.acquisition_geometry.pixel_num_v + + if delta_pix_h == 0: + delta_pix_h = delta_pix + padded_geometry.pixel_num_h = padded_width + #initialize a new AcquisitionData with values close to 0 + out = AcquisitionData(geometry=padded_geometry) + out = out + self.pad_value + + + #pad in the horizontal-vertical plane -> slice on angles + if delta > 0: + #pad left of middle + command = "out.array[" + for i in range(out.number_of_dimensions): + if out.dimension_labels[i] == 'horizontal': + value = '{0}:{1}'.format(delta_pix_h, delta_pix_h+w) + command = command + str(value) + else: + if out.dimension_labels[i] == 'vertical' : + value = '{0}:'.format(delta_pix_v) + command = command + str(value) + else: + command = command + ":" + if i < out.number_of_dimensions -1: + command = command + ',' + command = command + '] = projections.array' + #print (command) + else: + #pad right of middle + command = "out.array[" + for i in range(out.number_of_dimensions): + if out.dimension_labels[i] == 'horizontal': + value = '{0}:{1}'.format(0, w) + command = command + str(value) + else: + if out.dimension_labels[i] == 'vertical' : + value = '{0}:'.format(delta_pix_v) + command = command + str(value) + else: + command = command + ":" + if i < out.number_of_dimensions -1: + command = command + ',' + command = command + '] = projections.array' + #print (command) + #cleaned = eval(command) + exec(command) + return out \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/processors/Normalizer.py b/Wrappers/Python/build/lib/ccpi/processors/Normalizer.py new file mode 100644 index 0000000..da65e5c --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/processors/Normalizer.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +from ccpi.framework import DataProcessor, DataContainer, AcquisitionData,\ + AcquisitionGeometry, ImageGeometry, ImageData +import numpy + +class Normalizer(DataProcessor): + '''Normalization based on flat and dark + + This processor read in a AcquisitionData and normalises it based on + the instrument reading with and without incident photons or neutrons. + + Input: AcquisitionData + Parameter: 2D projection with flat field (or stack) + 2D projection with dark field (or stack) + Output: AcquisitionDataSetn + ''' + + def __init__(self, flat_field = None, dark_field = None, tolerance = 1e-5): + kwargs = { + 'flat_field' : flat_field, + 'dark_field' : dark_field, + # very small number. Used when there is a division by zero + 'tolerance' : tolerance + } + + #DataProcessor.__init__(self, **kwargs) + super(Normalizer, self).__init__(**kwargs) + if not flat_field is None: + self.set_flat_field(flat_field) + if not dark_field is None: + self.set_dark_field(dark_field) + + def check_input(self, dataset): + if dataset.number_of_dimensions == 3 or\ + dataset.number_of_dimensions == 2: + return True + else: + raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ + .format(dataset.number_of_dimensions)) + + def set_dark_field(self, df): + if type(df) is numpy.ndarray: + if len(numpy.shape(df)) == 3: + raise ValueError('Dark Field should be 2D') + elif len(numpy.shape(df)) == 2: + self.dark_field = df + elif issubclass(type(df), DataContainer): + self.dark_field = self.set_dark_field(df.as_array()) + + def set_flat_field(self, df): + if type(df) is numpy.ndarray: + if len(numpy.shape(df)) == 3: + raise ValueError('Flat Field should be 2D') + elif len(numpy.shape(df)) == 2: + self.flat_field = df + elif issubclass(type(df), DataContainer): + self.flat_field = self.set_flat_field(df.as_array()) + + @staticmethod + def normalize_projection(projection, flat, dark, tolerance): + a = (projection - dark) + b = (flat-dark) + with numpy.errstate(divide='ignore', invalid='ignore'): + c = numpy.true_divide( a, b ) + c[ ~ numpy.isfinite( c )] = tolerance # set to not zero if 0/0 + return c + + @staticmethod + def estimate_normalised_error(projection, flat, dark, delta_flat, delta_dark): + '''returns the estimated relative error of the normalised projection + + n = (projection - dark) / (flat - dark) + Dn/n = (flat-dark + projection-dark)/((flat-dark)*(projection-dark))*(Df/f + Dd/d) + ''' + a = (projection - dark) + b = (flat-dark) + df = delta_flat / flat + dd = delta_dark / dark + rel_norm_error = (b + a) / (b * a) * (df + dd) + return rel_norm_error + + def process(self, out=None): + + projections = self.get_input() + dark = self.dark_field + flat = self.flat_field + + if projections.number_of_dimensions == 3: + if not (projections.shape[1:] == dark.shape and \ + projections.shape[1:] == flat.shape): + raise ValueError('Flats/Dark and projections size do not match.') + + + a = numpy.asarray( + [ Normalizer.normalize_projection( + projection, flat, dark, self.tolerance) \ + for projection in projections.as_array() ] + ) + elif projections.number_of_dimensions == 2: + a = Normalizer.normalize_projection(projections.as_array(), + flat, dark, self.tolerance) + y = type(projections)( a , True, + dimension_labels=projections.dimension_labels, + geometry=projections.geometry) + return y + \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/processors/__init__.py b/Wrappers/Python/build/lib/ccpi/processors/__init__.py new file mode 100644 index 0000000..f8d050e --- /dev/null +++ b/Wrappers/Python/build/lib/ccpi/processors/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Apr 30 13:51:09 2019 + +@author: ofn77899 +""" + +from .CenterOfRotationFinder import CenterOfRotationFinder +from .Normalizer import Normalizer diff --git a/Wrappers/Python/ccpi/data/__init__.py b/Wrappers/Python/ccpi/data/__init__.py new file mode 100644 index 0000000..2884108 --- /dev/null +++ b/Wrappers/Python/ccpi/data/__init__.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from ccpi.framework import ImageData +import numpy +from PIL import Image +import os +import os.path + +data_dir = os.path.abspath(os.path.dirname(__file__)) + +def camera(**kwargs): + + tmp = Image.open(os.path.join(data_dir, 'camera.png')) + + size = kwargs.get('size',(512, 512)) + + data = numpy.array(tmp.resize(size)) + + data = data/data.max() + + return ImageData(data) + + +def boat(**kwargs): + + tmp = Image.open(os.path.join(data_dir, 'boat.tiff')) + + size = kwargs.get('size',(512, 512)) + + data = numpy.array(tmp.resize(size)) + + data = data/data.max() + + return ImageData(data) + + +def peppers(**kwargs): + + tmp = Image.open(os.path.join(data_dir, 'peppers.tiff')) + + size = kwargs.get('size',(512, 512)) + + data = numpy.array(tmp.resize(size)) + + data = data/data.max() + + return ImageData(data) + diff --git a/Wrappers/Python/ccpi/data/boat.tiff b/Wrappers/Python/ccpi/data/boat.tiff new file mode 100644 index 0000000..fc1205a Binary files /dev/null and b/Wrappers/Python/ccpi/data/boat.tiff differ diff --git a/Wrappers/Python/ccpi/data/camera.png b/Wrappers/Python/ccpi/data/camera.png new file mode 100644 index 0000000..49be869 Binary files /dev/null and b/Wrappers/Python/ccpi/data/camera.png differ diff --git a/Wrappers/Python/ccpi/data/peppers.tiff b/Wrappers/Python/ccpi/data/peppers.tiff new file mode 100644 index 0000000..8c956f8 Binary files /dev/null and b/Wrappers/Python/ccpi/data/peppers.tiff differ diff --git a/Wrappers/Python/ccpi/data/test_show_data.py b/Wrappers/Python/ccpi/data/test_show_data.py new file mode 100644 index 0000000..7325c27 --- /dev/null +++ b/Wrappers/Python/ccpi/data/test_show_data.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Tue May 7 20:43:48 2019 + +@author: evangelos +""" + +from ccpi.data import camera, boat, peppers +import matplotlib.pyplot as plt + + +d = camera(size=(256,256)) + +plt.imshow(d.as_array()) +plt.colorbar() +plt.show() + +d1 = boat(size=(256,256)) + +plt.imshow(d1.as_array()) +plt.colorbar() +plt.show() + + +d2 = peppers(size=(256,256)) + +plt.imshow(d2.as_array()) +plt.colorbar() +plt.show() \ No newline at end of file diff --git a/Wrappers/Python/ccpi/optimisation/__pycache__/__init__.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..c7dd009 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/__pycache__/__init__.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/Algorithm.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/Algorithm.cpython-36.pyc new file mode 100644 index 0000000..3a68266 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/Algorithm.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/CGLS.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/CGLS.cpython-36.pyc new file mode 100644 index 0000000..1b83229 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/CGLS.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FBPD.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FBPD.cpython-36.pyc new file mode 100644 index 0000000..1bb0153 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FBPD.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FISTA.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FISTA.cpython-36.pyc new file mode 100644 index 0000000..15a3317 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FISTA.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/GradientDescent.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/GradientDescent.cpython-36.pyc new file mode 100644 index 0000000..46d6d4a Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/GradientDescent.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/PDHG.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/PDHG.cpython-36.pyc new file mode 100644 index 0000000..45abb22 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/PDHG.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/SIRT.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/SIRT.cpython-36.pyc new file mode 100644 index 0000000..96b3e83 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/SIRT.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/__init__.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..a713e47 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/__init__.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/BlockFunction.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/BlockFunction.cpython-36.pyc new file mode 100644 index 0000000..941a262 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/BlockFunction.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/Function.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/Function.cpython-36.pyc new file mode 100644 index 0000000..5f52020 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/Function.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/FunctionOperatorComposition.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/FunctionOperatorComposition.cpython-36.pyc new file mode 100644 index 0000000..dd14b60 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/FunctionOperatorComposition.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/IndicatorBox.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/IndicatorBox.cpython-36.pyc new file mode 100644 index 0000000..5b12cc6 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/IndicatorBox.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/KullbackLeibler.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/KullbackLeibler.cpython-36.pyc new file mode 100644 index 0000000..b6d85de Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/KullbackLeibler.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/L1Norm.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/L1Norm.cpython-36.pyc new file mode 100644 index 0000000..05b5010 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/L1Norm.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/L2NormSquared.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/L2NormSquared.cpython-36.pyc new file mode 100644 index 0000000..61a4e29 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/L2NormSquared.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/MixedL21Norm.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/MixedL21Norm.cpython-36.pyc new file mode 100644 index 0000000..623f7d7 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/MixedL21Norm.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/Norm2Sq.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/Norm2Sq.cpython-36.pyc new file mode 100644 index 0000000..8d4779a Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/Norm2Sq.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/ScaledFunction.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/ScaledFunction.cpython-36.pyc new file mode 100644 index 0000000..ba77722 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/ScaledFunction.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/ZeroFunction.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/ZeroFunction.cpython-36.pyc new file mode 100644 index 0000000..93b93ed Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/ZeroFunction.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/__init__.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..f0d07a4 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/__init__.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockOperator.cpython-36.pyc new file mode 100644 index 0000000..b4da27c Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockOperator.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockScaledOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockScaledOperator.cpython-36.pyc new file mode 100644 index 0000000..32b7f28 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockScaledOperator.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/FiniteDifferenceOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/FiniteDifferenceOperator.cpython-36.pyc new file mode 100644 index 0000000..8484ac0 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/FiniteDifferenceOperator.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/GradientOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/GradientOperator.cpython-36.pyc new file mode 100644 index 0000000..727b35a Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/GradientOperator.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/IdentityOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/IdentityOperator.cpython-36.pyc new file mode 100644 index 0000000..95297d5 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/IdentityOperator.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperator.cpython-36.pyc new file mode 100644 index 0000000..83bc87b Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperator.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperatorMatrix.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperatorMatrix.cpython-36.pyc new file mode 100644 index 0000000..16c7bdc Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperatorMatrix.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/Operator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/Operator.cpython-36.pyc new file mode 100644 index 0000000..3d1ae4e Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/Operator.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ScaledOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ScaledOperator.cpython-36.pyc new file mode 100644 index 0000000..980891e Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ScaledOperator.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ShrinkageOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ShrinkageOperator.cpython-36.pyc new file mode 100644 index 0000000..3087afc Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ShrinkageOperator.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/SparseFiniteDiff.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/SparseFiniteDiff.cpython-36.pyc new file mode 100644 index 0000000..93f4f2d Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/SparseFiniteDiff.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/SymmetrizedGradientOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/SymmetrizedGradientOperator.cpython-36.pyc new file mode 100644 index 0000000..ff62cae Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/SymmetrizedGradientOperator.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ZeroOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ZeroOperator.cpython-36.pyc new file mode 100644 index 0000000..21a2a3a Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ZeroOperator.cpython-36.pyc differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/__init__.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..c5db258 Binary files /dev/null and b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/__init__.cpython-36.pyc differ diff --git a/Wrappers/Python/demos/PDHG_TGV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_TGV_Denoising_SaltPepper.py new file mode 100644 index 0000000..7b65c31 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_TGV_Denoising_SaltPepper.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Fri Feb 22 14:53:03 2019 + +@author: evangelos +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, \ + Gradient, SymmetrizedGradient, ZeroOperator +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TGV SaltPepper denoising + +N = 100 + +data = np.zeros((N,N)) + +x1 = np.linspace(0, int(N/2), N) +x2 = np.linspace(int(N/2), 0., N) +xv, yv = np.meshgrid(x1, x2) + +xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T + +data = xv +data = ImageData(data/data.max()) + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Add Gaussian noise +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Regularisation Parameters +alpha = 0.8 +beta = numpy.sqrt(2)* alpha + +method = '1' + +if method == '0': + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + op31 = Identity(ig, ag) + op32 = ZeroOperator(op22.domain_geometry(), ag) + + operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + f3 = L1Norm(b=noisy_data) + f = BlockFunction(f1, f2, f3) + g = ZeroFunction() + +else: + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + operator = BlockOperator(op11, -1*op12, op21, op22, shape=(2,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + + f = BlockFunction(f1, f2) + g = BlockFunction(L1Norm(b=noisy_data), ZeroFunction()) + +## Compute operator Norm +normK = operator.norm() +# +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + +#%% +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output()[0].as_array()) +plt.title('TGV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + +if cvx_not_installable: + + u = Variable(ig.shape) + w1 = Variable((N, N)) + w2 = Variable((N, N)) + + # create TGV regulariser + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ + DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ + beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) + + constraints = [] + fidelity = pnorm(u - noisy_data.as_array(),1) + solver = MOSEK + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output()[0].as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output()[0].as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/demos/PDHG_TGV_Tomo2D.py b/Wrappers/Python/demos/PDHG_TGV_Tomo2D.py new file mode 100644 index 0000000..49d4db6 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_TGV_Tomo2D.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient, Identity, \ + SymmetrizedGradient, ZeroOperator +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple + +# Create phantom for TV 2D tomography +N = 75 + +data = np.zeros((N,N)) + +x1 = np.linspace(0, int(N/2), N) +x2 = np.linspace(int(N/2), 0., N) +xv, yv = np.meshgrid(x1, x2) + +xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T +data = xv +data = ImageData(data/data.max()) + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'gpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +scale = 0.1 +np.random.seed(5) +n1 = scale * np.random.poisson(sin.as_array()/scale) +noisy_data = AcquisitionData(n1, ag) + + +plt.imshow(noisy_data.as_array()) +plt.show() +#%% +# Regularisation Parameters +alpha = 0.7 +beta = 2 + +# Create Operators +op11 = Gradient(ig) +op12 = Identity(op11.range_geometry()) + +op22 = SymmetrizedGradient(op11.domain_geometry()) +op21 = ZeroOperator(ig, op22.range_geometry()) + +op31 = Aop +op32 = ZeroOperator(op22.domain_geometry(), ag) + +operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + +f1 = alpha * MixedL21Norm() +f2 = beta * MixedL21Norm() +f3 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2, f3) +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output()[0].as_array()) +plt.title('TGV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TGV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + diff --git a/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian.py new file mode 100644 index 0000000..c830025 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian.py @@ -0,0 +1,211 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + + +""" + +Total Variation Denoising using PDHG algorithm: + + min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) + + +Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Gaussian Noise + + Method = 0: K = [ \nabla, + Identity] + + Method = 1: K = \nabla + + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + + +from ccpi.data import camera + + +# Load Data +data = camera(size=(256,256)) + +N, M = data.shape + +# Image and Acquitition Geometries +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create Noisy data. Add Gaussian noise +np.random.seed(10) +noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.1, size=ig.shape) ) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(15,15)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = 0.2 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + f1 = alpha * MixedL21Norm() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = 0.5 * L2NormSquared(b = noisy_data) + +# Compute Operator Norm +normK = operator.norm() + +# Primal & Dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + +# Setup and Run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 3000 +pdhg.update_objective_interval = 200 +pdhg.run(3000, verbose=False) + +# Show Results +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() + +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = MOSEK) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'Truth') + + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + diff --git a/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian_3D.py b/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian_3D.py new file mode 100644 index 0000000..c86ddc9 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian_3D.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Gaussian denoising +import timeit +import os +from tomophantom import TomoP3D +import tomophantom + +print ("Building 3D phantom using TomoPhantom software") +tic=timeit.default_timer() +model = 13 # select a model number from the library +N = 64 # Define phantom dimensions using a scalar value (cubic phantom) +path = os.path.dirname(tomophantom.__file__) +path_library3D = os.path.join(path, "Phantom3DLibrary.dat") + +#This will generate a N x N x N phantom (3D) +phantom_tm = TomoP3D.Model(model, N, path_library3D) + +# Create noisy data. Add Gaussian noise +ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N, voxel_num_z=N) +ag = ig +n1 = random_noise(phantom_tm, mode = 'gaussian', mean=0, var = 0.001, seed=10) +noisy_data = ImageData(n1) + +sliceSel = int(0.5*N) +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.title('Axial View') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.title('Coronal View') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.title('Sagittal View') +plt.colorbar() +plt.show() + + +# Regularisation Parameter +alpha = 0.05 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = 0.5 * L2NormSquared(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) + +plt.subplot(2,3,1) +plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Axial View') + +plt.subplot(2,3,2) +plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Coronal View') + +plt.subplot(2,3,3) +plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.axis('off') +plt.title('Sagittal View') + + +plt.subplot(2,3,4) +plt.imshow(pdhg.get_output().as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.axis('off') +plt.subplot(2,3,5) +plt.imshow(pdhg.get_output().as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.axis('off') +plt.subplot(2,3,6) +plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.axis('off') +im = plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + diff --git a/Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py new file mode 100644 index 0000000..70f6b9b --- /dev/null +++ b/Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py @@ -0,0 +1,207 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 STFC, University of Manchester + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" + +Total Variation Denoising using PDHG algorithm: + + min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + \int x - g * log(x) + + \nabla: Gradient operator + g: Noisy Data with Poisson Noise + \alpha: Regularization parameter + + Method = 0: K = [ \nabla, + Identity] + + Method = 1: K = \nabla + + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Poisson denoising +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Apply Poisson noise +n1 = random_noise(data.as_array(), mode = 'poisson', seed = 10) +noisy_data = ImageData(n1) + +# Regularisation Parameter +alpha = 2 + +method = '1' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = KullbackLeibler(noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = KullbackLeibler(noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +opt = {'niter':2000, 'memopt': True} + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 + +def pdgap_objectives(niter, objective, solution): + + + print( "{:04}/{:04} {:<5} {:.4f} {:<5} {:.4f} {:<5} {:.4f}".\ + format(niter, pdhg.max_iteration,'', \ + objective[0],'',\ + objective[1],'',\ + objective[2])) + +pdhg.run(2000, callback = pdgap_objectives) + + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u1 = Variable(ig.shape) + q = Variable() + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) + + fidelity = sum( u1 - multiply(noisy_data.as_array(), log(u1)) ) + constraints = [q>= fidelity, u1>=0] + + solver = ECOS + obj = Minimize( regulariser + q) + prob = Problem(obj, constraints) + result = prob.solve(verbose = True, solver = solver) + + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u1.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u1.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/demos/PDHG_TV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_TV_Denoising_SaltPepper.py new file mode 100644 index 0000000..f5d4ce4 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_TV_Denoising_SaltPepper.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" + +Total Variation Denoising using PDHG algorithm: + + min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + ||x-g||_{1} + + \nabla: Gradient operator + g: Noisy Data with Salt & Pepper Noise + \alpha: Regularization parameter + + Method = 0: K = [ \nabla, + Identity] + + Method = 1: K = \nabla + + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Salt & Pepper denoising +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Apply Salt & Pepper noise +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Regularisation Parameter +alpha = 2 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = L1Norm(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = L1Norm(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +opt = {'niter':2000, 'memopt': True} + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = pnorm( u - noisy_data.as_array(),1) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/demos/PDHG_TV_Tomo2D.py b/Wrappers/Python/demos/PDHG_TV_Tomo2D.py new file mode 100644 index 0000000..87d5328 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_TV_Tomo2D.py @@ -0,0 +1,245 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple + +""" + +Total Variation Denoising using PDHG algorithm: + + min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + int A x -g log(Ax + \eta) + + \nabla: Gradient operator + + A: Projection Matrix + g: Noisy sinogram corrupted with Poisson Noise + + \eta: Background Noise + \alpha: Regularization parameter + +""" + +# Create phantom for TV 2D tomography +N = 75 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +scale = 2 +n1 = scale * np.random.poisson(sin.as_array()/scale) +noisy_data = AcquisitionData(n1, ag) + +# Regularisation Parameter +alpha = 5 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +diag_precon = True + +if diag_precon: + + def tau_sigma_precond(operator): + + tau = 1/operator.sum_abs_row() + sigma = 1/ operator.sum_abs_col() + + return tau, sigma + + tau, sigma = tau_sigma_precond(operator) + +else: + # Compute operator Norm + normK = operator.norm() + # Primal & dual stepsizes + sigma = 10 + tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + + +#%% +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff +import astra +import numpy + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + + ##Construct problem + u = Variable(N*N) + #q = Variable() + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + + # create matrix representation for Astra operator + + vol_geom = astra.create_vol_geom(N, N) + proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + + proj_id = astra.create_projector('strip', proj_geom, vol_geom) + + matrix_id = astra.projector.matrix(proj_id) + + ProjMat = astra.matrix.get(matrix_id) + + fidelity = sum( ProjMat * u - noisy_data.as_array().ravel() * log(ProjMat * u)) + #constraints = [q>= fidelity, u>=0] + constraints = [u>=0] + + solver = SCS + obj = Minimize( regulariser + fidelity) + prob = Problem(obj, constraints) + result = prob.solve(verbose = True, solver = solver) + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = pnorm( u - noisy_data.as_array(),1) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(np.reshape(u.value, (N, N))) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_TV_Tomo2D_time.py b/Wrappers/Python/demos/PDHG_TV_Tomo2D_time.py new file mode 100644 index 0000000..045458a --- /dev/null +++ b/Wrappers/Python/demos/PDHG_TV_Tomo2D_time.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorMC + +import os +import tomophantom +from tomophantom import TomoP2D + +# Create phantom for TV 2D dynamic tomography + +model = 102 # note that the selected model is temporal (2D + time) +N = 50 # set dimension of the phantom +# one can specify an exact path to the parameters file +# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' +path = os.path.dirname(tomophantom.__file__) +path_library2D = os.path.join(path, "Phantom2DLibrary.dat") +#This will generate a N_size x N_size x Time frames phantom (2D + time) +phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) + +plt.close('all') +plt.figure(1) +plt.rcParams.update({'font.size': 21}) +plt.title('{}''{}'.format('2D+t phantom using model no.',model)) +for sl in range(0,np.shape(phantom_2Dt)[0]): + im = phantom_2Dt[sl,:,:] + plt.imshow(im, vmin=0, vmax=1) + plt.pause(.1) + plt.draw + + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) +data = ImageData(phantom_2Dt, geometry=ig) + +detectors = N +angles = np.linspace(0,np.pi,N) + +ag = AcquisitionGeometry('parallel','2D', angles, detectors, channels = np.shape(phantom_2Dt)[0]) +Aop = AstraProjectorMC(ig, ag, 'gpu') +sin = Aop.direct(data) + +scale = 2 +n1 = scale * np.random.poisson(sin.as_array()/scale) +noisy_data = AcquisitionData(n1, ag) + +tindex = [3, 6, 10] + +fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) +plt.subplot(1,3,1) +plt.imshow(noisy_data.as_array()[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) +plt.subplot(1,3,2) +plt.imshow(noisy_data.as_array()[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) +plt.subplot(1,3,3) +plt.imshow(noisy_data.as_array()[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +plt.show() + +#%% +# Regularisation Parameter +alpha = 5 + +# Create operators +#op1 = Gradient(ig) +op1 = Gradient(ig, correlation='SpaceChannels') +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + + +#%% +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) + +plt.subplot(2,3,1) +plt.imshow(phantom_2Dt[tindex[0],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) + +plt.subplot(2,3,2) +plt.imshow(phantom_2Dt[tindex[1],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) + +plt.subplot(2,3,3) +plt.imshow(phantom_2Dt[tindex[2],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + + +plt.subplot(2,3,4) +plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(2,3,5) +plt.imshow(pdhg.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(2,3,6) +plt.imshow(pdhg.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') +im = plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + diff --git a/Wrappers/Python/demos/PDHG_Tikhonov_Denoising.py b/Wrappers/Python/demos/PDHG_Tikhonov_Denoising.py new file mode 100644 index 0000000..041d4ee --- /dev/null +++ b/Wrappers/Python/demos/PDHG_Tikhonov_Denoising.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Salt & Pepper denoising +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Apply Salt & Pepper noise +n1 = random_noise(data.as_array(), mode = 'gaussian', mean=0, var = 0.05, seed=10) +noisy_data = ImageData(n1) + +# Regularisation Parameter +alpha = 4 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * L2NormSquared() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * L2NormSquared() + g = 0.5 * L2NormSquared(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +opt = {'niter':2000, 'memopt': True} + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('Tikhonov Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + + regulariser = alpha * sum_squares(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py b/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py new file mode 100644 index 0000000..f17c4fe --- /dev/null +++ b/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction +from skimage.util import random_noise +from ccpi.astra.ops import AstraProjectorSimple + +# Create phantom for TV 2D tomography +N = 75 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'gpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Gaussian noise + +np.random.seed(10) +noisy_data = sin + AcquisitionData(np.random.normal(0, 3, sin.shape)) + +# Regularisation Parameter +alpha = 500 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * L2NormSquared() +f2 = 0.5 * L2NormSquared(b=noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 5000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + +#%% +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('Tikhonov Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + diff --git a/Wrappers/Python/environment.yml b/Wrappers/Python/environment.yml new file mode 100644 index 0000000..5cdd6fe --- /dev/null +++ b/Wrappers/Python/environment.yml @@ -0,0 +1,11 @@ +name: test_new +dependencies: + - python=3.6.7=h8dc6b48_1004 + - numpy=1.11.3=py36hdf140aa_1207 + - spyder=3.3.4=py36_0 + - scikit-image=0.15.0=py36h6de7cb9_0 + - scipy=1.2.1=py36hbd7caa9_1 + - astra-toolbox=1.8.3=py36h804c3c0_0 + + + diff --git a/Wrappers/Python/wip/.DS_Store b/Wrappers/Python/wip/.DS_Store new file mode 100644 index 0000000..a9a83e2 Binary files /dev/null and b/Wrappers/Python/wip/.DS_Store differ diff --git a/Wrappers/Python/wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py b/Wrappers/Python/wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py index c877018..39f0907 100644 --- a/Wrappers/Python/wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py +++ b/Wrappers/Python/wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py @@ -61,7 +61,6 @@ sin = Aop.direct(data) noisy_data = sin # Setup and run Astra CGLS algorithm - vol_geom = astra.create_vol_geom(N, N) proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) diff --git a/Wrappers/Python/wip/Demos/.DS_Store b/Wrappers/Python/wip/Demos/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/Wrappers/Python/wip/Demos/.DS_Store differ diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py index 860e76e..5df02b1 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py @@ -1,21 +1,23 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= """ @@ -25,12 +27,14 @@ Total Variation Denoising using PDHG algorithm: min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) -Problem: min_x \alpha * ||\nabla x||_{1} + || x - g ||_{2}^{2} +Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} - \nabla: Gradient operator - g: Noisy Data with Gaussian Noise \alpha: Regularization parameter + \nabla: Gradient operator + + g: Noisy Data with Gaussian Noise + Method = 0: K = [ \nabla, Identity] @@ -50,22 +54,50 @@ from ccpi.optimisation.algorithms import PDHG from ccpi.optimisation.operators import BlockOperator, Identity, Gradient from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ MixedL21Norm, BlockFunction + + + + +from Data import * + +#%% -# Create phantom for TV Gaussian denoising -N = 200 +data = ImageData(plt.imread('camera.png')) + +# +## Create phantom for TV Gaussian denoising +#N = 200 +# +#data = np.zeros((N,N)) +#data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +#data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +#data = ImageData(data) +#ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +#ag = ig +# +# +# +## Replace with http://sipi.usc.edu/database/database.php?volume=misc&image=36#top + -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig # Create noisy data. Add Gaussian noise np.random.seed(10) noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.05, size=ig.shape) ) +# Show Ground Truth and Noisy Data +plt.figure(figsize=(15,15)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + # Regularisation Parameter alpha = 2 @@ -80,8 +112,7 @@ if method == '0': # Create BlockOperator operator = BlockOperator(op1, op2, shape=(2,1) ) - # Create functions - + # Create functions f1 = alpha * MixedL21Norm() f2 = 0.5 * L2NormSquared(b = noisy_data) f = BlockFunction(f1, f2) @@ -95,19 +126,20 @@ else: f = alpha * MixedL21Norm() g = 0.5 * L2NormSquared(b = noisy_data) -# Compute operator Norm +# Compute Operator Norm normK = operator.norm() -# Primal & dual stepsizes +# Primal & Dual stepsizes sigma = 1 tau = 1/(sigma*normK**2) -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +# Setup and Run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) pdhg.max_iteration = 3000 pdhg.update_objective_interval = 200 pdhg.run(3000, verbose=False) +# Show Results plt.figure(figsize=(15,15)) plt.subplot(3,1,1) plt.imshow(data.as_array()) diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py index 3c295f5..70f6b9b 100644 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py +++ b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py @@ -3,7 +3,7 @@ # Visual Analytics and Imaging System Group of the Science Technology # Facilities Council, STFC -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca +# Copyright 2018-2019 STFC, University of Manchester # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ Total Variation Denoising using PDHG algorithm: Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + \int x - g * log(x) - \nabla: Gradient operator + \nabla: Gradient operator g: Noisy Data with Poisson Noise \alpha: Regularization parameter -- cgit v1.2.3 From 0a1af2dd369c13639c0348d89a156d762f9832eb Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 7 May 2019 23:03:30 +0100 Subject: delete build pycache --- Wrappers/Python/build/lib/ccpi/__init__.py | 18 - Wrappers/Python/build/lib/ccpi/contrib/__init__.py | 0 .../lib/ccpi/contrib/optimisation/__init__.py | 0 .../contrib/optimisation/algorithms/__init__.py | 0 .../ccpi/contrib/optimisation/algorithms/spdhg.py | 338 ----- Wrappers/Python/build/lib/ccpi/data/__init__.py | 67 - .../build/lib/ccpi/framework/BlockDataContainer.py | 484 ------- .../build/lib/ccpi/framework/BlockGeometry.py | 80 -- .../Python/build/lib/ccpi/framework/__init__.py | 26 - .../Python/build/lib/ccpi/framework/framework.py | 1437 -------------------- Wrappers/Python/build/lib/ccpi/io/__init__.py | 18 - Wrappers/Python/build/lib/ccpi/io/reader.py | 511 ------- .../Python/build/lib/ccpi/optimisation/__init__.py | 18 - .../lib/ccpi/optimisation/algorithms/Algorithm.py | 161 --- .../build/lib/ccpi/optimisation/algorithms/CGLS.py | 87 -- .../build/lib/ccpi/optimisation/algorithms/FBPD.py | 86 -- .../lib/ccpi/optimisation/algorithms/FISTA.py | 121 -- .../optimisation/algorithms/GradientDescent.py | 76 -- .../build/lib/ccpi/optimisation/algorithms/PDHG.py | 178 --- .../build/lib/ccpi/optimisation/algorithms/SIRT.py | 74 - .../lib/ccpi/optimisation/algorithms/__init__.py | 33 - .../Python/build/lib/ccpi/optimisation/algs.py | 307 ----- .../Python/build/lib/ccpi/optimisation/funcs.py | 265 ---- .../ccpi/optimisation/functions/BlockFunction.py | 221 --- .../lib/ccpi/optimisation/functions/Function.py | 69 - .../functions/FunctionOperatorComposition.py | 92 -- .../functions/FunctionOperatorComposition_old.py | 85 -- .../ccpi/optimisation/functions/IndicatorBox.py | 65 - .../ccpi/optimisation/functions/KullbackLeibler.py | 136 -- .../lib/ccpi/optimisation/functions/L1Norm.py | 234 ---- .../ccpi/optimisation/functions/L2NormSquared.py | 286 ---- .../ccpi/optimisation/functions/MixedL21Norm.py | 159 --- .../lib/ccpi/optimisation/functions/Norm2Sq.py | 98 -- .../ccpi/optimisation/functions/ScaledFunction.py | 150 -- .../ccpi/optimisation/functions/ZeroFunction.py | 55 - .../lib/ccpi/optimisation/functions/__init__.py | 13 - .../ccpi/optimisation/operators/BlockOperator.py | 417 ------ .../optimisation/operators/BlockScaledOperator.py | 67 - .../operators/FiniteDifferenceOperator.py | 372 ----- .../operators/FiniteDifferenceOperator_old.py | 374 ----- .../optimisation/operators/GradientOperator.py | 242 ---- .../optimisation/operators/IdentityOperator.py | 79 -- .../ccpi/optimisation/operators/LinearOperator.py | 67 - .../optimisation/operators/LinearOperatorMatrix.py | 51 - .../lib/ccpi/optimisation/operators/Operator.py | 30 - .../ccpi/optimisation/operators/ScaledOperator.py | 51 - .../optimisation/operators/ShrinkageOperator.py | 19 - .../optimisation/operators/SparseFiniteDiff.py | 144 -- .../operators/SymmetrizedGradientOperator.py | 244 ---- .../ccpi/optimisation/operators/ZeroOperator.py | 44 - .../lib/ccpi/optimisation/operators/__init__.py | 23 - .../lib/ccpi/processors/CenterOfRotationFinder.py | 408 ------ .../Python/build/lib/ccpi/processors/Normalizer.py | 124 -- .../Python/build/lib/ccpi/processors/__init__.py | 9 - .../__pycache__/__init__.cpython-36.pyc | Bin 193 -> 0 bytes .../__pycache__/BlockOperator.cpython-36.pyc | Bin 11183 -> 0 bytes .../__pycache__/BlockScaledOperator.cpython-36.pyc | Bin 3177 -> 0 bytes .../FiniteDifferenceOperator.cpython-36.pyc | Bin 8321 -> 0 bytes .../__pycache__/GradientOperator.cpython-36.pyc | Bin 5532 -> 0 bytes .../__pycache__/IdentityOperator.cpython-36.pyc | Bin 2266 -> 0 bytes .../__pycache__/LinearOperator.cpython-36.pyc | Bin 2403 -> 0 bytes .../LinearOperatorMatrix.cpython-36.pyc | Bin 2364 -> 0 bytes .../operators/__pycache__/Operator.cpython-36.pyc | Bin 1632 -> 0 bytes .../__pycache__/ScaledOperator.cpython-36.pyc | Bin 2629 -> 0 bytes .../__pycache__/ShrinkageOperator.cpython-36.pyc | Bin 807 -> 0 bytes .../__pycache__/SparseFiniteDiff.cpython-36.pyc | Bin 4126 -> 0 bytes .../SymmetrizedGradientOperator.cpython-36.pyc | Bin 4810 -> 0 bytes .../__pycache__/ZeroOperator.cpython-36.pyc | Bin 1580 -> 0 bytes .../operators/__pycache__/__init__.cpython-36.pyc | Bin 844 -> 0 bytes 69 files changed, 8813 deletions(-) delete mode 100644 Wrappers/Python/build/lib/ccpi/__init__.py delete mode 100644 Wrappers/Python/build/lib/ccpi/contrib/__init__.py delete mode 100644 Wrappers/Python/build/lib/ccpi/contrib/optimisation/__init__.py delete mode 100644 Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/__init__.py delete mode 100644 Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/spdhg.py delete mode 100644 Wrappers/Python/build/lib/ccpi/data/__init__.py delete mode 100644 Wrappers/Python/build/lib/ccpi/framework/BlockDataContainer.py delete mode 100644 Wrappers/Python/build/lib/ccpi/framework/BlockGeometry.py delete mode 100644 Wrappers/Python/build/lib/ccpi/framework/__init__.py delete mode 100644 Wrappers/Python/build/lib/ccpi/framework/framework.py delete mode 100644 Wrappers/Python/build/lib/ccpi/io/__init__.py delete mode 100644 Wrappers/Python/build/lib/ccpi/io/reader.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/__init__.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/CGLS.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FBPD.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FISTA.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/GradientDescent.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/SIRT.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algorithms/__init__.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/algs.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/funcs.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/BlockFunction.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/Function.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition_old.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/IndicatorBox.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/L1Norm.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/MixedL21Norm.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/Norm2Sq.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/ScaledFunction.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFunction.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/functions/__init__.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockOperator.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockScaledOperator.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/IdentityOperator.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperator.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperatorMatrix.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/Operator.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/ScaledOperator.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/ShrinkageOperator.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/SparseFiniteDiff.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/SymmetrizedGradientOperator.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/ZeroOperator.py delete mode 100644 Wrappers/Python/build/lib/ccpi/optimisation/operators/__init__.py delete mode 100644 Wrappers/Python/build/lib/ccpi/processors/CenterOfRotationFinder.py delete mode 100644 Wrappers/Python/build/lib/ccpi/processors/Normalizer.py delete mode 100644 Wrappers/Python/build/lib/ccpi/processors/__init__.py delete mode 100644 Wrappers/Python/ccpi/optimisation/__pycache__/__init__.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockOperator.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockScaledOperator.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/FiniteDifferenceOperator.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/GradientOperator.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/IdentityOperator.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperator.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperatorMatrix.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/Operator.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/ScaledOperator.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/ShrinkageOperator.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/SparseFiniteDiff.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/SymmetrizedGradientOperator.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/ZeroOperator.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/__pycache__/__init__.cpython-36.pyc diff --git a/Wrappers/Python/build/lib/ccpi/__init__.py b/Wrappers/Python/build/lib/ccpi/__init__.py deleted file mode 100644 index cf2d93d..0000000 --- a/Wrappers/Python/build/lib/ccpi/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/contrib/__init__.py b/Wrappers/Python/build/lib/ccpi/contrib/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Wrappers/Python/build/lib/ccpi/contrib/optimisation/__init__.py b/Wrappers/Python/build/lib/ccpi/contrib/optimisation/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/__init__.py b/Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/spdhg.py b/Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/spdhg.py deleted file mode 100644 index 263a7cd..0000000 --- a/Wrappers/Python/build/lib/ccpi/contrib/optimisation/algorithms/spdhg.py +++ /dev/null @@ -1,338 +0,0 @@ -# Copyright 2018 Matthias Ehrhardt, Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy - -from ccpi.optimisation.funcs import Function -from ccpi.framework import ImageData -from ccpi.framework import AcquisitionData - - -class spdhg(): - """Computes a saddle point with a stochastic PDHG. - - This means, a solution (x*, y*), y* = (y*_1, ..., y*_n) such that - - (x*, y*) in arg min_x max_y sum_i=1^n - f*[i](y_i) + g(x) - - where g : X -> IR_infty and f[i] : Y[i] -> IR_infty are convex, l.s.c. and - proper functionals. For this algorithm, they all may be non-smooth and no - strong convexity is assumed. - - Parameters - ---------- - f : list of functions - Functionals Y[i] -> IR_infty that all have a convex conjugate with a - proximal operator, i.e. - f[i].convex_conj.prox(sigma[i]) : Y[i] -> Y[i]. - g : function - Functional X -> IR_infty that has a proximal operator, i.e. - g.prox(tau) : X -> X. - A : list of functions - Operators A[i] : X -> Y[i] that possess adjoints: A[i].adjoint - x : primal variable, optional - By default equals 0. - y : dual variable, optional - Part of a product space. By default equals 0. - z : variable, optional - Adjoint of dual variable, z = A^* y. By default equals 0 if y = 0. - tau : scalar / vector / matrix, optional - Step size for primal variable. Note that the proximal operator of g - has to be well-defined for this input. - sigma : scalar, optional - Scalar / vector / matrix used as step size for dual variable. Note that - the proximal operator related to f (see above) has to be well-defined - for this input. - prob : list of scalars, optional - Probabilities prob[i] that a subset i is selected in each iteration. - If fun_select is not given, then the sum of all probabilities must - equal 1. - A_norms : list of scalars, optional - Norms of the operators in A. Can be used to determine the step sizes - tau and sigma and the probabilities prob. - fun_select : function, optional - Function that selects blocks at every iteration IN -> {1,...,n}. By - default this is serial sampling, fun_select(k) selects an index - i \in {1,...,n} with probability prob[i]. - - References - ---------- - [CERS2018] A. Chambolle, M. J. Ehrhardt, P. Richtarik and C.-B. Schoenlieb, - *Stochastic Primal-Dual Hybrid Gradient Algorithm with Arbitrary Sampling - and Imaging Applications*. SIAM Journal on Optimization, 28(4), 2783-2808 - (2018) http://doi.org/10.1007/s10851-010-0251-1 - - [E+2017] M. J. Ehrhardt, P. J. Markiewicz, P. Richtarik, J. Schott, - A. Chambolle and C.-B. Schoenlieb, *Faster PET reconstruction with a - stochastic primal-dual hybrid gradient method*. Wavelets and Sparsity XVII, - 58 (2017) http://doi.org/10.1117/12.2272946. - - [EMS2018] M. J. Ehrhardt, P. J. Markiewicz and C.-B. Schoenlieb, *Faster - PET Reconstruction with Non-Smooth Priors by Randomization and - Preconditioning*. (2018) ArXiv: http://arxiv.org/abs/1808.07150 - """ - - def __init__(self, f, g, A, x=None, y=None, z=None, tau=None, sigma=None, - prob=None, A_norms=None, fun_select=None): - # fun_select is optional and by default performs serial sampling - - if x is None: - x = A[0].allocate_direct(0) - - if y is None: - if z is not None: - raise ValueError('y and z have to be defaulted together') - - y = [Ai.allocate_adjoint(0) for Ai in A] - z = 0 * x.copy() - - else: - if z is None: - raise ValueError('y and z have to be defaulted together') - - if A_norms is not None: - if tau is not None or sigma is not None or prob is not None: - raise ValueError('Either A_norms or (tau, sigma, prob) must ' - 'be given') - - tau = 1 / sum(A_norms) - sigma = [1 / nA for nA in A_norms] - prob = [nA / sum(A_norms) for nA in A_norms] - - #uniform prob, needs different sigma and tau - #n = len(A) - #prob = [1./n] * n - - if fun_select is None: - if prob is None: - raise ValueError('prob was not determined') - - def fun_select(k): - return [int(numpy.random.choice(len(A), 1, p=prob))] - - self.iter = 0 - self.x = x - - self.y = y - self.z = z - - self.f = f - self.g = g - self.A = A - self.tau = tau - self.sigma = sigma - self.prob = prob - self.fun_select = fun_select - - # Initialize variables - self.z_relax = z.copy() - self.tmp = self.x.copy() - - def update(self): - # select block - selected = self.fun_select(self.iter) - - # update primal variable - #tmp = (self.x - self.tau * self.z_relax).as_array() - #self.x.fill(self.g.prox(tmp, self.tau)) - self.tmp = - self.tau * self.z_relax - self.tmp += self.x - self.x = self.g.prox(self.tmp, self.tau) - - # update dual variable and z, z_relax - self.z_relax = self.z.copy() - for i in selected: - # save old yi - y_old = self.y[i].copy() - - # y[i]= prox(tmp) - tmp = y_old + self.sigma[i] * self.A[i].direct(self.x) - self.y[i] = self.f[i].convex_conj.prox(tmp, self.sigma[i]) - - # update adjoint of dual variable - dz = self.A[i].adjoint(self.y[i] - y_old) - self.z += dz - - # compute extrapolation - self.z_relax += (1 + 1 / self.prob[i]) * dz - - self.iter += 1 - - -## Functions - -class KullbackLeibler(Function): - def __init__(self, data, background): - self.data = data - self.background = background - self.__offset = None - - def __call__(self, x): - """Return the KL-diveregnce in the point ``x``. - - If any components of ``x`` is non-positive, the value is positive - infinity. - - Needs one extra array of memory of the size of `prior`. - """ - - # define short variable names - y = self.data - r = self.background - - # Compute - # sum(x + r - y + y * log(y / (x + r))) - # = sum(x - y * log(x + r)) + self.offset - # Assume that - # x + r > 0 - - # sum the result up - obj = numpy.sum(x - y * numpy.log(x + r)) + self.offset() - - if numpy.isnan(obj): - # In this case, some element was less than or equal to zero - return numpy.inf - else: - return obj - - @property - def convex_conj(self): - """The convex conjugate functional of the KL-functional.""" - return KullbackLeiblerConvexConjugate(self.data, self.background) - - def offset(self): - """The offset which is independent of the unknown.""" - - if self.__offset is None: - tmp = self.domain.element() - - # define short variable names - y = self.data - r = self.background - - tmp = self.domain.element(numpy.maximum(y, 1)) - tmp = r - y + y * numpy.log(tmp) - - # sum the result up - self.__offset = numpy.sum(tmp) - - return self.__offset - -# def __repr__(self): -# """to be added???""" -# """Return ``repr(self)``.""" - # return '{}({!r}, {!r}, {!r})'.format(self.__class__.__name__, - ## self.domain, self.data, - # self.background) - - -class KullbackLeiblerConvexConjugate(Function): - """The convex conjugate of Kullback-Leibler divergence functional. - - Notes - ----- - The functional :math:`F^*` with prior :math:`g>0` is given by: - - .. math:: - F^*(x) - = - \\begin{cases} - \\sum_{i} \left( -g_i \ln(1 - x_i) \\right) - & \\text{if } x_i < 1 \\forall i - \\\\ - +\\infty & \\text{else} - \\end{cases} - - See Also - -------- - KullbackLeibler : convex conjugate functional - """ - - def __init__(self, data, background): - self.data = data - self.background = background - - def __call__(self, x): - y = self.data - r = self.background - - tmp = numpy.sum(- x * r - y * numpy.log(1 - x)) - - if numpy.isnan(tmp): - # In this case, some element was larger than or equal to one - return numpy.inf - else: - return tmp - - - def prox(self, x, tau, out=None): - # Let y = data, r = background, z = x + tau * r - # Compute 0.5 * (z + 1 - sqrt((z - 1)**2 + 4 * tau * y)) - # Currently it needs 3 extra copies of memory. - - if out is None: - out = x.copy() - - # define short variable names - try: # this should be standard SIRF/CIL mode - y = self.data.as_array() - r = self.background.as_array() - x = x.as_array() - - try: - taua = tau.as_array() - except: - taua = tau - - z = x + tau * r - - out.fill(0.5 * (z + 1 - numpy.sqrt((z - 1) ** 2 + 4 * taua * y))) - - return out - - except: # e.g. for NumPy - y = self.data - r = self.background - - try: - taua = tau.as_array() - except: - taua = tau - - z = x + tau * r - - out[:] = 0.5 * (z + 1 - numpy.sqrt((z - 1) ** 2 + 4 * taua * y)) - - return out - - @property - def convex_conj(self): - return KullbackLeibler(self.data, self.background) - - -def mult(x, y): - try: - xa = x.as_array() - except: - xa = x - - out = y.clone() - out.fill(xa * y.as_array()) - - return out diff --git a/Wrappers/Python/build/lib/ccpi/data/__init__.py b/Wrappers/Python/build/lib/ccpi/data/__init__.py deleted file mode 100644 index af10536..0000000 --- a/Wrappers/Python/build/lib/ccpi/data/__init__.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from ccpi.framework import ImageData -import numpy -from PIL import Image -import os -import os.path - -data_dir = os.path.abspath(os.path.dirname(__file__)) - - -def camera(**kwargs): - - tmp = Image.open(os.path.join(data_dir, 'camera.png')) - - size = kwargs.get('size',(512, 512)) - - data = numpy.array(tmp.resize(size)) - - data = data/data.max() - - return ImageData(data) - - -def boat(**kwargs): - - tmp = Image.open(os.path.join(data_dir, 'boat.tiff')) - - size = kwargs.get('size',(512, 512)) - - data = numpy.array(tmp.resize(size)) - - data = data/data.max() - - return ImageData(data) - - -def peppers(**kwargs): - - tmp = Image.open(os.path.join(data_dir, 'peppers.tiff')) - - size = kwargs.get('size',(512, 512)) - - data = numpy.array(tmp.resize(size)) - - data = data/data.max() - - return ImageData(data) - diff --git a/Wrappers/Python/build/lib/ccpi/framework/BlockDataContainer.py b/Wrappers/Python/build/lib/ccpi/framework/BlockDataContainer.py deleted file mode 100644 index 166014b..0000000 --- a/Wrappers/Python/build/lib/ccpi/framework/BlockDataContainer.py +++ /dev/null @@ -1,484 +0,0 @@ - # -*- coding: utf-8 -*- -""" -Created on Tue Mar 5 16:04:45 2019 - -@author: ofn77899 -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy -from numbers import Number -import functools -from ccpi.framework import DataContainer -#from ccpi.framework import AcquisitionData, ImageData -#from ccpi.optimisation.operators import Operator, LinearOperator - -class BlockDataContainer(object): - '''Class to hold DataContainers as column vector - - Provides basic algebra between BlockDataContainer's, DataContainer's and - subclasses and Numbers - - 1) algebra between `BlockDataContainer`s will be element-wise, only if - the shape of the 2 `BlockDataContainer`s is the same, otherwise it - will fail - 2) algebra between `BlockDataContainer`s and `list` or `numpy array` will - work as long as the number of `rows` and element of the arrays match, - indipendently on the fact that the `BlockDataContainer` could be nested - 3) algebra between `BlockDataContainer` and one `DataContainer` is possible. - It will require that all the `DataContainers` in the block to be - compatible with the `DataContainer` we want to algebra with. Should we - require that the `DataContainer` is the same type? Like `ImageData` or `AcquisitionData`? - 4) algebra between `BlockDataContainer` and a `Number` is possible and it - will be done with each element of the `BlockDataContainer` even if nested - - A = [ [B,C] , D] - A * 3 = [ 3 * [B,C] , 3* D] = [ [ 3*B, 3*C] , 3*D ] - - ''' - ADD = 'add' - SUBTRACT = 'subtract' - MULTIPLY = 'multiply' - DIVIDE = 'divide' - POWER = 'power' - __array_priority__ = 1 - __container_priority__ = 2 - def __init__(self, *args, **kwargs): - '''''' - self.containers = args - self.index = 0 - shape = kwargs.get('shape', None) - if shape is None: - shape = (len(args),1) -# shape = (len(args),1) - self.shape = shape - - n_elements = functools.reduce(lambda x,y: x*y, shape, 1) - if len(args) != n_elements: - raise ValueError( - 'Dimension and size do not match: expected {} got {}' - .format(n_elements, len(args))) - - - def __iter__(self): - '''BlockDataContainer is Iterable''' - return self - def next(self): - '''python2 backwards compatibility''' - return self.__next__() - def __next__(self): - try: - out = self[self.index] - except IndexError as ie: - raise StopIteration() - self.index+=1 - return out - - def is_compatible(self, other): - '''basic check if the size of the 2 objects fit''' - - if isinstance(other, Number): - return True - elif isinstance(other, (list, numpy.ndarray)) : - for ot in other: - if not isinstance(ot, (Number,\ - numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.int64,\ - numpy.float, numpy.float16, numpy.float32, numpy.float64, \ - numpy.complex)): - raise ValueError('List/ numpy array can only contain numbers {}'\ - .format(type(ot))) - return len(self.containers) == len(other) - elif issubclass(other.__class__, DataContainer): - ret = True - for i, el in enumerate(self.containers): - if isinstance(el, BlockDataContainer): - a = el.is_compatible(other) - else: - a = el.shape == other.shape - ret = ret and a - return ret - #return self.get_item(0).shape == other.shape - return len(self.containers) == len(other.containers) - - def get_item(self, row): - if row > self.shape[0]: - raise ValueError('Requested row {} > max {}'.format(row, self.shape[0])) - return self.containers[row] - - def __getitem__(self, row): - return self.get_item(row) - - def add(self, other, *args, **kwargs): - '''Algebra: add method of BlockDataContainer with number/DataContainer or BlockDataContainer - - :param: other (number, DataContainer or subclasses or BlockDataContainer - :param: out (optional): provides a placehold for the resul. - ''' - out = kwargs.get('out', None) - if out is not None: - self.binary_operations(BlockDataContainer.ADD, other, *args, **kwargs) - else: - return self.binary_operations(BlockDataContainer.ADD, other, *args, **kwargs) - def subtract(self, other, *args, **kwargs): - '''Algebra: subtract method of BlockDataContainer with number/DataContainer or BlockDataContainer - - :param: other (number, DataContainer or subclasses or BlockDataContainer - :param: out (optional): provides a placehold for the resul. - ''' - out = kwargs.get('out', None) - if out is not None: - self.binary_operations(BlockDataContainer.SUBTRACT, other, *args, **kwargs) - else: - return self.binary_operations(BlockDataContainer.SUBTRACT, other, *args, **kwargs) - def multiply(self, other, *args, **kwargs): - '''Algebra: multiply method of BlockDataContainer with number/DataContainer or BlockDataContainer - - :param: other (number, DataContainer or subclasses or BlockDataContainer - :param: out (optional): provides a placehold for the resul. - ''' - out = kwargs.get('out', None) - if out is not None: - self.binary_operations(BlockDataContainer.MULTIPLY, other, *args, **kwargs) - else: - return self.binary_operations(BlockDataContainer.MULTIPLY, other, *args, **kwargs) - def divide(self, other, *args, **kwargs): - '''Algebra: divide method of BlockDataContainer with number/DataContainer or BlockDataContainer - - :param: other (number, DataContainer or subclasses or BlockDataContainer - :param: out (optional): provides a placehold for the resul. - ''' - out = kwargs.get('out', None) - if out is not None: - self.binary_operations(BlockDataContainer.DIVIDE, other, *args, **kwargs) - else: - return self.binary_operations(BlockDataContainer.DIVIDE, other, *args, **kwargs) - - - def binary_operations(self, operation, other, *args, **kwargs): - '''Algebra: generic method of algebric operation with BlockDataContainer with number/DataContainer or BlockDataContainer - - Provides commutativity with DataContainer and subclasses, i.e. this - class's reverse algebric methods take precedence w.r.t. direct algebric - methods of DataContainer and subclasses. - - This method is not to be used directly - ''' - if not self.is_compatible(other): - raise ValueError('Incompatible for divide') - out = kwargs.get('out', None) - if isinstance(other, Number) or issubclass(other.__class__, DataContainer): - # try to do algebra with one DataContainer. Will raise error if not compatible - kw = kwargs.copy() - res = [] - for i,el in enumerate(self.containers): - if operation == BlockDataContainer.ADD: - op = el.add - elif operation == BlockDataContainer.SUBTRACT: - op = el.subtract - elif operation == BlockDataContainer.MULTIPLY: - op = el.multiply - elif operation == BlockDataContainer.DIVIDE: - op = el.divide - elif operation == BlockDataContainer.POWER: - op = el.power - else: - raise ValueError('Unsupported operation', operation) - if out is not None: - kw['out'] = out.get_item(i) - op(other, *args, **kw) - else: - res.append(op(other, *args, **kw)) - if out is not None: - return - else: - return type(self)(*res, shape=self.shape) - elif isinstance(other, (list, numpy.ndarray, BlockDataContainer)): - # try to do algebra with one DataContainer. Will raise error if not compatible - kw = kwargs.copy() - res = [] - if isinstance(other, BlockDataContainer): - the_other = other.containers - else: - the_other = other - for i,zel in enumerate(zip ( self.containers, the_other) ): - el = zel[0] - ot = zel[1] - if operation == BlockDataContainer.ADD: - op = el.add - elif operation == BlockDataContainer.SUBTRACT: - op = el.subtract - elif operation == BlockDataContainer.MULTIPLY: - op = el.multiply - elif operation == BlockDataContainer.DIVIDE: - op = el.divide - elif operation == BlockDataContainer.POWER: - op = el.power - else: - raise ValueError('Unsupported operation', operation) - if out is not None: - kw['out'] = out.get_item(i) - op(ot, *args, **kw) - else: - res.append(op(ot, *args, **kw)) - if out is not None: - return - else: - return type(self)(*res, shape=self.shape) - return type(self)(*[ operation(ot, *args, **kwargs) for el,ot in zip(self.containers,other)], shape=self.shape) - else: - raise ValueError('Incompatible type {}'.format(type(other))) - - - def power(self, other, *args, **kwargs): - if not self.is_compatible(other): - raise ValueError('Incompatible for power') - out = kwargs.get('out', None) - if isinstance(other, Number): - return type(self)(*[ el.power(other, *args, **kwargs) for el in self.containers], shape=self.shape) - elif isinstance(other, list) or isinstance(other, numpy.ndarray): - return type(self)(*[ el.power(ot, *args, **kwargs) for el,ot in zip(self.containers,other)], shape=self.shape) - return type(self)(*[ el.power(ot, *args, **kwargs) for el,ot in zip(self.containers,other.containers)], shape=self.shape) - - def maximum(self,other, *args, **kwargs): - if not self.is_compatible(other): - raise ValueError('Incompatible for maximum') - out = kwargs.get('out', None) - if isinstance(other, Number): - return type(self)(*[ el.maximum(other, *args, **kwargs) for el in self.containers], shape=self.shape) - elif isinstance(other, list) or isinstance(other, numpy.ndarray): - return type(self)(*[ el.maximum(ot, *args, **kwargs) for el,ot in zip(self.containers,other)], shape=self.shape) - return type(self)(*[ el.maximum(ot, *args, **kwargs) for el,ot in zip(self.containers,other.containers)], shape=self.shape) - - ## unary operations - def abs(self, *args, **kwargs): - return type(self)(*[ el.abs(*args, **kwargs) for el in self.containers], shape=self.shape) - def sign(self, *args, **kwargs): - return type(self)(*[ el.sign(*args, **kwargs) for el in self.containers], shape=self.shape) - def sqrt(self, *args, **kwargs): - return type(self)(*[ el.sqrt(*args, **kwargs) for el in self.containers], shape=self.shape) - def conjugate(self, out=None): - return type(self)(*[el.conjugate() for el in self.containers], shape=self.shape) - - ## reductions - - def sum(self, *args, **kwargs): - return numpy.sum([ el.sum(*args, **kwargs) for el in self.containers]) - - def squared_norm(self): - y = numpy.asarray([el.squared_norm() for el in self.containers]) - return y.sum() - - - def norm(self): - return numpy.sqrt(self.squared_norm()) - - def pnorm(self, p=2): - - if p==1: - return sum(self.abs()) - elif p==2: - return sum([el*el for el in self.containers]).sqrt() - else: - return ValueError('Not implemented') - - def copy(self): - '''alias of clone''' - return self.clone() - def clone(self): - return type(self)(*[el.copy() for el in self.containers], shape=self.shape) - def fill(self, other): - if isinstance (other, BlockDataContainer): - if not self.is_compatible(other): - raise ValueError('Incompatible containers') - for el,ot in zip(self.containers, other.containers): - el.fill(ot) - else: - return ValueError('Cannot fill with object provided {}'.format(type(other))) - - def __add__(self, other): - return self.add( other ) - # __radd__ - - def __sub__(self, other): - return self.subtract( other ) - # __rsub__ - - def __mul__(self, other): - return self.multiply(other) - # __rmul__ - - def __div__(self, other): - return self.divide(other) - # __rdiv__ - def __truediv__(self, other): - return self.divide(other) - - def __pow__(self, other): - return self.power(other) - # reverse operand - def __radd__(self, other): - '''Reverse addition - - to make sure that this method is called rather than the __mul__ of a numpy array - the class constant __array_priority__ must be set > 0 - https://docs.scipy.org/doc/numpy-1.15.1/reference/arrays.classes.html#numpy.class.__array_priority__ - ''' - return self + other - # __radd__ - - def __rsub__(self, other): - '''Reverse subtraction - - to make sure that this method is called rather than the __mul__ of a numpy array - the class constant __array_priority__ must be set > 0 - https://docs.scipy.org/doc/numpy-1.15.1/reference/arrays.classes.html#numpy.class.__array_priority__ - ''' - return (-1 * self) + other - # __rsub__ - - def __rmul__(self, other): - '''Reverse multiplication - - to make sure that this method is called rather than the __mul__ of a numpy array - the class constant __array_priority__ must be set > 0 - https://docs.scipy.org/doc/numpy-1.15.1/reference/arrays.classes.html#numpy.class.__array_priority__ - ''' - return self * other - # __rmul__ - - def __rdiv__(self, other): - '''Reverse division - - to make sure that this method is called rather than the __mul__ of a numpy array - the class constant __array_priority__ must be set > 0 - https://docs.scipy.org/doc/numpy-1.15.1/reference/arrays.classes.html#numpy.class.__array_priority__ - ''' - return pow(self / other, -1) - # __rdiv__ - def __rtruediv__(self, other): - '''Reverse truedivision - - to make sure that this method is called rather than the __mul__ of a numpy array - the class constant __array_priority__ must be set > 0 - https://docs.scipy.org/doc/numpy-1.15.1/reference/arrays.classes.html#numpy.class.__array_priority__ - ''' - return self.__rdiv__(other) - - def __rpow__(self, other): - '''Reverse power - - to make sure that this method is called rather than the __mul__ of a numpy array - the class constant __array_priority__ must be set > 0 - https://docs.scipy.org/doc/numpy-1.15.1/reference/arrays.classes.html#numpy.class.__array_priority__ - ''' - return other.power(self) - - def __iadd__(self, other): - '''Inline addition''' - if isinstance (other, BlockDataContainer): - for el,ot in zip(self.containers, other.containers): - el += ot - elif isinstance(other, Number): - for el in self.containers: - el += other - elif isinstance(other, list) or isinstance(other, numpy.ndarray): - if not self.is_compatible(other): - raise ValueError('Incompatible for __iadd__') - for el,ot in zip(self.containers, other): - el += ot - return self - # __iadd__ - - def __isub__(self, other): - '''Inline subtraction''' - if isinstance (other, BlockDataContainer): - for el,ot in zip(self.containers, other.containers): - el -= ot - elif isinstance(other, Number): - for el in self.containers: - el -= other - elif isinstance(other, list) or isinstance(other, numpy.ndarray): - if not self.is_compatible(other): - raise ValueError('Incompatible for __isub__') - for el,ot in zip(self.containers, other): - el -= ot - return self - # __isub__ - - def __imul__(self, other): - '''Inline multiplication''' - if isinstance (other, BlockDataContainer): - for el,ot in zip(self.containers, other.containers): - el *= ot - elif isinstance(other, Number): - for el in self.containers: - el *= other - elif isinstance(other, list) or isinstance(other, numpy.ndarray): - if not self.is_compatible(other): - raise ValueError('Incompatible for __imul__') - for el,ot in zip(self.containers, other): - el *= ot - return self - # __imul__ - - def __idiv__(self, other): - '''Inline division''' - if isinstance (other, BlockDataContainer): - for el,ot in zip(self.containers, other.containers): - el /= ot - elif isinstance(other, Number): - for el in self.containers: - el /= other - elif isinstance(other, list) or isinstance(other, numpy.ndarray): - if not self.is_compatible(other): - raise ValueError('Incompatible for __idiv__') - for el,ot in zip(self.containers, other): - el /= ot - return self - # __rdiv__ - def __itruediv__(self, other): - '''Inline truedivision''' - return self.__idiv__(other) - - def dot(self, other): -# - tmp = [ self.containers[i].dot(other.containers[i]) for i in range(self.shape[0])] - return sum(tmp) - - - -if __name__ == '__main__': - - from ccpi.framework import ImageGeometry, BlockGeometry - import numpy - - N, M = 2, 3 - ig = ImageGeometry(N, M) - BG = BlockGeometry(ig, ig) - - U = BG.allocate('random_int') - V = BG.allocate('random_int') - - - print ("test sum BDC " ) - w = U[0].as_array() + U[1].as_array() - w1 = sum(U).as_array() - numpy.testing.assert_array_equal(w, w1) - - print ("test sum BDC " ) - z = numpy.sqrt(U[0].as_array()**2 + U[1].as_array()**2) - z1 = sum(U**2).sqrt().as_array() - numpy.testing.assert_array_equal(z, z1) - - z2 = U.pnorm(2) - - zzz = U.dot(V) - - - - - - diff --git a/Wrappers/Python/build/lib/ccpi/framework/BlockGeometry.py b/Wrappers/Python/build/lib/ccpi/framework/BlockGeometry.py deleted file mode 100644 index ed44d99..0000000 --- a/Wrappers/Python/build/lib/ccpi/framework/BlockGeometry.py +++ /dev/null @@ -1,80 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy -from numbers import Number -import functools -from ccpi.framework import BlockDataContainer -#from ccpi.optimisation.operators import Operator, LinearOperator - -class BlockGeometry(object): - '''Class to hold Geometry as column vector''' - #__array_priority__ = 1 - def __init__(self, *args, **kwargs): - '''''' - self.geometries = args - self.index = 0 - - shape = (len(args),1) - self.shape = shape - - n_elements = functools.reduce(lambda x,y: x*y, shape, 1) - if len(args) != n_elements: - raise ValueError( - 'Dimension and size do not match: expected {} got {}' - .format(n_elements, len(args))) - - - def get_item(self, index): - '''returns the Geometry in the BlockGeometry located at position index''' - return self.geometries[index] - - def allocate(self, value=0, dimension_labels=None, **kwargs): - - symmetry = kwargs.get('symmetry',False) - containers = [geom.allocate(value) for geom in self.geometries] - - if symmetry == True: - - # for 2x2 - # [ ig11, ig12\ - # ig21, ig22] - - # Row-wise Order - - if len(containers)==4: - containers[1]=containers[2] - - # for 3x3 - # [ ig11, ig12, ig13\ - # ig21, ig22, ig23\ - # ig31, ig32, ig33] - - elif len(containers)==9: - containers[1]=containers[3] - containers[2]=containers[6] - containers[5]=containers[7] - - # for 4x4 - # [ ig11, ig12, ig13, ig14\ - # ig21, ig22, ig23, ig24\ - # ig31, ig32, ig33, ig34 - # ig41, ig42, ig43, ig44] - - elif len(containers) == 16: - containers[1]=containers[4] - containers[2]=containers[8] - containers[3]=containers[12] - containers[6]=containers[9] - containers[7]=containers[10] - containers[11]=containers[15] - - - - - return BlockDataContainer(*containers) - - - diff --git a/Wrappers/Python/build/lib/ccpi/framework/__init__.py b/Wrappers/Python/build/lib/ccpi/framework/__init__.py deleted file mode 100644 index 229edb5..0000000 --- a/Wrappers/Python/build/lib/ccpi/framework/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Mar 5 16:00:18 2019 - -@author: ofn77899 -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy -import sys -from datetime import timedelta, datetime -import warnings -from functools import reduce - - -from .framework import DataContainer -from .framework import ImageData, AcquisitionData -from .framework import ImageGeometry, AcquisitionGeometry -from .framework import find_key, message -from .framework import DataProcessor -from .framework import AX, PixelByPixelDataProcessor, CastDataContainer -from .BlockDataContainer import BlockDataContainer -from .BlockGeometry import BlockGeometry diff --git a/Wrappers/Python/build/lib/ccpi/framework/framework.py b/Wrappers/Python/build/lib/ccpi/framework/framework.py deleted file mode 100644 index dbe7d0a..0000000 --- a/Wrappers/Python/build/lib/ccpi/framework/framework.py +++ /dev/null @@ -1,1437 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy -import sys -from datetime import timedelta, datetime -import warnings -from functools import reduce -from numbers import Number - - -def find_key(dic, val): - """return the key of dictionary dic given the value""" - return [k for k, v in dic.items() if v == val][0] - -def message(cls, msg, *args): - msg = "{0}: " + msg - for i in range(len(args)): - msg += " {%d}" %(i+1) - args = list(args) - args.insert(0, cls.__name__ ) - - return msg.format(*args ) - - -class ImageGeometry(object): - RANDOM = 'random' - RANDOM_INT = 'random_int' - CHANNEL = 'channel' - ANGLE = 'angle' - VERTICAL = 'vertical' - HORIZONTAL_X = 'horizontal_x' - HORIZONTAL_Y = 'horizontal_y' - - def __init__(self, - voxel_num_x=0, - voxel_num_y=0, - voxel_num_z=0, - voxel_size_x=1, - voxel_size_y=1, - voxel_size_z=1, - center_x=0, - center_y=0, - center_z=0, - channels=1): - - self.voxel_num_x = voxel_num_x - self.voxel_num_y = voxel_num_y - self.voxel_num_z = voxel_num_z - self.voxel_size_x = voxel_size_x - self.voxel_size_y = voxel_size_y - self.voxel_size_z = voxel_size_z - self.center_x = center_x - self.center_y = center_y - self.center_z = center_z - self.channels = channels - - # this is some code repetition - if self.channels > 1: - if self.voxel_num_z>1: - self.length = 4 - self.shape = (self.channels, self.voxel_num_z, self.voxel_num_y, self.voxel_num_x) - dim_labels = [ImageGeometry.CHANNEL, ImageGeometry.VERTICAL, - ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] - else: - self.length = 3 - self.shape = (self.channels, self.voxel_num_y, self.voxel_num_x) - dim_labels = [ImageGeometry.CHANNEL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] - else: - if self.voxel_num_z>1: - self.length = 3 - self.shape = (self.voxel_num_z, self.voxel_num_y, self.voxel_num_x) - dim_labels = [ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, - ImageGeometry.HORIZONTAL_X] - else: - self.length = 2 - self.shape = (self.voxel_num_y, self.voxel_num_x) - dim_labels = [ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] - - self.dimension_labels = dim_labels - - def get_min_x(self): - return self.center_x - 0.5*self.voxel_num_x*self.voxel_size_x - - def get_max_x(self): - return self.center_x + 0.5*self.voxel_num_x*self.voxel_size_x - - def get_min_y(self): - return self.center_y - 0.5*self.voxel_num_y*self.voxel_size_y - - def get_max_y(self): - return self.center_y + 0.5*self.voxel_num_y*self.voxel_size_y - - def get_min_z(self): - if not self.voxel_num_z == 0: - return self.center_z - 0.5*self.voxel_num_z*self.voxel_size_z - else: - return 0 - - def get_max_z(self): - if not self.voxel_num_z == 0: - return self.center_z + 0.5*self.voxel_num_z*self.voxel_size_z - else: - return 0 - - def clone(self): - '''returns a copy of ImageGeometry''' - return ImageGeometry( - self.voxel_num_x, - self.voxel_num_y, - self.voxel_num_z, - self.voxel_size_x, - self.voxel_size_y, - self.voxel_size_z, - self.center_x, - self.center_y, - self.center_z, - self.channels) - def __str__ (self): - repres = "" - repres += "Number of channels: {0}\n".format(self.channels) - repres += "voxel_num : x{0},y{1},z{2}\n".format(self.voxel_num_x, self.voxel_num_y, self.voxel_num_z) - repres += "voxel_size : x{0},y{1},z{2}\n".format(self.voxel_size_x, self.voxel_size_y, self.voxel_size_z) - repres += "center : x{0},y{1},z{2}\n".format(self.center_x, self.center_y, self.center_z) - return repres - def allocate(self, value=0, dimension_labels=None, **kwargs): - '''allocates an ImageData according to the size expressed in the instance''' - out = ImageData(geometry=self) - if isinstance(value, Number): - if value != 0: - out += value - else: - if value == ImageGeometry.RANDOM: - seed = kwargs.get('seed', None) - if seed is not None: - numpy.random.seed(seed) - out.fill(numpy.random.random_sample(self.shape)) - elif value == ImageGeometry.RANDOM_INT: - seed = kwargs.get('seed', None) - if seed is not None: - numpy.random.seed(seed) - max_value = kwargs.get('max_value', 100) - out.fill(numpy.random.randint(max_value,size=self.shape)) - else: - raise ValueError('Value {} unknown'.format(value)) - if dimension_labels is not None: - if dimension_labels != self.dimension_labels: - return out.subset(dimensions=dimension_labels) - return out - # The following methods return 2 members of the class, therefore I - # don't think we need to implement them. - # Additionally using __len__ is confusing as one would think this is - # an iterable. - #def __len__(self): - # '''returns the length of the geometry''' - # return self.length - #def shape(self): - # '''Returns the shape of the array of the ImageData it describes''' - # return self.shape - -class AcquisitionGeometry(object): - RANDOM = 'random' - RANDOM_INT = 'random_int' - ANGLE_UNIT = 'angle_unit' - DEGREE = 'degree' - RADIAN = 'radian' - CHANNEL = 'channel' - ANGLE = 'angle' - VERTICAL = 'vertical' - HORIZONTAL = 'horizontal' - def __init__(self, - geom_type, - dimension, - angles, - pixel_num_h=0, - pixel_size_h=1, - pixel_num_v=0, - pixel_size_v=1, - dist_source_center=None, - dist_center_detector=None, - channels=1, - **kwargs - ): - """ - General inputs for standard type projection geometries - detectorDomain or detectorpixelSize: - If 2D - If scalar: Width of detector or single detector pixel - If 2-vec: Error - If 3D - If scalar: Width in both dimensions - If 2-vec: Vertical then horizontal size - grid - If 2D - If scalar: number of detectors - If 2-vec: error - If 3D - If scalar: Square grid that size - If 2-vec vertical then horizontal size - cone or parallel - 2D or 3D - parallel_parameters: ? - cone_parameters: - source_to_center_dist (if parallel: NaN) - center_to_detector_dist (if parallel: NaN) - standard or nonstandard (vec) geometry - angles - angles_format radians or degrees - """ - self.geom_type = geom_type # 'parallel' or 'cone' - self.dimension = dimension # 2D or 3D - self.angles = angles - num_of_angles = len (angles) - - self.dist_source_center = dist_source_center - self.dist_center_detector = dist_center_detector - - self.pixel_num_h = pixel_num_h - self.pixel_size_h = pixel_size_h - self.pixel_num_v = pixel_num_v - self.pixel_size_v = pixel_size_v - - self.channels = channels - self.angle_unit=kwargs.get(AcquisitionGeometry.ANGLE_UNIT, - AcquisitionGeometry.DEGREE) - if channels > 1: - if pixel_num_v > 1: - shape = (channels, num_of_angles , pixel_num_v, pixel_num_h) - dim_labels = [AcquisitionGeometry.CHANNEL , - AcquisitionGeometry.ANGLE , AcquisitionGeometry.VERTICAL , - AcquisitionGeometry.HORIZONTAL] - else: - shape = (channels , num_of_angles, pixel_num_h) - dim_labels = [AcquisitionGeometry.CHANNEL , - AcquisitionGeometry.ANGLE, AcquisitionGeometry.HORIZONTAL] - else: - if pixel_num_v > 1: - shape = (num_of_angles, pixel_num_v, pixel_num_h) - dim_labels = [AcquisitionGeometry.ANGLE , AcquisitionGeometry.VERTICAL , - AcquisitionGeometry.HORIZONTAL] - else: - shape = (num_of_angles, pixel_num_h) - dim_labels = [AcquisitionGeometry.ANGLE, AcquisitionGeometry.HORIZONTAL] - self.shape = shape - - self.dimension_labels = dim_labels - - def clone(self): - '''returns a copy of the AcquisitionGeometry''' - return AcquisitionGeometry(self.geom_type, - self.dimension, - self.angles, - self.pixel_num_h, - self.pixel_size_h, - self.pixel_num_v, - self.pixel_size_v, - self.dist_source_center, - self.dist_center_detector, - self.channels) - - def __str__ (self): - repres = "" - repres += "Number of dimensions: {0}\n".format(self.dimension) - repres += "angles: {0}\n".format(self.angles) - repres += "voxel_num : h{0},v{1}\n".format(self.pixel_num_h, self.pixel_num_v) - repres += "voxel size: h{0},v{1}\n".format(self.pixel_size_h, self.pixel_size_v) - repres += "geometry type: {0}\n".format(self.geom_type) - repres += "distance source-detector: {0}\n".format(self.dist_source_center) - repres += "distance center-detector: {0}\n".format(self.dist_source_center) - repres += "number of channels: {0}\n".format(self.channels) - return repres - def allocate(self, value=0, dimension_labels=None): - '''allocates an AcquisitionData according to the size expressed in the instance''' - out = AcquisitionData(geometry=self) - if isinstance(value, Number): - if value != 0: - out += value - else: - if value == AcquisitionData.RANDOM: - seed = kwargs.get('seed', None) - if seed is not None: - numpy.random.seed(seed) - out.fill(numpy.random.random_sample(self.shape)) - elif value == AcquisitionData.RANDOM_INT: - seed = kwargs.get('seed', None) - if seed is not None: - numpy.random.seed(seed) - max_value = kwargs.get('max_value', 100) - out.fill(numpy.random.randint(max_value,size=self.shape)) - else: - raise ValueError('Value {} unknown'.format(value)) - if dimension_labels is not None: - if dimension_labels != self.dimension_labels: - return out.subset(dimensions=dimension_labels) - return out - -class DataContainer(object): - '''Generic class to hold data - - Data is currently held in a numpy arrays''' - - __container_priority__ = 1 - def __init__ (self, array, deep_copy=True, dimension_labels=None, - **kwargs): - '''Holds the data''' - - self.shape = numpy.shape(array) - self.number_of_dimensions = len (self.shape) - self.dimension_labels = {} - self.geometry = None # Only relevant for AcquisitionData and ImageData - - if dimension_labels is not None and \ - len (dimension_labels) == self.number_of_dimensions: - for i in range(self.number_of_dimensions): - self.dimension_labels[i] = dimension_labels[i] - else: - for i in range(self.number_of_dimensions): - self.dimension_labels[i] = 'dimension_{0:02}'.format(i) - - if type(array) == numpy.ndarray: - if deep_copy: - self.array = array.copy() - else: - self.array = array - else: - raise TypeError('Array must be NumpyArray, passed {0}'\ - .format(type(array))) - - # finally copy the geometry - if 'geometry' in kwargs.keys(): - self.geometry = kwargs['geometry'] - else: - # assume it is parallel beam - pass - - def get_dimension_size(self, dimension_label): - if dimension_label in self.dimension_labels.values(): - acq_size = -1 - for k,v in self.dimension_labels.items(): - if v == dimension_label: - acq_size = self.shape[k] - return acq_size - else: - raise ValueError('Unknown dimension {0}. Should be one of'.format(dimension_label, - self.dimension_labels)) - def get_dimension_axis(self, dimension_label): - if dimension_label in self.dimension_labels.values(): - for k,v in self.dimension_labels.items(): - if v == dimension_label: - return k - else: - raise ValueError('Unknown dimension {0}. Should be one of'.format(dimension_label, - self.dimension_labels.values())) - - - def as_array(self, dimensions=None): - '''Returns the DataContainer as Numpy Array - - Returns the pointer to the array if dimensions is not set. - If dimensions is set, it first creates a new DataContainer with the subset - and then it returns the pointer to the array''' - if dimensions is not None: - return self.subset(dimensions).as_array() - return self.array - - - def subset(self, dimensions=None, **kw): - '''Creates a DataContainer containing a subset of self according to the - labels in dimensions''' - if dimensions is None: - if kw == {}: - return self.array.copy() - else: - reduced_dims = [v for k,v in self.dimension_labels.items()] - for dim_l, dim_v in kw.items(): - for k,v in self.dimension_labels.items(): - if v == dim_l: - reduced_dims.pop(k) - return self.subset(dimensions=reduced_dims, **kw) - else: - # check that all the requested dimensions are in the array - # this is done by checking the dimension_labels - proceed = True - unknown_key = '' - # axis_order contains the order of the axis that the user wants - # in the output DataContainer - axis_order = [] - if type(dimensions) == list: - for dl in dimensions: - if dl not in self.dimension_labels.values(): - proceed = False - unknown_key = dl - break - else: - axis_order.append(find_key(self.dimension_labels, dl)) - if not proceed: - raise KeyError('Subset error: Unknown key specified {0}'.format(dl)) - - # slice away the unwanted data from the array - unwanted_dimensions = self.dimension_labels.copy() - left_dimensions = [] - for ax in sorted(axis_order): - this_dimension = unwanted_dimensions.pop(ax) - left_dimensions.append(this_dimension) - #print ("unwanted_dimensions {0}".format(unwanted_dimensions)) - #print ("left_dimensions {0}".format(left_dimensions)) - #new_shape = [self.shape[ax] for ax in axis_order] - #print ("new_shape {0}".format(new_shape)) - command = "self.array[" - for i in range(self.number_of_dimensions): - if self.dimension_labels[i] in unwanted_dimensions.values(): - value = 0 - for k,v in kw.items(): - if k == self.dimension_labels[i]: - value = v - - command = command + str(value) - else: - command = command + ":" - if i < self.number_of_dimensions -1: - command = command + ',' - command = command + ']' - - cleaned = eval(command) - # cleaned has collapsed dimensions in the same order of - # self.array, but we want it in the order stated in the - # "dimensions". - # create axes order for numpy.transpose - axes = [] - for key in dimensions: - #print ("key {0}".format( key)) - for i in range(len( left_dimensions )): - ld = left_dimensions[i] - #print ("ld {0}".format( ld)) - if ld == key: - axes.append(i) - #print ("axes {0}".format(axes)) - - cleaned = numpy.transpose(cleaned, axes).copy() - - return type(self)(cleaned , True, dimensions) - - def fill(self, array, **dimension): - '''fills the internal numpy array with the one provided''' - if dimension == {}: - if issubclass(type(array), DataContainer) or\ - issubclass(type(array), numpy.ndarray): - if array.shape != self.shape: - raise ValueError('Cannot fill with the provided array.' + \ - 'Expecting {0} got {1}'.format( - self.shape,array.shape)) - if issubclass(type(array), DataContainer): - numpy.copyto(self.array, array.array) - else: - #self.array[:] = array - numpy.copyto(self.array, array) - else: - - command = 'self.array[' - i = 0 - for k,v in self.dimension_labels.items(): - for dim_label, dim_value in dimension.items(): - if dim_label == v: - command = command + str(dim_value) - else: - command = command + ":" - if i < self.number_of_dimensions -1: - command = command + ',' - i += 1 - command = command + "] = array[:]" - exec(command) - - - def check_dimensions(self, other): - return self.shape == other.shape - - ## algebra - - def __add__(self, other): - return self.add(other) - def __mul__(self, other): - return self.multiply(other) - def __sub__(self, other): - return self.subtract(other) - def __div__(self, other): - return self.divide(other) - def __truediv__(self, other): - return self.divide(other) - def __pow__(self, other): - return self.power(other) - - - - # reverse operand - def __radd__(self, other): - return self + other - # __radd__ - - def __rsub__(self, other): - return (-1 * self) + other - # __rsub__ - - def __rmul__(self, other): - return self * other - # __rmul__ - - def __rdiv__(self, other): - print ("call __rdiv__") - return pow(self / other, -1) - # __rdiv__ - def __rtruediv__(self, other): - return self.__rdiv__(other) - - def __rpow__(self, other): - if isinstance(other, (int, float)) : - fother = numpy.ones(numpy.shape(self.array)) * other - return type(self)(fother ** self.array , - dimension_labels=self.dimension_labels, - geometry=self.geometry) - elif issubclass(type(other), DataContainer): - if self.check_dimensions(other): - return type(self)(other.as_array() ** self.array , - dimension_labels=self.dimension_labels, - geometry=self.geometry) - else: - raise ValueError('Dimensions do not match') - # __rpow__ - - # in-place arithmetic operators: - # (+=, -=, *=, /= , //=, - # must return self - - def __iadd__(self, other): - kw = {'out':self} - return self.add(other, **kw) - - def __imul__(self, other): - kw = {'out':self} - return self.multiply(other, **kw) - - def __isub__(self, other): - kw = {'out':self} - return self.subtract(other, **kw) - - def __idiv__(self, other): - kw = {'out':self} - return self.divide(other, **kw) - - def __itruediv__(self, other): - kw = {'out':self} - return self.divide(other, **kw) - - - - def __str__ (self, representation=False): - repres = "" - repres += "Number of dimensions: {0}\n".format(self.number_of_dimensions) - repres += "Shape: {0}\n".format(self.shape) - repres += "Axis labels: {0}\n".format(self.dimension_labels) - if representation: - repres += "Representation: \n{0}\n".format(self.array) - return repres - - def clone(self): - '''returns a copy of itself''' - - return type(self)(self.array, - dimension_labels=self.dimension_labels, - deep_copy=True, - geometry=self.geometry ) - - def get_data_axes_order(self,new_order=None): - '''returns the axes label of self as a list - - if new_order is None returns the labels of the axes as a sorted-by-key list - if new_order is a list of length number_of_dimensions, returns a list - with the indices of the axes in new_order with respect to those in - self.dimension_labels: i.e. - self.dimension_labels = {0:'horizontal',1:'vertical'} - new_order = ['vertical','horizontal'] - returns [1,0] - ''' - if new_order is None: - - axes_order = [i for i in range(len(self.shape))] - for k,v in self.dimension_labels.items(): - axes_order[k] = v - return axes_order - else: - if len(new_order) == self.number_of_dimensions: - axes_order = [i for i in range(self.number_of_dimensions)] - - for i in range(len(self.shape)): - found = False - for k,v in self.dimension_labels.items(): - if new_order[i] == v: - axes_order[i] = k - found = True - if not found: - raise ValueError('Axis label {0} not found.'.format(new_order[i])) - return axes_order - else: - raise ValueError('Expecting {0} axes, got {2}'\ - .format(len(self.shape),len(new_order))) - - - def copy(self): - '''alias of clone''' - return self.clone() - - ## binary operations - - def pixel_wise_binary(self, pwop, x2, *args, **kwargs): - out = kwargs.get('out', None) - if out is None: - if isinstance(x2, (int, float, complex)): - out = pwop(self.as_array() , x2 , *args, **kwargs ) - elif isinstance(x2, (numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.int64,\ - numpy.float, numpy.float16, numpy.float32, numpy.float64, \ - numpy.complex)): - out = pwop(self.as_array() , x2 , *args, **kwargs ) - elif issubclass(type(x2) , DataContainer): - out = pwop(self.as_array() , x2.as_array() , *args, **kwargs ) - return type(self)(out, - deep_copy=False, - dimension_labels=self.dimension_labels, - geometry=self.geometry) - - - elif issubclass(type(out), DataContainer) and issubclass(type(x2), DataContainer): - if self.check_dimensions(out) and self.check_dimensions(x2): - kwargs['out'] = out.as_array() - pwop(self.as_array(), x2.as_array(), *args, **kwargs ) - #return type(self)(out.as_array(), - # deep_copy=False, - # dimension_labels=self.dimension_labels, - # geometry=self.geometry) - return out - else: - raise ValueError(message(type(self),"Wrong size for data memory: ", out.shape,self.shape)) - elif issubclass(type(out), DataContainer) and isinstance(x2, (int,float,complex)): - if self.check_dimensions(out): - kwargs['out']=out.as_array() - pwop(self.as_array(), x2, *args, **kwargs ) - return out - else: - raise ValueError(message(type(self),"Wrong size for data memory: ", out.shape,self.shape)) - elif issubclass(type(out), numpy.ndarray): - if self.array.shape == out.shape and self.array.dtype == out.dtype: - kwargs['out'] = out - pwop(self.as_array(), x2, *args, **kwargs) - #return type(self)(out, - # deep_copy=False, - # dimension_labels=self.dimension_labels, - # geometry=self.geometry) - else: - raise ValueError (message(type(self), "incompatible class:" , pwop.__name__, type(out))) - - def add(self, other, *args, **kwargs): - if hasattr(other, '__container_priority__') and \ - self.__class__.__container_priority__ < other.__class__.__container_priority__: - return other.add(self, *args, **kwargs) - return self.pixel_wise_binary(numpy.add, other, *args, **kwargs) - - def subtract(self, other, *args, **kwargs): - if hasattr(other, '__container_priority__') and \ - self.__class__.__container_priority__ < other.__class__.__container_priority__: - return other.subtract(self, *args, **kwargs) - return self.pixel_wise_binary(numpy.subtract, other, *args, **kwargs) - - def multiply(self, other, *args, **kwargs): - if hasattr(other, '__container_priority__') and \ - self.__class__.__container_priority__ < other.__class__.__container_priority__: - return other.multiply(self, *args, **kwargs) - return self.pixel_wise_binary(numpy.multiply, other, *args, **kwargs) - - def divide(self, other, *args, **kwargs): - if hasattr(other, '__container_priority__') and \ - self.__class__.__container_priority__ < other.__class__.__container_priority__: - return other.divide(self, *args, **kwargs) - return self.pixel_wise_binary(numpy.divide, other, *args, **kwargs) - - def power(self, other, *args, **kwargs): - return self.pixel_wise_binary(numpy.power, other, *args, **kwargs) - - def maximum(self, x2, *args, **kwargs): - return self.pixel_wise_binary(numpy.maximum, x2, *args, **kwargs) - - def minimum(self,x2, out=None, *args, **kwargs): - return self.pixel_wise_binary(numpy.minimum, x2=x2, out=out, *args, **kwargs) - - - ## unary operations - def pixel_wise_unary(self, pwop, *args, **kwargs): - out = kwargs.get('out', None) - if out is None: - out = pwop(self.as_array() , *args, **kwargs ) - return type(self)(out, - deep_copy=False, - dimension_labels=self.dimension_labels, - geometry=self.geometry) - elif issubclass(type(out), DataContainer): - if self.check_dimensions(out): - kwargs['out'] = out.as_array() - pwop(self.as_array(), *args, **kwargs ) - else: - raise ValueError(message(type(self),"Wrong size for data memory: ", out.shape,self.shape)) - elif issubclass(type(out), numpy.ndarray): - if self.array.shape == out.shape and self.array.dtype == out.dtype: - kwargs['out'] = out - pwop(self.as_array(), *args, **kwargs) - else: - raise ValueError (message(type(self), "incompatible class:" , pwop.__name__, type(out))) - - def abs(self, *args, **kwargs): - return self.pixel_wise_unary(numpy.abs, *args, **kwargs) - - def sign(self, *args, **kwargs): - return self.pixel_wise_unary(numpy.sign, *args, **kwargs) - - def sqrt(self, *args, **kwargs): - return self.pixel_wise_unary(numpy.sqrt, *args, **kwargs) - - def conjugate(self, *args, **kwargs): - return self.pixel_wise_unary(numpy.conjugate, *args, **kwargs) - #def __abs__(self): - # operation = FM.OPERATION.ABS - # return self.callFieldMath(operation, None, self.mask, self.maskOnValue) - # __abs__ - - ## reductions - def sum(self, *args, **kwargs): - return self.as_array().sum(*args, **kwargs) - def squared_norm(self): - '''return the squared euclidean norm of the DataContainer viewed as a vector''' - #shape = self.shape - #size = reduce(lambda x,y:x*y, shape, 1) - #y = numpy.reshape(self.as_array(), (size, )) - return self.dot(self.conjugate()) - #return self.dot(self) - def norm(self): - '''return the euclidean norm of the DataContainer viewed as a vector''' - return numpy.sqrt(self.squared_norm()) - - - def dot(self, other, *args, **kwargs): - '''return the inner product of 2 DataContainers viewed as vectors''' - method = kwargs.get('method', 'reduce') - - if method not in ['numpy','reduce']: - raise ValueError('dot: specified method not valid. Expecting numpy or reduce got {} '.format( - method)) - - if self.shape == other.shape: - # return (self*other).sum() - if method == 'numpy': - return numpy.dot(self.as_array().ravel(), other.as_array()) - elif method == 'reduce': - # see https://github.com/vais-ral/CCPi-Framework/pull/273 - # notice that Python seems to be smart enough to use - # the appropriate type to hold the result of the reduction - sf = reduce(lambda x,y: x + y[0]*y[1], - zip(self.as_array().ravel(), - other.as_array().ravel()), - 0) - return sf - else: - raise ValueError('Shapes are not aligned: {} != {}'.format(self.shape, other.shape)) - - - - - -class ImageData(DataContainer): - '''DataContainer for holding 2D or 3D DataContainer''' - __container_priority__ = 1 - - - def __init__(self, - array = None, - deep_copy=False, - dimension_labels=None, - **kwargs): - - self.geometry = kwargs.get('geometry', None) - if array is None: - if self.geometry is not None: - shape, dimension_labels = self.get_shape_labels(self.geometry) - - array = numpy.zeros( shape , dtype=numpy.float32) - super(ImageData, self).__init__(array, deep_copy, - dimension_labels, **kwargs) - - else: - raise ValueError('Please pass either a DataContainer, ' +\ - 'a numpy array or a geometry') - else: - if self.geometry is not None: - shape, labels = self.get_shape_labels(self.geometry, dimension_labels) - if array.shape != shape: - raise ValueError('Shape mismatch {} {}'.format(shape, array.shape)) - - if issubclass(type(array) , DataContainer): - # if the array is a DataContainer get the info from there - if not ( array.number_of_dimensions == 2 or \ - array.number_of_dimensions == 3 or \ - array.number_of_dimensions == 4): - raise ValueError('Number of dimensions are not 2 or 3 or 4: {0}'\ - .format(array.number_of_dimensions)) - - #DataContainer.__init__(self, array.as_array(), deep_copy, - # array.dimension_labels, **kwargs) - super(ImageData, self).__init__(array.as_array(), deep_copy, - array.dimension_labels, **kwargs) - elif issubclass(type(array) , numpy.ndarray): - if not ( array.ndim == 2 or array.ndim == 3 or array.ndim == 4 ): - raise ValueError( - 'Number of dimensions are not 2 or 3 or 4 : {0}'\ - .format(array.ndim)) - - if dimension_labels is None: - if array.ndim == 4: - dimension_labels = [ImageGeometry.CHANNEL, - ImageGeometry.VERTICAL, - ImageGeometry.HORIZONTAL_Y, - ImageGeometry.HORIZONTAL_X] - elif array.ndim == 3: - dimension_labels = [ImageGeometry.VERTICAL, - ImageGeometry.HORIZONTAL_Y, - ImageGeometry.HORIZONTAL_X] - else: - dimension_labels = [ ImageGeometry.HORIZONTAL_Y, - ImageGeometry.HORIZONTAL_X] - - #DataContainer.__init__(self, array, deep_copy, dimension_labels, **kwargs) - super(ImageData, self).__init__(array, deep_copy, - dimension_labels, **kwargs) - - # load metadata from kwargs if present - for key, value in kwargs.items(): - if (type(value) == list or type(value) == tuple) and \ - ( len (value) == 3 and len (value) == 2) : - if key == 'origin' : - self.origin = value - if key == 'spacing' : - self.spacing = value - - def subset(self, dimensions=None, **kw): - # FIXME: this is clearly not rigth - # it should be something like - # out = DataContainer.subset(self, dimensions, **kw) - # followed by regeneration of the proper geometry. - out = super(ImageData, self).subset(dimensions, **kw) - #out.geometry = self.recalculate_geometry(dimensions , **kw) - out.geometry = self.geometry - return out - - def get_shape_labels(self, geometry, dimension_labels=None): - channels = geometry.channels - horiz_x = geometry.voxel_num_x - horiz_y = geometry.voxel_num_y - vert = 1 if geometry.voxel_num_z is None\ - else geometry.voxel_num_z # this should be 1 for 2D - if dimension_labels is None: - if channels > 1: - if vert > 1: - shape = (channels, vert, horiz_y, horiz_x) - dim_labels = [ImageGeometry.CHANNEL, - ImageGeometry.VERTICAL, - ImageGeometry.HORIZONTAL_Y, - ImageGeometry.HORIZONTAL_X] - else: - shape = (channels , horiz_y, horiz_x) - dim_labels = [ImageGeometry.CHANNEL, - ImageGeometry.HORIZONTAL_Y, - ImageGeometry.HORIZONTAL_X] - else: - if vert > 1: - shape = (vert, horiz_y, horiz_x) - dim_labels = [ImageGeometry.VERTICAL, - ImageGeometry.HORIZONTAL_Y, - ImageGeometry.HORIZONTAL_X] - else: - shape = (horiz_y, horiz_x) - dim_labels = [ImageGeometry.HORIZONTAL_Y, - ImageGeometry.HORIZONTAL_X] - dimension_labels = dim_labels - else: - shape = [] - for i in range(len(dimension_labels)): - dim = dimension_labels[i] - if dim == ImageGeometry.CHANNEL: - shape.append(channels) - elif dim == ImageGeometry.HORIZONTAL_Y: - shape.append(horiz_y) - elif dim == ImageGeometry.VERTICAL: - shape.append(vert) - elif dim == ImageGeometry.HORIZONTAL_X: - shape.append(horiz_x) - if len(shape) != len(dimension_labels): - raise ValueError('Missing {0} axes {1} shape {2}'.format( - len(dimension_labels) - len(shape), dimension_labels, shape)) - shape = tuple(shape) - - return (shape, dimension_labels) - - -class AcquisitionData(DataContainer): - '''DataContainer for holding 2D or 3D sinogram''' - __container_priority__ = 1 - - - def __init__(self, - array = None, - deep_copy=True, - dimension_labels=None, - **kwargs): - self.geometry = kwargs.get('geometry', None) - if array is None: - if 'geometry' in kwargs.keys(): - geometry = kwargs['geometry'] - self.geometry = geometry - - shape, dimension_labels = self.get_shape_labels(geometry, dimension_labels) - - - array = numpy.zeros( shape , dtype=numpy.float32) - super(AcquisitionData, self).__init__(array, deep_copy, - dimension_labels, **kwargs) - else: - if self.geometry is not None: - shape, labels = self.get_shape_labels(self.geometry, dimension_labels) - if array.shape != shape: - raise ValueError('Shape mismatch {} {}'.format(shape, array.shape)) - - if issubclass(type(array) ,DataContainer): - # if the array is a DataContainer get the info from there - if not ( array.number_of_dimensions == 2 or \ - array.number_of_dimensions == 3 or \ - array.number_of_dimensions == 4): - raise ValueError('Number of dimensions are not 2 or 3 or 4: {0}'\ - .format(array.number_of_dimensions)) - - #DataContainer.__init__(self, array.as_array(), deep_copy, - # array.dimension_labels, **kwargs) - super(AcquisitionData, self).__init__(array.as_array(), deep_copy, - array.dimension_labels, **kwargs) - elif issubclass(type(array) ,numpy.ndarray): - if not ( array.ndim == 2 or array.ndim == 3 or array.ndim == 4 ): - raise ValueError( - 'Number of dimensions are not 2 or 3 or 4 : {0}'\ - .format(array.ndim)) - - if dimension_labels is None: - if array.ndim == 4: - dimension_labels = [AcquisitionGeometry.CHANNEL, - AcquisitionGeometry.ANGLE, - AcquisitionGeometry.VERTICAL, - AcquisitionGeometry.HORIZONTAL] - elif array.ndim == 3: - dimension_labels = [AcquisitionGeometry.ANGLE, - AcquisitionGeometry.VERTICAL, - AcquisitionGeometry.HORIZONTAL] - else: - dimension_labels = [AcquisitionGeometry.ANGLE, - AcquisitionGeometry.HORIZONTAL] - - super(AcquisitionData, self).__init__(array, deep_copy, - dimension_labels, **kwargs) - - def get_shape_labels(self, geometry, dimension_labels=None): - channels = geometry.channels - horiz = geometry.pixel_num_h - vert = geometry.pixel_num_v - angles = geometry.angles - num_of_angles = numpy.shape(angles)[0] - - if dimension_labels is None: - if channels > 1: - if vert > 1: - shape = (channels, num_of_angles , vert, horiz) - dim_labels = [AcquisitionGeometry.CHANNEL, - AcquisitionGeometry.ANGLE, - AcquisitionGeometry.VERTICAL, - AcquisitionGeometry.HORIZONTAL] - else: - shape = (channels , num_of_angles, horiz) - dim_labels = [AcquisitionGeometry.CHANNEL, - AcquisitionGeometry.ANGLE, - AcquisitionGeometry.HORIZONTAL] - else: - if vert > 1: - shape = (num_of_angles, vert, horiz) - dim_labels = [AcquisitionGeometry.ANGLE, - AcquisitionGeometry.VERTICAL, - AcquisitionGeometry.HORIZONTAL - ] - else: - shape = (num_of_angles, horiz) - dim_labels = [AcquisitionGeometry.ANGLE, - AcquisitionGeometry.HORIZONTAL - ] - - dimension_labels = dim_labels - else: - shape = [] - for i in range(len(dimension_labels)): - dim = dimension_labels[i] - - if dim == AcquisitionGeometry.CHANNEL: - shape.append(channels) - elif dim == AcquisitionGeometry.ANGLE: - shape.append(num_of_angles) - elif dim == AcquisitionGeometry.VERTICAL: - shape.append(vert) - elif dim == AcquisitionGeometry.HORIZONTAL: - shape.append(horiz) - if len(shape) != len(dimension_labels): - raise ValueError('Missing {0} axes.\nExpected{1} got {2}'\ - .format( - len(dimension_labels) - len(shape), - dimension_labels, shape) - ) - shape = tuple(shape) - return (shape, dimension_labels) - - - -class DataProcessor(object): - - '''Defines a generic DataContainer processor - - accepts DataContainer as inputs and - outputs DataContainer - additional attributes can be defined with __setattr__ - ''' - - def __init__(self, **attributes): - if not 'store_output' in attributes.keys(): - attributes['store_output'] = True - attributes['output'] = False - attributes['runTime'] = -1 - attributes['mTime'] = datetime.now() - attributes['input'] = None - for key, value in attributes.items(): - self.__dict__[key] = value - - - def __setattr__(self, name, value): - if name == 'input': - self.set_input(value) - elif name in self.__dict__.keys(): - self.__dict__[name] = value - self.__dict__['mTime'] = datetime.now() - else: - raise KeyError('Attribute {0} not found'.format(name)) - #pass - - def set_input(self, dataset): - if issubclass(type(dataset), DataContainer): - if self.check_input(dataset): - self.__dict__['input'] = dataset - else: - raise TypeError("Input type mismatch: got {0} expecting {1}"\ - .format(type(dataset), DataContainer)) - - def check_input(self, dataset): - '''Checks parameters of the input DataContainer - - Should raise an Error if the DataContainer does not match expectation, e.g. - if the expected input DataContainer is 3D and the Processor expects 2D. - ''' - raise NotImplementedError('Implement basic checks for input DataContainer') - - def get_output(self, out=None): - - for k,v in self.__dict__.items(): - if v is None and k != 'output': - raise ValueError('Key {0} is None'.format(k)) - shouldRun = False - if self.runTime == -1: - shouldRun = True - elif self.mTime > self.runTime: - shouldRun = True - - # CHECK this - if self.store_output and shouldRun: - self.runTime = datetime.now() - try: - self.output = self.process(out=out) - return self.output - except TypeError as te: - self.output = self.process() - return self.output - self.runTime = datetime.now() - try: - return self.process(out=out) - except TypeError as te: - return self.process() - - - def set_input_processor(self, processor): - if issubclass(type(processor), DataProcessor): - self.__dict__['input'] = processor - else: - raise TypeError("Input type mismatch: got {0} expecting {1}"\ - .format(type(processor), DataProcessor)) - - def get_input(self): - '''returns the input DataContainer - - It is useful in the case the user has provided a DataProcessor as - input - ''' - if issubclass(type(self.input), DataProcessor): - dsi = self.input.get_output() - else: - dsi = self.input - return dsi - - def process(self, out=None): - raise NotImplementedError('process must be implemented') - - - - -class DataProcessor23D(DataProcessor): - '''Regularizers DataProcessor - ''' - - def check_input(self, dataset): - '''Checks number of dimensions input DataContainer - - Expected input is 2D or 3D - ''' - if dataset.number_of_dimensions == 2 or \ - dataset.number_of_dimensions == 3: - return True - else: - raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ - .format(dataset.number_of_dimensions)) - -###### Example of DataProcessors - -class AX(DataProcessor): - '''Example DataProcessor - The AXPY routines perform a vector multiplication operation defined as - - y := a*x - where: - - a is a scalar - - x a DataContainer. - ''' - - def __init__(self): - kwargs = {'scalar':None, - 'input':None, - } - - #DataProcessor.__init__(self, **kwargs) - super(AX, self).__init__(**kwargs) - - def check_input(self, dataset): - return True - - def process(self, out=None): - - dsi = self.get_input() - a = self.scalar - if out is None: - y = DataContainer( a * dsi.as_array() , True, - dimension_labels=dsi.dimension_labels ) - #self.setParameter(output_dataset=y) - return y - else: - out.fill(a * dsi.as_array()) - - -###### Example of DataProcessors - -class CastDataContainer(DataProcessor): - '''Example DataProcessor - Cast a DataContainer array to a different type. - - y := a*x - where: - - a is a scalar - - x a DataContainer. - ''' - - def __init__(self, dtype=None): - kwargs = {'dtype':dtype, - 'input':None, - } - - #DataProcessor.__init__(self, **kwargs) - super(CastDataContainer, self).__init__(**kwargs) - - def check_input(self, dataset): - return True - - def process(self, out=None): - - dsi = self.get_input() - dtype = self.dtype - if out is None: - y = numpy.asarray(dsi.as_array(), dtype=dtype) - - return type(dsi)(numpy.asarray(dsi.as_array(), dtype=dtype), - dimension_labels=dsi.dimension_labels ) - else: - out.fill(numpy.asarray(dsi.as_array(), dtype=dtype)) - - - - - -class PixelByPixelDataProcessor(DataProcessor): - '''Example DataProcessor - - This processor applies a python function to each pixel of the DataContainer - - f is a python function - - x a DataSet. - ''' - - def __init__(self): - kwargs = {'pyfunc':None, - 'input':None, - } - #DataProcessor.__init__(self, **kwargs) - super(PixelByPixelDataProcessor, self).__init__(**kwargs) - - def check_input(self, dataset): - return True - - def process(self, out=None): - - pyfunc = self.pyfunc - dsi = self.get_input() - - eval_func = numpy.frompyfunc(pyfunc,1,1) - - - y = DataContainer( eval_func( dsi.as_array() ) , True, - dimension_labels=dsi.dimension_labels ) - return y - - - - -if __name__ == '__main__': - shape = (2,3,4,5) - size = shape[0] - for i in range(1, len(shape)): - size = size * shape[i] - #print("a refcount " , sys.getrefcount(a)) - a = numpy.asarray([i for i in range( size )]) - print("a refcount " , sys.getrefcount(a)) - a = numpy.reshape(a, shape) - print("a refcount " , sys.getrefcount(a)) - ds = DataContainer(a, False, ['X', 'Y','Z' ,'W']) - print("a refcount " , sys.getrefcount(a)) - print ("ds label {0}".format(ds.dimension_labels)) - subset = ['W' ,'X'] - b = ds.subset( subset ) - print("a refcount " , sys.getrefcount(a)) - print ("b label {0} shape {1}".format(b.dimension_labels, - numpy.shape(b.as_array()))) - c = ds.subset(['Z','W','X']) - print("a refcount " , sys.getrefcount(a)) - - # Create a ImageData sharing the array with c - volume0 = ImageData(c.as_array(), False, dimensions = c.dimension_labels) - volume1 = ImageData(c, False) - - print ("volume0 {0} volume1 {1}".format(id(volume0.array), - id(volume1.array))) - - # Create a ImageData copying the array from c - volume2 = ImageData(c.as_array(), dimensions = c.dimension_labels) - volume3 = ImageData(c) - - print ("volume2 {0} volume3 {1}".format(id(volume2.array), - id(volume3.array))) - - # single number DataSet - sn = DataContainer(numpy.asarray([1])) - - ax = AX() - ax.scalar = 2 - ax.set_input(c) - #ax.apply() - print ("ax in {0} out {1}".format(c.as_array().flatten(), - ax.get_output().as_array().flatten())) - - cast = CastDataContainer(dtype=numpy.float32) - cast.set_input(c) - out = cast.get_output() - out *= 0 - axm = AX() - axm.scalar = 0.5 - axm.set_input_processor(cast) - axm.get_output(out) - #axm.apply() - print ("axm in {0} out {1}".format(c.as_array(), axm.get_output().as_array())) - - # check out in DataSetProcessor - #a = numpy.asarray([i for i in range( size )]) - - - # create a PixelByPixelDataProcessor - - #define a python function which will take only one input (the pixel value) - pyfunc = lambda x: -x if x > 20 else x - clip = PixelByPixelDataProcessor() - clip.pyfunc = pyfunc - clip.set_input(c) - #clip.apply() - - print ("clip in {0} out {1}".format(c.as_array(), clip.get_output().as_array())) - - #dsp = DataProcessor() - #dsp.set_input(ds) - #dsp.input = a - # pipeline - - chain = AX() - chain.scalar = 0.5 - chain.set_input_processor(ax) - print ("chain in {0} out {1}".format(ax.get_output().as_array(), chain.get_output().as_array())) - - # testing arithmetic operations - - print (b) - print ((b+1)) - print ((1+b)) - - print (b) - print ((b*2)) - - print (b) - print ((2*b)) - - print (b) - print ((b/2)) - - print (b) - print ((2/b)) - - print (b) - print ((b**2)) - - print (b) - print ((2**b)) - - print (type(volume3 + 2)) - - s = [i for i in range(3 * 4 * 4)] - s = numpy.reshape(numpy.asarray(s), (3,4,4)) - sino = AcquisitionData( s ) - - shape = (4,3,2) - a = [i for i in range(2*3*4)] - a = numpy.asarray(a) - a = numpy.reshape(a, shape) - print (numpy.shape(a)) - ds = DataContainer(a, True, ['X', 'Y','Z']) - # this means that I expect the X to be of length 2 , - # y of length 3 and z of length 4 - subset = ['Y' ,'Z'] - b0 = ds.subset( subset ) - print ("shape b 3,2? {0}".format(numpy.shape(b0.as_array()))) - # expectation on b is that it is - # 3x2 cut at z = 0 - - subset = ['X' ,'Y'] - b1 = ds.subset( subset , Z=1) - print ("shape b 2,3? {0}".format(numpy.shape(b1.as_array()))) - - - - # create VolumeData from geometry - vgeometry = ImageGeometry(voxel_num_x=2, voxel_num_y=3, channels=2) - vol = ImageData(geometry=vgeometry) - - sgeometry = AcquisitionGeometry(dimension=2, angles=numpy.linspace(0, 180, num=20), - geom_type='parallel', pixel_num_v=3, - pixel_num_h=5 , channels=2) - sino = AcquisitionData(geometry=sgeometry) - sino2 = sino.clone() - - a0 = numpy.asarray([i for i in range(2*3*4)]) - a1 = numpy.asarray([2*i for i in range(2*3*4)]) - - - ds0 = DataContainer(numpy.reshape(a0,(2,3,4))) - ds1 = DataContainer(numpy.reshape(a1,(2,3,4))) - - numpy.testing.assert_equal(ds0.dot(ds1), a0.dot(a1)) - - a2 = numpy.asarray([2*i for i in range(2*3*5)]) - ds2 = DataContainer(numpy.reshape(a2,(2,3,5))) - -# # it should fail if the shape is wrong -# try: -# ds2.dot(ds0) -# self.assertTrue(False) -# except ValueError as ve: -# self.assertTrue(True) - diff --git a/Wrappers/Python/build/lib/ccpi/io/__init__.py b/Wrappers/Python/build/lib/ccpi/io/__init__.py deleted file mode 100644 index 9233d7a..0000000 --- a/Wrappers/Python/build/lib/ccpi/io/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/io/reader.py b/Wrappers/Python/build/lib/ccpi/io/reader.py deleted file mode 100644 index 07e3bf9..0000000 --- a/Wrappers/Python/build/lib/ccpi/io/reader.py +++ /dev/null @@ -1,511 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Jakob Jorgensen, Daniil Kazantsev, Edoardo Pasca and Srikanth Nagella - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -''' -This is a reader module with classes for loading 3D datasets. - -@author: Mr. Srikanth Nagella -''' -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from ccpi.framework import AcquisitionGeometry -from ccpi.framework import AcquisitionData -import numpy as np -import os - -h5pyAvailable = True -try: - from h5py import File as NexusFile -except: - h5pyAvailable = False - -pilAvailable = True -try: - from PIL import Image -except: - pilAvailable = False - -class NexusReader(object): - ''' - Reader class for loading Nexus files. - ''' - - def __init__(self, nexus_filename=None): - ''' - This takes in input as filename and loads the data dataset. - ''' - self.flat = None - self.dark = None - self.angles = None - self.geometry = None - self.filename = nexus_filename - self.key_path = 'entry1/tomo_entry/instrument/detector/image_key' - self.data_path = 'entry1/tomo_entry/data/data' - self.angle_path = 'entry1/tomo_entry/data/rotation_angle' - - def get_image_keys(self): - try: - with NexusFile(self.filename,'r') as file: - return np.array(file[self.key_path]) - except KeyError as ke: - raise KeyError("get_image_keys: " , ke.args[0] , self.key_path) - - - def load(self, dimensions=None, image_key_id=0): - ''' - This is generic loading function of flat field, dark field and projection data. - ''' - if not h5pyAvailable: - raise Exception("Error: h5py is not installed") - if self.filename is None: - return - try: - with NexusFile(self.filename,'r') as file: - image_keys = np.array(file[self.key_path]) - projections = None - if dimensions == None: - projections = np.array(file[self.data_path]) - result = projections[image_keys==image_key_id] - return result - else: - #When dimensions are specified they need to be mapped to image_keys - index_array = np.where(image_keys==image_key_id) - projection_indexes = index_array[0][dimensions[0]] - new_dimensions = list(dimensions) - new_dimensions[0]= projection_indexes - new_dimensions = tuple(new_dimensions) - result = np.array(file[self.data_path][new_dimensions]) - return result - except: - print("Error reading nexus file") - raise - - def load_projection(self, dimensions=None): - ''' - Loads the projection data from the nexus file. - returns: numpy array with projection data - ''' - try: - if 0 not in self.get_image_keys(): - raise ValueError("Projections are not in the data. Data Path " , - self.data_path) - except KeyError as ke: - raise KeyError(ke.args[0] , self.data_path) - return self.load(dimensions, 0) - - def load_flat(self, dimensions=None): - ''' - Loads the flat field data from the nexus file. - returns: numpy array with flat field data - ''' - try: - if 1 not in self.get_image_keys(): - raise ValueError("Flats are not in the data. Data Path " , - self.data_path) - except KeyError as ke: - raise KeyError(ke.args[0] , self.data_path) - return self.load(dimensions, 1) - - def load_dark(self, dimensions=None): - ''' - Loads the Dark field data from the nexus file. - returns: numpy array with dark field data - ''' - try: - if 2 not in self.get_image_keys(): - raise ValueError("Darks are not in the data. Data Path " , - self.data_path) - except KeyError as ke: - raise KeyError(ke.args[0] , self.data_path) - return self.load(dimensions, 2) - - def get_projection_angles(self): - ''' - This function returns the projection angles - ''' - if not h5pyAvailable: - raise Exception("Error: h5py is not installed") - if self.filename is None: - return - try: - with NexusFile(self.filename,'r') as file: - angles = np.array(file[self.angle_path],np.float32) - image_keys = np.array(file[self.key_path]) - return angles[image_keys==0] - except: - print("get_projection_angles Error reading nexus file") - raise - - - def get_sinogram_dimensions(self): - ''' - Return the dimensions of the dataset - ''' - if not h5pyAvailable: - raise Exception("Error: h5py is not installed") - if self.filename is None: - return - try: - with NexusFile(self.filename,'r') as file: - projections = file[self.data_path] - image_keys = np.array(file[self.key_path]) - dims = list(projections.shape) - dims[0] = dims[1] - dims[1] = np.sum(image_keys==0) - return tuple(dims) - except: - print("Error reading nexus file") - raise - - def get_projection_dimensions(self): - ''' - Return the dimensions of the dataset - ''' - if not h5pyAvailable: - raise Exception("Error: h5py is not installed") - if self.filename is None: - return - try: - with NexusFile(self.filename,'r') as file: - try: - projections = file[self.data_path] - except KeyError as ke: - raise KeyError('Error: data path {0} not found\n{1}'\ - .format(self.data_path, - ke.args[0])) - #image_keys = np.array(file[self.key_path]) - image_keys = self.get_image_keys() - dims = list(projections.shape) - dims[0] = np.sum(image_keys==0) - return tuple(dims) - except: - print("Warning: Error reading image_keys trying accessing data on " , self.data_path) - with NexusFile(self.filename,'r') as file: - dims = file[self.data_path].shape - return tuple(dims) - - - - def get_acquisition_data(self, dimensions=None): - ''' - This method load the acquisition data and given dimension and returns an AcquisitionData Object - ''' - data = self.load_projection(dimensions) - dims = self.get_projection_dimensions() - geometry = AcquisitionGeometry('parallel', '3D', - self.get_projection_angles(), - pixel_num_h = dims[2], - pixel_size_h = 1 , - pixel_num_v = dims[1], - pixel_size_v = 1, - dist_source_center = None, - dist_center_detector = None, - channels = 1) - return AcquisitionData(data, geometry=geometry, - dimension_labels=['angle','vertical','horizontal']) - - def get_acquisition_data_subset(self, ymin=None, ymax=None): - ''' - This method load the acquisition data and given dimension and returns an AcquisitionData Object - ''' - if not h5pyAvailable: - raise Exception("Error: h5py is not installed") - if self.filename is None: - return - try: - - - with NexusFile(self.filename,'r') as file: - try: - dims = self.get_projection_dimensions() - except KeyError: - pass - dims = file[self.data_path].shape - if ymin is None and ymax is None: - - try: - image_keys = self.get_image_keys() - print ("image_keys", image_keys) - projections = np.array(file[self.data_path]) - data = projections[image_keys==0] - except KeyError as ke: - print (ke) - data = np.array(file[self.data_path]) - - else: - image_keys = self.get_image_keys() - print ("image_keys", image_keys) - projections = np.array(file[self.data_path])[image_keys==0] - if ymin is None: - ymin = 0 - if ymax > dims[1]: - raise ValueError('ymax out of range') - data = projections[:,:ymax,:] - elif ymax is None: - ymax = dims[1] - if ymin < 0: - raise ValueError('ymin out of range') - data = projections[:,ymin:,:] - else: - if ymax > dims[1]: - raise ValueError('ymax out of range') - if ymin < 0: - raise ValueError('ymin out of range') - - data = projections[: , ymin:ymax , :] - - except: - print("Error reading nexus file") - raise - - - try: - angles = self.get_projection_angles() - except KeyError as ke: - n = data.shape[0] - angles = np.linspace(0, n, n+1, dtype=np.float32) - - if ymax-ymin > 1: - - geometry = AcquisitionGeometry('parallel', '3D', - angles, - pixel_num_h = dims[2], - pixel_size_h = 1 , - pixel_num_v = ymax-ymin, - pixel_size_v = 1, - dist_source_center = None, - dist_center_detector = None, - channels = 1) - return AcquisitionData(data, False, geometry=geometry, - dimension_labels=['angle','vertical','horizontal']) - elif ymax-ymin == 1: - geometry = AcquisitionGeometry('parallel', '2D', - angles, - pixel_num_h = dims[2], - pixel_size_h = 1 , - dist_source_center = None, - dist_center_detector = None, - channels = 1) - return AcquisitionData(data.squeeze(), False, geometry=geometry, - dimension_labels=['angle','horizontal']) - def get_acquisition_data_slice(self, y_slice=0): - return self.get_acquisition_data_subset(ymin=y_slice , ymax=y_slice+1) - def get_acquisition_data_whole(self): - with NexusFile(self.filename,'r') as file: - try: - dims = self.get_projection_dimensions() - except KeyError: - print ("Warning: ") - dims = file[self.data_path].shape - - ymin = 0 - ymax = dims[1] - 1 - - return self.get_acquisition_data_subset(ymin=ymin, ymax=ymax) - - - - def list_file_content(self): - try: - with NexusFile(self.filename,'r') as file: - file.visit(print) - except: - print("Error reading nexus file") - raise - def get_acquisition_data_batch(self, bmin=None, bmax=None): - if not h5pyAvailable: - raise Exception("Error: h5py is not installed") - if self.filename is None: - return - try: - - - with NexusFile(self.filename,'r') as file: - try: - dims = self.get_projection_dimensions() - except KeyError: - dims = file[self.data_path].shape - if bmin is None or bmax is None: - raise ValueError('get_acquisition_data_batch: please specify fastest index batch limits') - - if bmin >= 0 and bmin < bmax and bmax <= dims[0]: - data = np.array(file[self.data_path][bmin:bmax]) - else: - raise ValueError('get_acquisition_data_batch: bmin {0}>0 bmax {1}<{2}'.format(bmin, bmax, dims[0])) - - except: - print("Error reading nexus file") - raise - - - try: - angles = self.get_projection_angles()[bmin:bmax] - except KeyError as ke: - n = data.shape[0] - angles = np.linspace(0, n, n+1, dtype=np.float32)[bmin:bmax] - - if bmax-bmin > 1: - - geometry = AcquisitionGeometry('parallel', '3D', - angles, - pixel_num_h = dims[2], - pixel_size_h = 1 , - pixel_num_v = bmax-bmin, - pixel_size_v = 1, - dist_source_center = None, - dist_center_detector = None, - channels = 1) - return AcquisitionData(data, False, geometry=geometry, - dimension_labels=['angle','vertical','horizontal']) - elif bmax-bmin == 1: - geometry = AcquisitionGeometry('parallel', '2D', - angles, - pixel_num_h = dims[2], - pixel_size_h = 1 , - dist_source_center = None, - dist_center_detector = None, - channels = 1) - return AcquisitionData(data.squeeze(), False, geometry=geometry, - dimension_labels=['angle','horizontal']) - - - -class XTEKReader(object): - ''' - Reader class for loading XTEK files - ''' - - def __init__(self, xtek_config_filename=None): - ''' - This takes in the xtek config filename and loads the dataset and the - required geometry parameters - ''' - self.projections = None - self.geometry = {} - self.filename = xtek_config_filename - self.load() - - def load(self): - pixel_num_h = 0 - pixel_num_v = 0 - xpixel_size = 0 - ypixel_size = 0 - source_x = 0 - detector_x = 0 - with open(self.filename) as f: - content = f.readlines() - content = [x.strip() for x in content] - for line in content: - if line.startswith("SrcToObject"): - source_x = float(line.split('=')[1]) - elif line.startswith("SrcToDetector"): - detector_x = float(line.split('=')[1]) - elif line.startswith("DetectorPixelsY"): - pixel_num_v = int(line.split('=')[1]) - #self.num_of_vertical_pixels = self.calc_v_alighment(self.num_of_vertical_pixels, self.pixels_per_voxel) - elif line.startswith("DetectorPixelsX"): - pixel_num_h = int(line.split('=')[1]) - elif line.startswith("DetectorPixelSizeX"): - xpixel_size = float(line.split('=')[1]) - elif line.startswith("DetectorPixelSizeY"): - ypixel_size = float(line.split('=')[1]) - elif line.startswith("Projections"): - self.num_projections = int(line.split('=')[1]) - elif line.startswith("InitialAngle"): - self.initial_angle = float(line.split('=')[1]) - elif line.startswith("Name"): - self.experiment_name = line.split('=')[1] - elif line.startswith("Scattering"): - self.scattering = float(line.split('=')[1]) - elif line.startswith("WhiteLevel"): - self.white_level = float(line.split('=')[1]) - elif line.startswith("MaskRadius"): - self.mask_radius = float(line.split('=')[1]) - - #Read Angles - angles = self.read_angles() - self.geometry = AcquisitionGeometry('cone', '3D', angles, pixel_num_h, xpixel_size, pixel_num_v, ypixel_size, -1 * source_x, - detector_x - source_x, - ) - - def read_angles(self): - """ - Read the angles file .ang or _ctdata.txt file and returns the angles - as an numpy array. - """ - input_path = os.path.dirname(self.filename) - angles_ctdata_file = os.path.join(input_path, '_ctdata.txt') - angles_named_file = os.path.join(input_path, self.experiment_name+'.ang') - angles = np.zeros(self.num_projections,dtype='f') - #look for _ctdata.txt - if os.path.exists(angles_ctdata_file): - #read txt file with angles - with open(angles_ctdata_file) as f: - content = f.readlines() - #skip firt three lines - #read the middle value of 3 values in each line as angles in degrees - index = 0 - for line in content[3:]: - self.angles[index]=float(line.split(' ')[1]) - index+=1 - angles = np.deg2rad(self.angles+self.initial_angle); - elif os.path.exists(angles_named_file): - #read the angles file which is text with first line as header - with open(angles_named_file) as f: - content = f.readlines() - #skip first line - index = 0 - for line in content[1:]: - angles[index] = float(line.split(':')[1]) - index+=1 - angles = np.flipud(angles+self.initial_angle) #angles are in the reverse order - else: - raise RuntimeError("Can't find angles file") - return angles - - def load_projection(self, dimensions=None): - ''' - This method reads the projection images from the directory and returns a numpy array - ''' - if not pilAvailable: - raise('Image library pillow is not installed') - if dimensions != None: - raise('Extracting subset of data is not implemented') - input_path = os.path.dirname(self.filename) - pixels = np.zeros((self.num_projections, self.geometry.pixel_num_h, self.geometry.pixel_num_v), dtype='float32') - for i in range(1, self.num_projections+1): - im = Image.open(os.path.join(input_path,self.experiment_name+"_%04d"%i+".tif")) - pixels[i-1,:,:] = np.fliplr(np.transpose(np.array(im))) ##Not sure this is the correct way to populate the image - - #normalising the data - #TODO: Move this to a processor - pixels = pixels - (self.white_level*self.scattering)/100.0 - pixels[pixels < 0.0] = 0.000001 # all negative values to approximately 0 as the std log of zero and non negative number is not defined - return pixels - - def get_acquisition_data(self, dimensions=None): - ''' - This method load the acquisition data and given dimension and returns an AcquisitionData Object - ''' - data = self.load_projection(dimensions) - return AcquisitionData(data, geometry=self.geometry) - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/__init__.py b/Wrappers/Python/build/lib/ccpi/optimisation/__init__.py deleted file mode 100644 index cf2d93d..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py deleted file mode 100644 index a14378c..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/Algorithm.py +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2019 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import time -from numbers import Integral - -class Algorithm(object): - '''Base class for iterative algorithms - - provides the minimal infrastructure. - Algorithms are iterables so can be easily run in a for loop. They will - stop as soon as the stop cryterion is met. - The user is required to implement the set_up, __init__, update and - and update_objective methods - - A courtesy method run is available to run n iterations. The method accepts - a callback function that receives the current iteration number and the actual objective - value and can be used to trigger print to screens and other user interactions. The run - method will stop when the stopping cryterion is met. - ''' - - def __init__(self, **kwargs): - '''Constructor - - Set the minimal number of parameters: - iteration: current iteration number - max_iteration: maximum number of iterations - memopt: whether to use memory optimisation () - timing: list to hold the times it took to run each iteration - update_objectice_interval: the interval every which we would save the current - objective. 1 means every iteration, 2 every 2 iteration - and so forth. This is by default 1 and should be increased - when evaluating the objective is computationally expensive. - ''' - self.iteration = 0 - self.__max_iteration = kwargs.get('max_iteration', 0) - self.__loss = [] - self.memopt = False - self.timing = [] - self.update_objective_interval = kwargs.get('update_objective_interval', 1) - def set_up(self, *args, **kwargs): - '''Set up the algorithm''' - raise NotImplementedError() - def update(self): - '''A single iteration of the algorithm''' - raise NotImplementedError() - - def should_stop(self): - '''default stopping cryterion: number of iterations - - The user can change this in concrete implementatition of iterative algorithms.''' - return self.max_iteration_stop_cryterion() - - def max_iteration_stop_cryterion(self): - '''default stop cryterion for iterative algorithm: max_iteration reached''' - return self.iteration >= self.max_iteration - def __iter__(self): - '''Algorithm is an iterable''' - return self - def next(self): - '''Algorithm is an iterable - - python2 backwards compatibility''' - return self.__next__() - def __next__(self): - '''Algorithm is an iterable - - calling this method triggers update and update_objective - ''' - if self.should_stop(): - raise StopIteration() - else: - time0 = time.time() - self.update() - self.timing.append( time.time() - time0 ) - if self.iteration % self.update_objective_interval == 0: - self.update_objective() - self.iteration += 1 - - def get_output(self): - '''Returns the solution found''' - return self.x - - def get_last_loss(self): - '''Returns the last stored value of the loss function - - if update_objective_interval is 1 it is the value of the objective at the current - iteration. If update_objective_interval > 1 it is the last stored value. - ''' - return self.__loss[-1] - def get_last_objective(self): - '''alias to get_last_loss''' - return self.get_last_loss() - def update_objective(self): - '''calculates the objective with the current solution''' - raise NotImplementedError() - @property - def loss(self): - '''returns the list of the values of the objective during the iteration - - The length of this list may be shorter than the number of iterations run when - the update_objective_interval > 1 - ''' - return self.__loss - @property - def objective(self): - '''alias of loss''' - return self.loss - @property - def max_iteration(self): - '''gets the maximum number of iterations''' - return self.__max_iteration - @max_iteration.setter - def max_iteration(self, value): - '''sets the maximum number of iterations''' - assert isinstance(value, int) - self.__max_iteration = value - @property - def update_objective_interval(self): - return self.__update_objective_interval - @update_objective_interval.setter - def update_objective_interval(self, value): - if isinstance(value, Integral): - if value >= 1: - self.__update_objective_interval = value - else: - raise ValueError('Update objective interval must be an integer >= 1') - else: - raise ValueError('Update objective interval must be an integer >= 1') - def run(self, iterations, verbose=True, callback=None): - '''run n iterations and update the user with the callback if specified''' - if self.should_stop(): - print ("Stop cryterion has been reached.") - i = 0 - - for _ in self: - if (self.iteration -1) % self.update_objective_interval == 0: - if verbose: - print ("Iteration {}/{}, = {}".format(self.iteration-1, - self.max_iteration, self.get_last_objective()) ) - if callback is not None: - callback(self.iteration -1, self.get_last_objective(), self.x) - i += 1 - if i == iterations: - break - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/CGLS.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/CGLS.py deleted file mode 100644 index 4d4843c..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/CGLS.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Created on Thu Feb 21 11:11:23 2019 - -@author: ofn77899 -""" - -from ccpi.optimisation.algorithms import Algorithm - -class CGLS(Algorithm): - - '''Conjugate Gradient Least Squares algorithm - - Parameters: - x_init: initial guess - operator: operator for forward/backward projections - data: data to operate on - ''' - def __init__(self, **kwargs): - super(CGLS, self).__init__() - self.x = kwargs.get('x_init', None) - self.operator = kwargs.get('operator', None) - self.data = kwargs.get('data', None) - if self.x is not None and self.operator is not None and \ - self.data is not None: - print ("Calling from creator") - self.set_up(x_init =kwargs['x_init'], - operator=kwargs['operator'], - data =kwargs['data']) - - def set_up(self, x_init, operator , data ): - - self.r = data.copy() - self.x = x_init.copy() - - self.operator = operator - self.d = operator.adjoint(self.r) - - - self.normr2 = self.d.squared_norm() - #if isinstance(self.normr2, Iterable): - # self.normr2 = sum(self.normr2) - #self.normr2 = numpy.sqrt(self.normr2) - #print ("set_up" , self.normr2) - - def update(self): - - Ad = self.operator.direct(self.d) - #norm = (Ad*Ad).sum() - #if isinstance(norm, Iterable): - # norm = sum(norm) - norm = Ad.squared_norm() - - alpha = self.normr2/norm - self.x += (self.d * alpha) - self.r -= (Ad * alpha) - s = self.operator.adjoint(self.r) - - normr2_new = s.squared_norm() - #if isinstance(normr2_new, Iterable): - # normr2_new = sum(normr2_new) - #normr2_new = numpy.sqrt(normr2_new) - #print (normr2_new) - - beta = normr2_new/self.normr2 - self.normr2 = normr2_new - self.d = s + beta*self.d - - def update_objective(self): - self.loss.append(self.r.squared_norm()) \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FBPD.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FBPD.py deleted file mode 100644 index aa07359..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FBPD.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2019 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Created on Thu Feb 21 11:09:03 2019 - -@author: ofn77899 -""" - -from ccpi.optimisation.algorithms import Algorithm -from ccpi.optimisation.functions import ZeroFunction - -class FBPD(Algorithm): - '''FBPD Algorithm - - Parameters: - x_init: initial guess - f: constraint - g: data fidelity - h: regularizer - opt: additional algorithm - ''' - constraint = None - data_fidelity = None - regulariser = None - def __init__(self, **kwargs): - pass - def set_up(self, x_init, operator=None, constraint=None, data_fidelity=None,\ - regulariser=None, opt=None): - - # default inputs - if constraint is None: - self.constraint = ZeroFun() - else: - self.constraint = constraint - if data_fidelity is None: - data_fidelity = ZeroFun() - else: - self.data_fidelity = data_fidelity - if regulariser is None: - self.regulariser = ZeroFun() - else: - self.regulariser = regulariser - - # algorithmic parameters - - - # step-sizes - self.tau = 2 / (self.data_fidelity.L + 2) - self.sigma = (1/self.tau - self.data_fidelity.L/2) / self.regulariser.L - - self.inv_sigma = 1/self.sigma - - # initialization - self.x = x_init - self.y = operator.direct(self.x) - - - def update(self): - - # primal forward-backward step - x_old = self.x - self.x = self.x - self.tau * ( self.data_fidelity.grad(self.x) + self.operator.adjoint(self.y) ) - self.x = self.constraint.prox(self.x, self.tau); - - # dual forward-backward step - self.y = self.y + self.sigma * self.operator.direct(2*self.x - x_old); - self.y = self.y - self.sigma * self.regulariser.prox(self.inv_sigma*self.y, self.inv_sigma); - - # time and criterion - self.loss = self.constraint(self.x) + self.data_fidelity(self.x) + self.regulariser(self.operator.direct(self.x)) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FISTA.py deleted file mode 100644 index ee51049..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/FISTA.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Feb 21 11:07:30 2019 - -@author: ofn77899 -""" - -from ccpi.optimisation.algorithms import Algorithm -from ccpi.optimisation.functions import ZeroFunction -import numpy - -class FISTA(Algorithm): - '''Fast Iterative Shrinkage-Thresholding Algorithm - - Beck, A. and Teboulle, M., 2009. A fast iterative shrinkage-thresholding - algorithm for linear inverse problems. - SIAM journal on imaging sciences,2(1), pp.183-202. - - Parameters: - x_init: initial guess - f: data fidelity - g: regularizer - h: - opt: additional algorithm - ''' - - def __init__(self, **kwargs): - '''initialisation can be done at creation time if all - proper variables are passed or later with set_up''' - super(FISTA, self).__init__() - self.f = None - self.g = None - self.invL = None - self.t_old = 1 - args = ['x_init', 'f', 'g', 'opt'] - for k,v in kwargs.items(): - if k in args: - args.pop(args.index(k)) - if len(args) == 0: - return self.set_up(kwargs['x_init'], - f=kwargs['f'], - g=kwargs['g'], - opt=kwargs['opt']) - - def set_up(self, x_init, f=None, g=None, opt=None): - - # default inputs - if f is None: - self.f = ZeroFunction() - else: - self.f = f - if g is None: - g = ZeroFunction() - self.g = g - else: - self.g = g - - # algorithmic parameters - if opt is None: - opt = {'tol': 1e-4, 'memopt':False} - - self.tol = opt['tol'] if 'tol' in opt.keys() else 1e-4 - memopt = opt['memopt'] if 'memopt' in opt.keys() else False - self.memopt = memopt - - # initialization - if memopt: - self.y = x_init.copy() - self.x_old = x_init.copy() - self.x = x_init.copy() - self.u = x_init.copy() - else: - self.x_old = x_init.copy() - self.y = x_init.copy() - - #timing = numpy.zeros(max_iter) - #criter = numpy.zeros(max_iter) - - - self.invL = 1/f.L - - self.t_old = 1 - - def update(self): - # algorithm loop - #for it in range(0, max_iter): - - if self.memopt: - # u = y - invL*f.grad(y) - # store the result in x_old - self.f.gradient(self.y, out=self.u) - self.u.__imul__( -self.invL ) - self.u.__iadd__( self.y ) - # x = g.prox(u,invL) - self.g.proximal(self.u, self.invL, out=self.x) - - self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) - - # y = x + (t_old-1)/t*(x-x_old) - self.x.subtract(self.x_old, out=self.y) - self.y.__imul__ ((self.t_old-1)/self.t) - self.y.__iadd__( self.x ) - - self.x_old.fill(self.x) - self.t_old = self.t - - - else: - u = self.y - self.invL*self.f.gradient(self.y) - - self.x = self.g.proximal(u,self.invL) - - self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) - - self.y = self.x + (self.t_old-1)/self.t*(self.x-self.x_old) - - self.x_old = self.x.copy() - self.t_old = self.t - - def update_objective(self): - self.loss.append( self.f(self.x) + self.g(self.x) ) \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/GradientDescent.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/GradientDescent.py deleted file mode 100644 index 14763c5..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/GradientDescent.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2019 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Created on Thu Feb 21 11:05:09 2019 - -@author: ofn77899 -""" -from ccpi.optimisation.algorithms import Algorithm - -class GradientDescent(Algorithm): - '''Implementation of Gradient Descent algorithm - ''' - - def __init__(self, **kwargs): - '''initialisation can be done at creation time if all - proper variables are passed or later with set_up''' - super(GradientDescent, self).__init__() - self.x = None - self.rate = 0 - self.objective_function = None - self.regulariser = None - args = ['x_init', 'objective_function', 'rate'] - for k,v in kwargs.items(): - if k in args: - args.pop(args.index(k)) - if len(args) == 0: - return self.set_up(x_init=kwargs['x_init'], - objective_function=kwargs['objective_function'], - rate=kwargs['rate']) - - def should_stop(self): - '''stopping cryterion, currently only based on number of iterations''' - return self.iteration >= self.max_iteration - - def set_up(self, x_init, objective_function, rate): - '''initialisation of the algorithm''' - self.x = x_init.copy() - self.objective_function = objective_function - self.rate = rate - self.loss.append(objective_function(x_init)) - self.iteration = 0 - try: - self.memopt = self.objective_function.memopt - except AttributeError as ae: - self.memopt = False - if self.memopt: - self.x_update = x_init.copy() - - def update(self): - '''Single iteration''' - if self.memopt: - self.objective_function.gradient(self.x, out=self.x_update) - self.x_update *= -self.rate - self.x += self.x_update - else: - self.x += -self.rate * self.objective_function.gradient(self.x) - - def update_objective(self): - self.loss.append(self.objective_function(self.x)) - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py deleted file mode 100644 index 39b092b..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/PDHG.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Mon Feb 4 16:18:06 2019 - -@author: evangelos -""" -from ccpi.optimisation.algorithms import Algorithm -from ccpi.framework import ImageData, DataContainer -import numpy as np -import numpy -import time -from ccpi.optimisation.operators import BlockOperator -from ccpi.framework import BlockDataContainer -from ccpi.optimisation.functions import FunctionOperatorComposition - -class PDHG(Algorithm): - '''Primal Dual Hybrid Gradient''' - - def __init__(self, **kwargs): - super(PDHG, self).__init__(max_iteration=kwargs.get('max_iteration',0)) - self.f = kwargs.get('f', None) - self.operator = kwargs.get('operator', None) - self.g = kwargs.get('g', None) - self.tau = kwargs.get('tau', None) - self.sigma = kwargs.get('sigma', None) - - if self.f is not None and self.operator is not None and \ - self.g is not None: - print ("Calling from creator") - self.set_up(self.f, - self.g, - self.operator, - self.tau, - self.sigma) - - def set_up(self, f, g, operator, tau = None, sigma = None, opt = None, **kwargs): - # algorithmic parameters - self.operator = operator - self.f = f - self.g = g - self.tau = tau - self.sigma = sigma - self.opt = opt - if sigma is None and tau is None: - raise ValueError('Need sigma*tau||K||^2<1') - - self.x_old = self.operator.domain_geometry().allocate() - self.x_tmp = self.x_old.copy() - self.x = self.x_old.copy() - - self.y_old = self.operator.range_geometry().allocate() - self.y_tmp = self.y_old.copy() - self.y = self.y_old.copy() - - self.xbar = self.x_old.copy() - - # relaxation parameter - self.theta = 1 - - def update(self): - - # Gradient descent, Dual problem solution - self.operator.direct(self.xbar, out=self.y_tmp) - self.y_tmp *= self.sigma - self.y_tmp += self.y_old - - #self.y = self.f.proximal_conjugate(self.y_old, self.sigma) - self.f.proximal_conjugate(self.y_tmp, self.sigma, out=self.y) - - # Gradient ascent, Primal problem solution - self.operator.adjoint(self.y, out=self.x_tmp) - self.x_tmp *= -1*self.tau - self.x_tmp += self.x_old - - - self.g.proximal(self.x_tmp, self.tau, out=self.x) - - #Update - self.x.subtract(self.x_old, out=self.xbar) - self.xbar *= self.theta - self.xbar += self.x - - self.x_old.fill(self.x) - self.y_old.fill(self.y) - - def update_objective(self): - - p1 = self.f(self.operator.direct(self.x)) + self.g(self.x) - d1 = -(self.f.convex_conjugate(self.y) + self.g.convex_conjugate(-1*self.operator.adjoint(self.y))) - - self.loss.append([p1,d1,p1-d1]) - - - -def PDHG_old(f, g, operator, tau = None, sigma = None, opt = None, **kwargs): - - # algorithmic parameters - if opt is None: - opt = {'tol': 1e-6, 'niter': 500, 'show_iter': 100, \ - 'memopt': False} - - if sigma is None and tau is None: - raise ValueError('Need sigma*tau||K||^2<1') - - niter = opt['niter'] if 'niter' in opt.keys() else 1000 - tol = opt['tol'] if 'tol' in opt.keys() else 1e-4 - memopt = opt['memopt'] if 'memopt' in opt.keys() else False - show_iter = opt['show_iter'] if 'show_iter' in opt.keys() else False - stop_crit = opt['stop_crit'] if 'stop_crit' in opt.keys() else False - - x_old = operator.domain_geometry().allocate() - y_old = operator.range_geometry().allocate() - - xbar = x_old.copy() - x_tmp = x_old.copy() - x = x_old.copy() - - y_tmp = y_old.copy() - y = y_tmp.copy() - - - # relaxation parameter - theta = 1 - - t = time.time() - - primal = [] - dual = [] - pdgap = [] - - - for i in range(niter): - - - - if memopt: - operator.direct(xbar, out = y_tmp) - y_tmp *= sigma - y_tmp += y_old - else: - y_tmp = y_old + sigma * operator.direct(xbar) - - f.proximal_conjugate(y_tmp, sigma, out=y) - - if memopt: - operator.adjoint(y, out = x_tmp) - x_tmp *= -1*tau - x_tmp += x_old - else: - x_tmp = x_old - tau*operator.adjoint(y) - - g.proximal(x_tmp, tau, out=x) - - x.subtract(x_old, out=xbar) - xbar *= theta - xbar += x - - x_old.fill(x) - y_old.fill(y) - - if i%10==0: - - p1 = f(operator.direct(x)) + g(x) - d1 = - ( f.convex_conjugate(y) + g.convex_conjugate(-1*operator.adjoint(y)) ) - primal.append(p1) - dual.append(d1) - pdgap.append(p1-d1) - print(p1, d1, p1-d1) - - - - t_end = time.time() - - return x, t_end - t, primal, dual, pdgap - - - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/SIRT.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/SIRT.py deleted file mode 100644 index 30584d4..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/SIRT.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.optimisation.algorithms import Algorithm - -class SIRT(Algorithm): - - '''Simultaneous Iterative Reconstruction Technique - - Parameters: - x_init: initial guess - operator: operator for forward/backward projections - data: data to operate on - constraint: Function with prox-method, for example IndicatorBox to - enforce box constraints, default is None). - ''' - def __init__(self, **kwargs): - super(SIRT, self).__init__() - self.x = kwargs.get('x_init', None) - self.operator = kwargs.get('operator', None) - self.data = kwargs.get('data', None) - self.constraint = kwargs.get('constraint', None) - if self.x is not None and self.operator is not None and \ - self.data is not None: - print ("Calling from creator") - self.set_up(x_init=kwargs['x_init'], - operator=kwargs['operator'], - data=kwargs['data'], - constraint=kwargs['constraint']) - - def set_up(self, x_init, operator , data, constraint=None ): - - self.x = x_init.copy() - self.operator = operator - self.data = data - self.constraint = constraint - - self.r = data.copy() - - self.relax_par = 1.0 - - # Set up scaling matrices D and M. - self.M = 1/self.operator.direct(self.operator.domain_geometry().allocate(value=1.0)) - self.D = 1/self.operator.adjoint(self.operator.range_geometry().allocate(value=1.0)) - - - def update(self): - - self.r = self.data - self.operator.direct(self.x) - - self.x += self.relax_par * (self.D*self.operator.adjoint(self.M*self.r)) - - if self.constraint != None: - self.x = self.constraint.prox(self.x,None) - - def update_objective(self): - self.loss.append(self.r.squared_norm()) \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/__init__.py b/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/__init__.py deleted file mode 100644 index 2dbacfc..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algorithms/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2019 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Created on Thu Feb 21 11:03:13 2019 - -@author: ofn77899 -""" - -from .Algorithm import Algorithm -from .CGLS import CGLS -from .SIRT import SIRT -from .GradientDescent import GradientDescent -from .FISTA import FISTA -from .FBPD import FBPD -from .PDHG import PDHG -from .PDHG import PDHG_old - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/algs.py b/Wrappers/Python/build/lib/ccpi/optimisation/algs.py deleted file mode 100644 index f5ba85e..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/algs.py +++ /dev/null @@ -1,307 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy -import time - - - - -def FISTA(x_init, f=None, g=None, opt=None): - '''Fast Iterative Shrinkage-Thresholding Algorithm - - Beck, A. and Teboulle, M., 2009. A fast iterative shrinkage-thresholding - algorithm for linear inverse problems. - SIAM journal on imaging sciences,2(1), pp.183-202. - - Parameters: - x_init: initial guess - f: data fidelity - g: regularizer - h: - opt: additional algorithm - ''' - # default inputs - if f is None: f = ZeroFun() - if g is None: g = ZeroFun() - - # algorithmic parameters - if opt is None: - opt = {'tol': 1e-4, 'iter': 1000, 'memopt':False} - - max_iter = opt['iter'] if 'iter' in opt.keys() else 1000 - tol = opt['tol'] if 'tol' in opt.keys() else 1e-4 - memopt = opt['memopt'] if 'memopt' in opt.keys() else False - - - # initialization - if memopt: - y = x_init.clone() - x_old = x_init.clone() - x = x_init.clone() - u = x_init.clone() - else: - x_old = x_init - y = x_init; - - timing = numpy.zeros(max_iter) - criter = numpy.zeros(max_iter) - - invL = 1/f.L - - t_old = 1 - -# c = f(x_init) + g(x_init) - - # algorithm loop - for it in range(0, max_iter): - - time0 = time.time() - if memopt: - # u = y - invL*f.grad(y) - # store the result in x_old - f.gradient(y, out=u) - u.__imul__( -invL ) - u.__iadd__( y ) - # x = g.prox(u,invL) - g.proximal(u, invL, out=x) - - t = 0.5*(1 + numpy.sqrt(1 + 4*(t_old**2))) - - # y = x + (t_old-1)/t*(x-x_old) - x.subtract(x_old, out=y) - y.__imul__ ((t_old-1)/t) - y.__iadd__( x ) - - x_old.fill(x) - t_old = t - - - else: - u = y - invL*f.gradient(y) - - x = g.proximal(u,invL) - - t = 0.5*(1 + numpy.sqrt(1 + 4*(t_old**2))) - - y = x + (t_old-1)/t*(x-x_old) - - x_old = x.copy() - t_old = t - - # time and criterion -# timing[it] = time.time() - time0 -# criter[it] = f(x) + g(x); - - # stopping rule - #if np.linalg.norm(x - x_old) < tol * np.linalg.norm(x_old) and it > 10: - # break - - #print(it, 'out of', 10, 'iterations', end='\r'); - - #criter = criter[0:it+1]; -# timing = numpy.cumsum(timing[0:it+1]); - - return x #, it, timing, criter - -def FBPD(x_init, operator=None, constraint=None, data_fidelity=None,\ - regulariser=None, opt=None): - '''FBPD Algorithm - - Parameters: - x_init: initial guess - f: constraint - g: data fidelity - h: regularizer - opt: additional algorithm - ''' - # default inputs - if constraint is None: constraint = ZeroFun() - if data_fidelity is None: data_fidelity = ZeroFun() - if regulariser is None: regulariser = ZeroFun() - - # algorithmic parameters - if opt is None: - opt = {'tol': 1e-4, 'iter': 1000} - else: - try: - max_iter = opt['iter'] - except KeyError as ke: - opt[ke] = 1000 - try: - opt['tol'] = 1000 - except KeyError as ke: - opt[ke] = 1e-4 - tol = opt['tol'] - max_iter = opt['iter'] - memopt = opt['memopts'] if 'memopts' in opt.keys() else False - - # step-sizes - tau = 2 / (data_fidelity.L + 2) - sigma = (1/tau - data_fidelity.L/2) / regulariser.L - inv_sigma = 1/sigma - - # initialization - x = x_init - y = operator.direct(x); - - timing = numpy.zeros(max_iter) - criter = numpy.zeros(max_iter) - - - - - # algorithm loop - for it in range(0, max_iter): - - t = time.time() - - # primal forward-backward step - x_old = x; - x = x - tau * ( data_fidelity.grad(x) + operator.adjoint(y) ); - x = constraint.prox(x, tau); - - # dual forward-backward step - y = y + sigma * operator.direct(2*x - x_old); - y = y - sigma * regulariser.prox(inv_sigma*y, inv_sigma); - - # time and criterion - timing[it] = time.time() - t - criter[it] = constraint(x) + data_fidelity(x) + regulariser(operator.direct(x)) - - # stopping rule - #if np.linalg.norm(x - x_old) < tol * np.linalg.norm(x_old) and it > 10: - # break - - criter = criter[0:it+1] - timing = numpy.cumsum(timing[0:it+1]) - - return x, it, timing, criter - -def CGLS(x_init, operator , data , opt=None): - '''Conjugate Gradient Least Squares algorithm - - Parameters: - x_init: initial guess - operator: operator for forward/backward projections - data: data to operate on - opt: additional algorithm - ''' - - if opt is None: - opt = {'tol': 1e-4, 'iter': 1000} - else: - try: - max_iter = opt['iter'] - except KeyError as ke: - opt[ke] = 1000 - try: - opt['tol'] = 1000 - except KeyError as ke: - opt[ke] = 1e-4 - tol = opt['tol'] - max_iter = opt['iter'] - - r = data.copy() - x = x_init.copy() - - d = operator.adjoint(r) - - normr2 = (d**2).sum() - - timing = numpy.zeros(max_iter) - criter = numpy.zeros(max_iter) - - # algorithm loop - for it in range(0, max_iter): - - t = time.time() - - Ad = operator.direct(d) - alpha = normr2/( (Ad**2).sum() ) - x = x + alpha*d - r = r - alpha*Ad - s = operator.adjoint(r) - - normr2_new = (s**2).sum() - beta = normr2_new/normr2 - normr2 = normr2_new - d = s + beta*d - - # time and criterion - timing[it] = time.time() - t - criter[it] = (r**2).sum() - - return x, it, timing, criter - -def SIRT(x_init, operator , data , opt=None, constraint=None): - '''Simultaneous Iterative Reconstruction Technique - - Parameters: - x_init: initial guess - operator: operator for forward/backward projections - data: data to operate on - opt: additional algorithm - constraint: func of Indicator type specifying convex constraint. - ''' - - if opt is None: - opt = {'tol': 1e-4, 'iter': 1000} - else: - try: - max_iter = opt['iter'] - except KeyError as ke: - opt[ke] = 1000 - try: - opt['tol'] = 1000 - except KeyError as ke: - opt[ke] = 1e-4 - tol = opt['tol'] - max_iter = opt['iter'] - - x = x_init.clone() - - timing = numpy.zeros(max_iter) - criter = numpy.zeros(max_iter) - - # Relaxation parameter must be strictly between 0 and 2. For now fix at 1.0 - relax_par = 1.0 - - # Set up scaling matrices D and M. - M = 1/operator.direct(operator.domain_geometry().allocate(value=1.0)) - D = 1/operator.adjoint(operator.range_geometry().allocate(value=1.0)) - - # algorithm loop - for it in range(0, max_iter): - t = time.time() - r = data - operator.direct(x) - - x = x + relax_par * (D*operator.adjoint(M*r)) - - if constraint != None: - x = constraint.prox(x,None) - - timing[it] = time.time() - t - if it > 0: - criter[it-1] = (r**2).sum() - - r = data - operator.direct(x) - criter[it] = (r**2).sum() - return x, it, timing, criter - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/funcs.py b/Wrappers/Python/build/lib/ccpi/optimisation/funcs.py deleted file mode 100644 index b2b9791..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/funcs.py +++ /dev/null @@ -1,265 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy -from ccpi.framework import DataContainer -import warnings -from ccpi.optimisation.functions import Function -def isSizeCorrect(data1 ,data2): - if issubclass(type(data1), DataContainer) and \ - issubclass(type(data2), DataContainer): - # check dimensionality - if data1.check_dimensions(data2): - return True - elif issubclass(type(data1) , numpy.ndarray) and \ - issubclass(type(data2) , numpy.ndarray): - return data1.shape == data2.shape - else: - raise ValueError("{0}: getting two incompatible types: {1} {2}"\ - .format('Function', type(data1), type(data2))) - return False -class Norm2(Function): - - def __init__(self, - gamma=1.0, - direction=None): - super(Norm2, self).__init__() - self.gamma = gamma; - self.direction = direction; - - def __call__(self, x, out=None): - - if out is None: - xx = numpy.sqrt(numpy.sum(numpy.square(x.as_array()), self.direction, - keepdims=True)) - else: - if isSizeCorrect(out, x): - # check dimensionality - if issubclass(type(out), DataContainer): - arr = out.as_array() - numpy.square(x.as_array(), out=arr) - xx = numpy.sqrt(numpy.sum(arr, self.direction, keepdims=True)) - - elif issubclass(type(out) , numpy.ndarray): - numpy.square(x.as_array(), out=out) - xx = numpy.sqrt(numpy.sum(out, self.direction, keepdims=True)) - else: - raise ValueError ('Wrong size: x{0} out{1}'.format(x.shape,out.shape) ) - - p = numpy.sum(self.gamma*xx) - - return p - - def prox(self, x, tau): - - xx = numpy.sqrt(numpy.sum( numpy.square(x.as_array()), self.direction, - keepdims=True )) - xx = numpy.maximum(0, 1 - tau*self.gamma / xx) - p = x.as_array() * xx - - return type(x)(p,geometry=x.geometry) - def proximal(self, x, tau, out=None): - if out is None: - return self.prox(x,tau) - else: - if isSizeCorrect(out, x): - # check dimensionality - if issubclass(type(out), DataContainer): - numpy.square(x.as_array(), out = out.as_array()) - xx = numpy.sqrt(numpy.sum( out.as_array() , self.direction, - keepdims=True )) - xx = numpy.maximum(0, 1 - tau*self.gamma / xx) - x.multiply(xx, out= out.as_array()) - - - elif issubclass(type(out) , numpy.ndarray): - numpy.square(x.as_array(), out=out) - xx = numpy.sqrt(numpy.sum(out, self.direction, keepdims=True)) - - xx = numpy.maximum(0, 1 - tau*self.gamma / xx) - x.multiply(xx, out= out) - else: - raise ValueError ('Wrong size: x{0} out{1}'.format(x.shape,out.shape) ) - - - - -# Define a class for squared 2-norm -class Norm2sq(Function): - ''' - f(x) = c*||A*x-b||_2^2 - - which has - - grad[f](x) = 2*c*A^T*(A*x-b) - - and Lipschitz constant - - L = 2*c*||A||_2^2 = 2*s1(A)^2 - - where s1(A) is the largest singular value of A. - - ''' - - def __init__(self,A,b,c=1.0,memopt=False): - super(Norm2sq, self).__init__() - - self.A = A # Should be an operator, default identity - self.b = b # Default zero DataSet? - self.c = c # Default 1. - if memopt: - try: - self.range_tmp = A.range_geometry().allocate() - self.domain_tmp = A.domain_geometry().allocate() - self.memopt = True - except NameError as ne: - warnings.warn(str(ne)) - self.memopt = False - except NotImplementedError as nie: - print (nie) - warnings.warn(str(nie)) - self.memopt = False - else: - self.memopt = False - - # Compute the Lipschitz parameter from the operator if possible - # Leave it initialised to None otherwise - try: - self.L = 2.0*self.c*(self.A.norm()**2) - except AttributeError as ae: - pass - except NotImplementedError as noe: - pass - - #def grad(self,x): - # return self.gradient(x, out=None) - - def __call__(self,x): - #return self.c* np.sum(np.square((self.A.direct(x) - self.b).ravel())) - #if out is None: - # return self.c*( ( (self.A.direct(x)-self.b)**2).sum() ) - #else: - y = self.A.direct(x) - y.__isub__(self.b) - #y.__imul__(y) - #return y.sum() * self.c - try: - return y.squared_norm() * self.c - except AttributeError as ae: - # added for compatibility with SIRF - return (y.norm()**2) * self.c - - def gradient(self, x, out = None): - if self.memopt: - #return 2.0*self.c*self.A.adjoint( self.A.direct(x) - self.b ) - - self.A.direct(x, out=self.range_tmp) - self.range_tmp -= self.b - self.A.adjoint(self.range_tmp, out=out) - #self.direct_placehold.multiply(2.0*self.c, out=out) - out *= (self.c * 2.0) - else: - return (2.0*self.c)*self.A.adjoint( self.A.direct(x) - self.b ) - - - -# Box constraints indicator function. Calling returns 0 if argument is within -# the box. The prox operator is projection onto the box. Only implements one -# scalar lower and one upper as constraint on all elements. Should generalise -# to vectors to allow different constraints one elements. -class IndicatorBox(Function): - - def __init__(self,lower=-numpy.inf,upper=numpy.inf): - # Do nothing - super(IndicatorBox, self).__init__() - self.lower = lower - self.upper = upper - - - def __call__(self,x): - - if (numpy.all(x.array>=self.lower) and - numpy.all(x.array <= self.upper) ): - val = 0 - else: - val = numpy.inf - return val - - def prox(self,x,tau=None): - return (x.maximum(self.lower)).minimum(self.upper) - - def proximal(self, x, tau, out=None): - if out is None: - return self.prox(x, tau) - else: - if not x.shape == out.shape: - raise ValueError('Norm1 Incompatible size:', - x.shape, out.shape) - #(x.abs() - tau*self.gamma).maximum(0) * x.sign() - x.abs(out = out) - out.__isub__(tau*self.gamma) - out.maximum(0, out=out) - if self.sign_x is None or not x.shape == self.sign_x.shape: - self.sign_x = x.sign() - else: - x.sign(out=self.sign_x) - - out.__imul__( self.sign_x ) - -# A more interesting example, least squares plus 1-norm minimization. -# Define class to represent 1-norm including prox function -class Norm1(Function): - - def __init__(self,gamma): - super(Norm1, self).__init__() - self.gamma = gamma - self.L = 1 - self.sign_x = None - - def __call__(self,x,out=None): - if out is None: - return self.gamma*(x.abs().sum()) - else: - if not x.shape == out.shape: - raise ValueError('Norm1 Incompatible size:', - x.shape, out.shape) - x.abs(out=out) - return out.sum() * self.gamma - - def prox(self,x,tau): - return (x.abs() - tau*self.gamma).maximum(0) * x.sign() - - def proximal(self, x, tau, out=None): - if out is None: - return self.prox(x, tau) - else: - if isSizeCorrect(x,out): - # check dimensionality - if issubclass(type(out), DataContainer): - v = (x.abs() - tau*self.gamma).maximum(0) - x.sign(out=out) - out *= v - #out.fill(self.prox(x,tau)) - elif issubclass(type(out) , numpy.ndarray): - v = (x.abs() - tau*self.gamma).maximum(0) - out[:] = x.sign() - out *= v - #out[:] = self.prox(x,tau) - else: - raise ValueError ('Wrong size: x{0} out{1}'.format(x.shape,out.shape) ) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/BlockFunction.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/BlockFunction.py deleted file mode 100644 index 0836324..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/BlockFunction.py +++ /dev/null @@ -1,221 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 8 10:01:31 2019 - -@author: evangelos -""" - -from ccpi.optimisation.functions import Function -from ccpi.framework import BlockDataContainer -from numbers import Number - -class BlockFunction(Function): - - '''BlockFunction acts as a separable sum function, i.e., - - f = [f_1,...,f_n] - - f([x_1,...,x_n]) = f_1(x_1) + .... + f_n(x_n) - - ''' - def __init__(self, *functions): - - self.functions = functions - self.length = len(self.functions) - - super(BlockFunction, self).__init__() - - def __call__(self, x): - - '''Evaluates the BlockFunction at a BlockDataContainer x - - :param: x (BlockDataContainer): must have as many rows as self.length - - returns sum(f_i(x_i)) - ''' - - if self.length != x.shape[0]: - raise ValueError('BlockFunction and BlockDataContainer have incompatible size') - t = 0 - for i in range(x.shape[0]): - t += self.functions[i](x.get_item(i)) - return t - - def convex_conjugate(self, x): - - ''' Evaluate convex conjugate of BlockFunction at x - - returns sum(f_i^{*}(x_i)) - - ''' - t = 0 - for i in range(x.shape[0]): - t += self.functions[i].convex_conjugate(x.get_item(i)) - return t - - - def proximal_conjugate(self, x, tau, out = None): - - ''' Evaluate Proximal Operator of tau * f(\cdot) at x - - prox_{tau*f}(x) = sum_{i} prox_{tau*f_{i}}(x_{i}) - - - ''' - - if out is not None: - if isinstance(tau, Number): - for i in range(self.length): - self.functions[i].proximal_conjugate(x.get_item(i), tau, out=out.get_item(i)) - else: - for i in range(self.length): - self.functions[i].proximal_conjugate(x.get_item(i), tau.get_item(i),out=out.get_item(i)) - - else: - - out = [None]*self.length - if isinstance(tau, Number): - for i in range(self.length): - out[i] = self.functions[i].proximal_conjugate(x.get_item(i), tau) - else: - for i in range(self.length): - out[i] = self.functions[i].proximal_conjugate(x.get_item(i), tau.get_item(i)) - - return BlockDataContainer(*out) - - - def proximal(self, x, tau, out = None): - - ''' Evaluate Proximal Operator of tau * f^{*}(\cdot) at x - - prox_{tau*f^{*}}(x) = sum_{i} prox_{tau*f^{*}_{i}}(x_{i}) - - - ''' - - if out is None: - - out = [None]*self.length - if isinstance(tau, Number): - for i in range(self.length): - out[i] = self.functions[i].proximal(x.get_item(i), tau) - else: - for i in range(self.length): - out[i] = self.functions[i].proximal(x.get_item(i), tau.get_item(i)) - - return BlockDataContainer(*out) - - else: - if isinstance(tau, Number): - for i in range(self.length): - self.functions[i].proximal(x.get_item(i), tau, out[i]) - else: - for i in range(self.length): - self.functions[i].proximal(x.get_item(i), tau.get_item(i), out[i]) - - - - def gradient(self,x, out=None): - - ''' Evaluate gradient of f at x: f'(x) - - returns: BlockDataContainer [f_{1}'(x_{1}), ... , f_{n}'(x_{n})] - - ''' - - out = [None]*self.length - for i in range(self.length): - out[i] = self.functions[i].gradient(x.get_item(i)) - - return BlockDataContainer(*out) - - - -if __name__ == '__main__': - - M, N, K = 2,3,5 - - from ccpi.optimisation.functions import L2NormSquared, MixedL21Norm - from ccpi.framework import ImageGeometry, BlockGeometry - from ccpi.optimisation.operators import Gradient, Identity, BlockOperator - import numpy - import numpy as np - - - ig = ImageGeometry(M, N) - BG = BlockGeometry(ig, ig) - - u = ig.allocate('random_int') - B = BlockOperator( Gradient(ig), Identity(ig) ) - - U = B.direct(u) - b = ig.allocate('random_int') - - f1 = 10 * MixedL21Norm() - f2 = 0.5 * L2NormSquared(b=b) - - f = BlockFunction(f1, f2) - tau = 0.3 - - print( " without out " ) - res_no_out = f.proximal_conjugate( U, tau) - res_out = B.range_geometry().allocate() - f.proximal_conjugate( U, tau, out = res_out) - - numpy.testing.assert_array_almost_equal(res_no_out[0][0].as_array(), \ - res_out[0][0].as_array(), decimal=4) - - numpy.testing.assert_array_almost_equal(res_no_out[0][1].as_array(), \ - res_out[0][1].as_array(), decimal=4) - - numpy.testing.assert_array_almost_equal(res_no_out[1].as_array(), \ - res_out[1].as_array(), decimal=4) - - - - ########################################################################## - - - - - - - -# zzz = B.range_geometry().allocate('random_int') -# www = B.range_geometry().allocate() -# www.fill(zzz) - -# res[0].fill(z) - - - - -# f.proximal_conjugate(z, sigma, out = res) - -# print(z1[0][0].as_array()) -# print(res[0][0].as_array()) - - - - -# U = BG.allocate('random_int') -# RES = BG.allocate() -# f = BlockFunction(f1, f2) -# -# z = f.proximal_conjugate(U, 0.2) -# f.proximal_conjugate(U, 0.2, out = RES) -# -# print(z[0].as_array()) -# print(RES[0].as_array()) -# -# print(z[1].as_array()) -# print(RES[1].as_array()) - - - - - - - - \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/Function.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/Function.py deleted file mode 100644 index ba33666..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/Function.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import warnings -from ccpi.optimisation.functions.ScaledFunction import ScaledFunction - -class Function(object): - '''Abstract class representing a function - - Members: - L is the Lipschitz constant of the gradient of the Function - ''' - def __init__(self): - self.L = None - - def __call__(self,x, out=None): - '''Evaluates the function at x ''' - raise NotImplementedError - - def gradient(self, x, out=None): - '''Returns the gradient of the function at x, if the function is differentiable''' - raise NotImplementedError - - def proximal(self, x, tau, out=None): - '''This returns the proximal operator for the function at x, tau''' - raise NotImplementedError - - def convex_conjugate(self, x, out=None): - '''This evaluates the convex conjugate of the function at x''' - raise NotImplementedError - - def proximal_conjugate(self, x, tau, out = None): - '''This returns the proximal operator for the convex conjugate of the function at x, tau''' - raise NotImplementedError - - def grad(self, x): - '''Alias of gradient(x,None)''' - warnings.warn('''This method will disappear in following - versions of the CIL. Use gradient instead''', DeprecationWarning) - return self.gradient(x, out=None) - - def prox(self, x, tau): - '''Alias of proximal(x, tau, None)''' - warnings.warn('''This method will disappear in following - versions of the CIL. Use proximal instead''', DeprecationWarning) - return self.proximal(x, tau, out=None) - - def __rmul__(self, scalar): - '''Defines the multiplication by a scalar on the left - - returns a ScaledFunction''' - return ScaledFunction(self, scalar) - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition.py deleted file mode 100644 index 8895f3d..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 8 09:55:36 2019 - -@author: evangelos -""" - -from ccpi.optimisation.functions import Function -from ccpi.optimisation.functions import ScaledFunction - - -class FunctionOperatorComposition(Function): - - ''' Function composition with Operator, i.e., f(Ax) - - A: operator - f: function - - ''' - - def __init__(self, function, operator): - - super(FunctionOperatorComposition, self).__init__() - - self.function = function - self.operator = operator - self.L = function.L * operator.norm()**2 - - - def __call__(self, x): - - ''' Evaluate FunctionOperatorComposition at x - - returns f(Ax) - - ''' - - return self.function(self.operator.direct(x)) - - def gradient(self, x, out=None): -# - ''' Gradient takes into account the Operator''' - if out is None: - return self.operator.adjoint(self.function.gradient(self.operator.direct(x))) - else: - tmp = self.operator.range_geometry().allocate() - self.operator.direct(x, out=tmp) - self.function.gradient(tmp, out=tmp) - self.operator.adjoint(tmp, out=out) - - - - - #TODO do not know if we need it - #def call_adjoint(self, x): - # - # return self.function(self.operator.adjoint(x)) - - - #def convex_conjugate(self, x): - # - # ''' convex_conjugate does not take into account the Operator''' - # return self.function.convex_conjugate(x) - - - - - -if __name__ == '__main__': - - from ccpi.framework import ImageGeometry - from ccpi.optimisation.operators import Gradient - from ccpi.optimisation.functions import L2NormSquared - - M, N, K = 2,3 - ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N) - - G = Gradient(ig) - alpha = 0.5 - - f = L2NormSquared() - f_comp = FunctionOperatorComposition(G, alpha * f) - x = ig.allocate('random_int') - print(f_comp.gradient(x).shape - - ) - - - - - \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition_old.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition_old.py deleted file mode 100644 index 70511bb..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/FunctionOperatorComposition_old.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 8 09:55:36 2019 - -@author: evangelos -""" - -from ccpi.optimisation.functions import Function -from ccpi.optimisation.functions import ScaledFunction - - -class FunctionOperatorComposition(Function): - - ''' Function composition with Operator, i.e., f(Ax) - - A: operator - f: function - - ''' - - def __init__(self, operator, function): - - super(FunctionOperatorComposition, self).__init__() - self.function = function - self.operator = operator - alpha = 1 - - if isinstance (function, ScaledFunction): - alpha = function.scalar - self.L = 2 * alpha * operator.norm()**2 - - - def __call__(self, x): - - ''' Evaluate FunctionOperatorComposition at x - - returns f(Ax) - - ''' - - return self.function(self.operator.direct(x)) - - #TODO do not know if we need it - def call_adjoint(self, x): - - return self.function(self.operator.adjoint(x)) - - - def convex_conjugate(self, x): - - ''' convex_conjugate does not take into account the Operator''' - return self.function.convex_conjugate(x) - - def proximal(self, x, tau, out=None): - - '''proximal does not take into account the Operator''' - if out is None: - return self.function.proximal(x, tau) - else: - self.function.proximal(x, tau, out=out) - - - def proximal_conjugate(self, x, tau, out=None): - - ''' proximal conjugate does not take into account the Operator''' - if out is None: - return self.function.proximal_conjugate(x, tau) - else: - self.function.proximal_conjugate(x, tau, out=out) - - def gradient(self, x, out=None): - - ''' Gradient takes into account the Operator''' - if out is None: - return self.operator.adjoint( - self.function.gradient(self.operator.direct(x)) - ) - else: - self.operator.adjoint( - self.function.gradient(self.operator.direct(x), - out=out) - ) - - \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/IndicatorBox.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/IndicatorBox.py deleted file mode 100644 index df8dc89..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/IndicatorBox.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from ccpi.optimisation.functions import Function -import numpy - -class IndicatorBox(Function): - '''Box constraints indicator function. - - Calling returns 0 if argument is within the box. The prox operator is projection onto the box. - Only implements one scalar lower and one upper as constraint on all elements. Should generalise - to vectors to allow different constraints one elements. -''' - - def __init__(self,lower=-numpy.inf,upper=numpy.inf): - # Do nothing - super(IndicatorBox, self).__init__() - self.lower = lower - self.upper = upper - - - def __call__(self,x): - - if (numpy.all(x.array>=self.lower) and - numpy.all(x.array <= self.upper) ): - val = 0 - else: - val = numpy.inf - return val - - def prox(self,x,tau=None): - return (x.maximum(self.lower)).minimum(self.upper) - - def proximal(self, x, tau, out=None): - if out is None: - return self.prox(x, tau) - else: - if not x.shape == out.shape: - raise ValueError('Norm1 Incompatible size:', - x.shape, out.shape) - #(x.abs() - tau*self.gamma).maximum(0) * x.sign() - x.abs(out = out) - out.__isub__(tau*self.gamma) - out.maximum(0, out=out) - if self.sign_x is None or not x.shape == self.sign_x.shape: - self.sign_x = x.sign() - else: - x.sign(out=self.sign_x) - - out.__imul__( self.sign_x ) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py deleted file mode 100644 index cf1a244..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/KullbackLeibler.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy -from ccpi.optimisation.functions import Function -from ccpi.optimisation.functions.ScaledFunction import ScaledFunction -from ccpi.framework import ImageData, ImageGeometry -import functools - -class KullbackLeibler(Function): - - ''' Assume that data > 0 - - ''' - - def __init__(self,data, **kwargs): - - super(KullbackLeibler, self).__init__() - - self.b = data - self.bnoise = kwargs.get('bnoise', 0) - - - def __call__(self, x): - - res_tmp = numpy.zeros(x.shape) - - tmp = x + self.bnoise - ind = x.as_array()>0 - - res_tmp[ind] = x.as_array()[ind] - self.b.as_array()[ind] * numpy.log(tmp.as_array()[ind]) - - return res_tmp.sum() - - def log(self, datacontainer): - '''calculates the in-place log of the datacontainer''' - if not functools.reduce(lambda x,y: x and y>0, - datacontainer.as_array().ravel(), True): - raise ValueError('KullbackLeibler. Cannot calculate log of negative number') - datacontainer.fill( numpy.log(datacontainer.as_array()) ) - - - def gradient(self, x, out=None): - - #TODO Division check - if out is None: - return 1 - self.b/(x + self.bnoise) - else: - - x.add(self.bnoise, out=out) - self.b.divide(out, out=out) - out.subtract(1, out=out) - out *= -1 - - def convex_conjugate(self, x): - - tmp = self.b/(1-x) - ind = tmp.as_array()>0 - - return (self.b.as_array()[ind] * (numpy.log(tmp.as_array()[ind])-1)).sum() - - - def proximal(self, x, tau, out=None): - - if out is None: - return 0.5 *( (x - self.bnoise - tau) + ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() ) - else: - - tmp = 0.5 *( (x - self.bnoise - tau) + - ( (x + self.bnoise - tau)**2 + 4*tau*self.b ) .sqrt() - ) - x.add(self.bnoise, out=out) - out -= tau - out *= out - tmp = self.b * (4 * tau) - out.add(tmp, out=out) - out.sqrt(out=out) - - x.subtract(self.bnoise, out=tmp) - tmp -= tau - - out += tmp - - out *= 0.5 - - def proximal_conjugate(self, x, tau, out=None): - - - if out is None: - z = x + tau * self.bnoise - return 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) - else: - - z_m = x + tau * self.bnoise -1 - self.b.multiply(4*tau, out=out) - z_m.multiply(z_m, out=z_m) - out += z_m - - out.sqrt(out=out) - - out *= -1 - out += tmp2 - out *= 0.5 - - - - def __rmul__(self, scalar): - - ''' Multiplication of L2NormSquared with a scalar - - Returns: ScaledFunction - - ''' - - return ScaledFunction(self, scalar) - - - - - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/L1Norm.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/L1Norm.py deleted file mode 100644 index 4e53f2c..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/L1Norm.py +++ /dev/null @@ -1,234 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from ccpi.optimisation.functions import Function -from ccpi.optimisation.functions.ScaledFunction import ScaledFunction -from ccpi.optimisation.operators import ShrinkageOperator - - -class L1Norm(Function): - - ''' - - Class: L1Norm - - Cases: a) f(x) = ||x||_{1} - - b) f(x) = ||x - b||_{1} - - ''' - - def __init__(self, **kwargs): - - super(L1Norm, self).__init__() - self.b = kwargs.get('b',None) - - def __call__(self, x): - - ''' Evaluate L1Norm at x: f(x) ''' - - y = x - if self.b is not None: - y = x - self.b - return y.abs().sum() - - def gradient(self,x): - #TODO implement subgradient??? - return ValueError('Not Differentiable') - - def convex_conjugate(self,x): - #TODO implement Indicator infty??? - - y = 0 - if self.b is not None: - y = 0 + (self.b * x).sum() - return y - - def proximal(self, x, tau, out=None): - - # TODO implement shrinkage operator, we will need it later e.g SplitBregman - - if out is None: - if self.b is not None: - return self.b + ShrinkageOperator.__call__(self, x - self.b, tau) - else: - return ShrinkageOperator.__call__(self, x, tau) - else: - if self.b is not None: - out.fill(self.b + ShrinkageOperator.__call__(self, x - self.b, tau)) - else: - out.fill(ShrinkageOperator.__call__(self, x, tau)) - - def proximal_conjugate(self, x, tau, out=None): - - if out is None: - if self.b is not None: - return (x - tau*self.b).divide((x - tau*self.b).abs().maximum(1.0)) - else: - return x.divide(x.abs().maximum(1.0)) - else: - if self.b is not None: - out.fill((x - tau*self.b).divide((x - tau*self.b).abs().maximum(1.0))) - else: - out.fill(x.divide(x.abs().maximum(1.0)) ) - - def __rmul__(self, scalar): - return ScaledFunction(self, scalar) - - -#import numpy as np -##from ccpi.optimisation.funcs import Function -#from ccpi.optimisation.functions import Function -#from ccpi.framework import DataContainer, ImageData -# -# -############################# L1NORM FUNCTIONS ############################# -#class SimpleL1Norm(Function): -# -# def __init__(self, alpha=1): -# -# super(SimpleL1Norm, self).__init__() -# self.alpha = alpha -# -# def __call__(self, x): -# return self.alpha * x.abs().sum() -# -# def gradient(self,x): -# return ValueError('Not Differentiable') -# -# def convex_conjugate(self,x): -# return 0 -# -# def proximal(self, x, tau): -# ''' Soft Threshold''' -# return x.sign() * (x.abs() - tau * self.alpha).maximum(0) -# -# def proximal_conjugate(self, x, tau): -# return x.divide((x.abs()/self.alpha).maximum(1.0)) - -#class L1Norm(SimpleL1Norm): -# -# def __init__(self, alpha=1, **kwargs): -# -# super(L1Norm, self).__init__() -# self.alpha = alpha -# self.b = kwargs.get('b',None) -# -# def __call__(self, x): -# -# if self.b is None: -# return SimpleL1Norm.__call__(self, x) -# else: -# return SimpleL1Norm.__call__(self, x - self.b) -# -# def gradient(self, x): -# return ValueError('Not Differentiable') -# -# def convex_conjugate(self,x): -# if self.b is None: -# return SimpleL1Norm.convex_conjugate(self, x) -# else: -# return SimpleL1Norm.convex_conjugate(self, x) + (self.b * x).sum() -# -# def proximal(self, x, tau): -# -# if self.b is None: -# return SimpleL1Norm.proximal(self, x, tau) -# else: -# return self.b + SimpleL1Norm.proximal(self, x - self.b , tau) -# -# def proximal_conjugate(self, x, tau): -# -# if self.b is None: -# return SimpleL1Norm.proximal_conjugate(self, x, tau) -# else: -# return SimpleL1Norm.proximal_conjugate(self, x - tau*self.b, tau) -# - -############################################################################### - - - - -if __name__ == '__main__': - - from ccpi.framework import ImageGeometry - import numpy - N, M = 40,40 - ig = ImageGeometry(N, M) - scalar = 10 - b = ig.allocate('random_int') - u = ig.allocate('random_int') - - f = L1Norm() - f_scaled = scalar * L1Norm() - - f_b = L1Norm(b=b) - f_scaled_b = scalar * L1Norm(b=b) - - # call - - a1 = f(u) - a2 = f_scaled(u) - numpy.testing.assert_equal(scalar * a1, a2) - - a3 = f_b(u) - a4 = f_scaled_b(u) - numpy.testing.assert_equal(scalar * a3, a4) - - # proximal - tau = 0.4 - b1 = f.proximal(u, tau*scalar) - b2 = f_scaled.proximal(u, tau) - - numpy.testing.assert_array_almost_equal(b1.as_array(), b2.as_array(), decimal=4) - - b3 = f_b.proximal(u, tau*scalar) - b4 = f_scaled_b.proximal(u, tau) - - z1 = b + (u-b).sign() * ((u-b).abs() - tau * scalar).maximum(0) - - numpy.testing.assert_array_almost_equal(b3.as_array(), b4.as_array(), decimal=4) -# -# #proximal conjugate -# - c1 = f_scaled.proximal_conjugate(u, tau) - c2 = u.divide((u.abs()/scalar).maximum(1.0)) - - numpy.testing.assert_array_almost_equal(c1.as_array(), c2.as_array(), decimal=4) - - c3 = f_scaled_b.proximal_conjugate(u, tau) - c4 = (u - tau*b).divide( ((u-tau*b).abs()/scalar).maximum(1.0) ) - - numpy.testing.assert_array_almost_equal(c3.as_array(), c4.as_array(), decimal=4) - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py deleted file mode 100644 index b77d472..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/L2NormSquared.py +++ /dev/null @@ -1,286 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.optimisation.functions import Function -from ccpi.optimisation.functions.ScaledFunction import ScaledFunction -from ccpi.optimisation.functions import FunctionOperatorComposition - -class L2NormSquared(Function): - - ''' - - Cases: a) f(x) = \|x\|^{2}_{2} - - b) f(x) = ||x - b||^{2}_{2} - - ''' - - def __init__(self, **kwargs): - - super(L2NormSquared, self).__init__() - self.b = kwargs.get('b',None) - self.L = 2 - - def __call__(self, x): - - ''' Evaluate L2NormSquared at x: f(x) ''' - - y = x - if self.b is not None: - y = x - self.b - try: - return y.squared_norm() - except AttributeError as ae: - # added for compatibility with SIRF - return (y.norm()**2) - - def gradient(self, x, out=None): - - ''' Evaluate gradient of L2NormSquared at x: f'(x) ''' - - if out is not None: - - out.fill(x) - if self.b is not None: - out -= self.b - out *= 2 - - else: - - y = x - if self.b is not None: - y = x - self.b - return 2*y - - - def convex_conjugate(self, x): - - ''' Evaluate convex conjugate of L2NormSquared at x: f^{*}(x)''' - - tmp = 0 - - if self.b is not None: - tmp = (x * self.b).sum() - - return (1./4.) * x.squared_norm() + tmp - - - def proximal(self, x, tau, out = None): - - ''' Evaluate Proximal Operator of tau * f(\cdot) at x: - - prox_{tau*f(\cdot)}(x) = \argmin_{z} \frac{1}{2}|| z - x ||^{2}_{2} + tau * f(z) - - ''' - - if out is None: - - if self.b is None: - return x/(1+2*tau) - else: - tmp = x.subtract(self.b) - tmp /= (1+2*tau) - tmp += self.b - return tmp - - else: - if self.b is not None: - x.subtract(self.b, out=out) - out /= (1+2*tau) - out += self.b - else: - x.divide((1+2*tau), out=out) - - - def proximal_conjugate(self, x, tau, out=None): - - ''' Evaluate Proximal Operator of tau * f^{*}(\cdot) at x (i.e., the convex conjugate of f) : - - prox_{tau*f(\cdot)}(x) = \argmin_{z} \frac{1}{2}|| z - x ||^{2}_{2} + tau * f^{*}(z) - - ''' - - if out is None: - if self.b is not None: - return (x - tau*self.b)/(1 + tau/2) - else: - return x/(1 + tau/2) - else: - if self.b is not None: - x.subtract(tau*self.b, out=out) - out.divide(1+tau/2, out=out) - else: - x.divide(1 + tau/2, out=out) - - def __rmul__(self, scalar): - - ''' Multiplication of L2NormSquared with a scalar - - Returns: ScaledFunction - - ''' - - return ScaledFunction(self, scalar) - - - def composition(self, operator): - - return FunctionOperatorComposition(operator) - - - -if __name__ == '__main__': - - from ccpi.framework import ImageGeometry - import numpy - # TESTS for L2 and scalar * L2 - - M, N, K = 2,3,5 - ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N, voxel_num_z = K) - u = ig.allocate('random_int') - b = ig.allocate('random_int') - - # check grad/call no data - f = L2NormSquared() - a1 = f.gradient(u) - a2 = 2 * u - numpy.testing.assert_array_almost_equal(a1.as_array(), a2.as_array(), decimal=4) - numpy.testing.assert_equal(f(u), u.squared_norm()) - - # check grad/call with data - f1 = L2NormSquared(b=b) - b1 = f1.gradient(u) - b2 = 2 * (u-b) - - numpy.testing.assert_array_almost_equal(b1.as_array(), b2.as_array(), decimal=4) - numpy.testing.assert_equal(f1(u), (u-b).squared_norm()) - - #check convex conjuagate no data - c1 = f.convex_conjugate(u) - c2 = 1/4 * u.squared_norm() - numpy.testing.assert_equal(c1, c2) - - #check convex conjuagate with data - d1 = f1.convex_conjugate(u) - d2 = (1/4) * u.squared_norm() + (u*b).sum() - numpy.testing.assert_equal(d1, d2) - - # check proximal no data - tau = 5 - e1 = f.proximal(u, tau) - e2 = u/(1+2*tau) - numpy.testing.assert_array_almost_equal(e1.as_array(), e2.as_array(), decimal=4) - - # check proximal with data - tau = 5 - h1 = f1.proximal(u, tau) - h2 = (u-b)/(1+2*tau) + b - numpy.testing.assert_array_almost_equal(h1.as_array(), h2.as_array(), decimal=4) - - # check proximal conjugate no data - tau = 0.2 - k1 = f.proximal_conjugate(u, tau) - k2 = u/(1 + tau/2 ) - numpy.testing.assert_array_almost_equal(k1.as_array(), k2.as_array(), decimal=4) - - # check proximal conjugate with data - l1 = f1.proximal_conjugate(u, tau) - l2 = (u - tau * b)/(1 + tau/2 ) - numpy.testing.assert_array_almost_equal(l1.as_array(), l2.as_array(), decimal=4) - - - # check scaled function properties - - # scalar - scalar = 100 - f_scaled_no_data = scalar * L2NormSquared() - f_scaled_data = scalar * L2NormSquared(b=b) - - # call - numpy.testing.assert_equal(f_scaled_no_data(u), scalar*f(u)) - numpy.testing.assert_equal(f_scaled_data(u), scalar*f1(u)) - - # grad - numpy.testing.assert_array_almost_equal(f_scaled_no_data.gradient(u).as_array(), scalar*f.gradient(u).as_array(), decimal=4) - numpy.testing.assert_array_almost_equal(f_scaled_data.gradient(u).as_array(), scalar*f1.gradient(u).as_array(), decimal=4) - - # conj - numpy.testing.assert_almost_equal(f_scaled_no_data.convex_conjugate(u), \ - f.convex_conjugate(u/scalar) * scalar, decimal=4) - - numpy.testing.assert_almost_equal(f_scaled_data.convex_conjugate(u), \ - scalar * f1.convex_conjugate(u/scalar), decimal=4) - - # proximal - numpy.testing.assert_array_almost_equal(f_scaled_no_data.proximal(u, tau).as_array(), \ - f.proximal(u, tau*scalar).as_array()) - - - numpy.testing.assert_array_almost_equal(f_scaled_data.proximal(u, tau).as_array(), \ - f1.proximal(u, tau*scalar).as_array()) - - - # proximal conjugate - numpy.testing.assert_array_almost_equal(f_scaled_no_data.proximal_conjugate(u, tau).as_array(), \ - (u/(1 + tau/(2*scalar) )).as_array(), decimal=4) - - numpy.testing.assert_array_almost_equal(f_scaled_data.proximal_conjugate(u, tau).as_array(), \ - ((u - tau * b)/(1 + tau/(2*scalar) )).as_array(), decimal=4) - - - - print( " ####### check without out ######### " ) - - - u_out_no_out = ig.allocate('random_int') - res_no_out = f_scaled_data.proximal_conjugate(u_out_no_out, 0.5) - print(res_no_out.as_array()) - - print( " ####### check with out ######### " ) - - res_out = ig.allocate() - f_scaled_data.proximal_conjugate(u_out_no_out, 0.5, out = res_out) - - print(res_out.as_array()) - - numpy.testing.assert_array_almost_equal(res_no_out.as_array(), \ - res_out.as_array(), decimal=4) - - - - ig1 = ImageGeometry(2,3) - - tau = 0.1 - - u = ig1.allocate('random_int') - b = ig1.allocate('random_int') - - scalar = 0.5 - f_scaled = scalar * L2NormSquared(b=b) - f_noscaled = L2NormSquared(b=b) - - - res1 = f_scaled.proximal(u, tau) - res2 = f_noscaled.proximal(u, tau*scalar) - -# res2 = (u + tau*b)/(1+tau) - - numpy.testing.assert_array_almost_equal(res1.as_array(), \ - res2.as_array(), decimal=4) - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/MixedL21Norm.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/MixedL21Norm.py deleted file mode 100644 index e8f6da4..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/MixedL21Norm.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.optimisation.functions import Function, ScaledFunction -from ccpi.framework import BlockDataContainer - -import functools - -class MixedL21Norm(Function): - - - ''' - f(x) = ||x||_{2,1} = \sum |x|_{2} - ''' - - def __init__(self, **kwargs): - - super(MixedL21Norm, self).__init__() - self.SymTensor = kwargs.get('SymTensor',False) - - def __call__(self, x): - - ''' Evaluates L2,1Norm at point x - - :param: x is a BlockDataContainer - - ''' - if not isinstance(x, BlockDataContainer): - raise ValueError('__call__ expected BlockDataContainer, got {}'.format(type(x))) - - tmp = [ el**2 for el in x.containers ] - res = sum(tmp).sqrt().sum() - - return res - - def gradient(self, x, out=None): - return ValueError('Not Differentiable') - - def convex_conjugate(self,x): - - ''' This is the Indicator function of ||\cdot||_{2, \infty} - which is either 0 if ||x||_{2, \infty} or \infty - ''' - - return 0.0 - - #tmp = [ el**2 for el in x.containers ] - #print(sum(tmp).sqrt().as_array().max()) - #return sum(tmp).sqrt().as_array().max() - - def proximal(self, x, tau, out=None): - - ''' - For this we need to define a MixedL2,2 norm acting on BDC, - different form L2NormSquared which acts on DC - - ''' - pass - - def proximal_conjugate(self, x, tau, out=None): - - - if out is None: - tmp = [ el*el for el in x.containers] - res = sum(tmp).sqrt().maximum(1.0) - frac = [el/res for el in x.containers] - return BlockDataContainer(*frac) - - - #TODO this is slow, why??? -# return x.divide(x.pnorm().maximum(1.0)) - else: - - res1 = functools.reduce(lambda a,b: a + b*b, x.containers, x.get_item(0) * 0 ) - res = res1.sqrt().maximum(1.0) - x.divide(res, out=out) - -# x.divide(sum([el*el for el in x.containers]).sqrt().maximum(1.0), out=out) - #TODO this is slow, why ??? -# x.divide(x.pnorm().maximum(1.0), out=out) - - - def __rmul__(self, scalar): - - ''' Multiplication of L2NormSquared with a scalar - - Returns: ScaledFunction - - ''' - return ScaledFunction(self, scalar) - - -# -if __name__ == '__main__': - - M, N, K = 2,3,5 - from ccpi.framework import BlockGeometry - import numpy - - ig = ImageGeometry(M, N) - - BG = BlockGeometry(ig, ig) - - U = BG.allocate('random_int') - - # Define no scale and scaled - f_no_scaled = MixedL21Norm() - f_scaled = 0.5 * MixedL21Norm() - - # call - - a1 = f_no_scaled(U) - a2 = f_scaled(U) - print(a1, 2*a2) - - - print( " ####### check without out ######### " ) - - - u_out_no_out = BG.allocate('random_int') - res_no_out = f_scaled.proximal_conjugate(u_out_no_out, 0.5) - print(res_no_out[0].as_array()) - - print( " ####### check with out ######### " ) -# - res_out = BG.allocate() - f_scaled.proximal_conjugate(u_out_no_out, 0.5, out = res_out) -# - print(res_out[0].as_array()) -# - numpy.testing.assert_array_almost_equal(res_no_out[0].as_array(), \ - res_out[0].as_array(), decimal=4) - - numpy.testing.assert_array_almost_equal(res_no_out[1].as_array(), \ - res_out[1].as_array(), decimal=4) -# - - - - - - - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/Norm2Sq.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/Norm2Sq.py deleted file mode 100644 index b553d7c..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/Norm2Sq.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from ccpi.optimisation.functions import Function -import numpy -import warnings - -# Define a class for squared 2-norm -class Norm2sq(Function): - ''' - f(x) = c*||A*x-b||_2^2 - - which has - - grad[f](x) = 2*c*A^T*(A*x-b) - - and Lipschitz constant - - L = 2*c*||A||_2^2 = 2*s1(A)^2 - - where s1(A) is the largest singular value of A. - - ''' - - def __init__(self,A,b,c=1.0,memopt=False): - super(Norm2sq, self).__init__() - - self.A = A # Should be an operator, default identity - self.b = b # Default zero DataSet? - self.c = c # Default 1. - if memopt: - try: - self.range_tmp = A.range_geometry().allocate() - self.domain_tmp = A.domain_geometry().allocate() - self.memopt = True - except NameError as ne: - warnings.warn(str(ne)) - self.memopt = False - except NotImplementedError as nie: - print (nie) - warnings.warn(str(nie)) - self.memopt = False - else: - self.memopt = False - - # Compute the Lipschitz parameter from the operator if possible - # Leave it initialised to None otherwise - try: - self.L = 2.0*self.c*(self.A.norm()**2) - except AttributeError as ae: - pass - except NotImplementedError as noe: - pass - - #def grad(self,x): - # return self.gradient(x, out=None) - - def __call__(self,x): - #return self.c* np.sum(np.square((self.A.direct(x) - self.b).ravel())) - #if out is None: - # return self.c*( ( (self.A.direct(x)-self.b)**2).sum() ) - #else: - y = self.A.direct(x) - y.__isub__(self.b) - #y.__imul__(y) - #return y.sum() * self.c - try: - return y.squared_norm() * self.c - except AttributeError as ae: - # added for compatibility with SIRF - return (y.norm()**2) * self.c - - def gradient(self, x, out = None): - if self.memopt: - #return 2.0*self.c*self.A.adjoint( self.A.direct(x) - self.b ) - - self.A.direct(x, out=self.range_tmp) - self.range_tmp -= self.b - self.A.adjoint(self.range_tmp, out=out) - #self.direct_placehold.multiply(2.0*self.c, out=out) - out *= (self.c * 2.0) - else: - return (2.0*self.c)*self.A.adjoint( self.A.direct(x) - self.b ) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/ScaledFunction.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/ScaledFunction.py deleted file mode 100644 index 1db223b..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/ScaledFunction.py +++ /dev/null @@ -1,150 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from numbers import Number -import numpy - -class ScaledFunction(object): - - '''ScaledFunction - - A class to represent the scalar multiplication of an Function with a scalar. - It holds a function and a scalar. Basically it returns the multiplication - of the product of the function __call__, convex_conjugate and gradient with the scalar. - For the rest it behaves like the function it holds. - - Args: - function (Function): a Function or BlockOperator - scalar (Number): a scalar multiplier - Example: - The scaled operator behaves like the following: - - ''' - def __init__(self, function, scalar): - super(ScaledFunction, self).__init__() - - if not isinstance (scalar, Number): - raise TypeError('expected scalar: got {}'.format(type(scalar))) - self.scalar = scalar - self.function = function - - if self.function.L is not None: - self.L = self.scalar * self.function.L - - def __call__(self,x, out=None): - '''Evaluates the function at x ''' - return self.scalar * self.function(x) - - def convex_conjugate(self, x): - '''returns the convex_conjugate of the scaled function ''' - return self.scalar * self.function.convex_conjugate(x/self.scalar) - - def gradient(self, x, out=None): - '''Returns the gradient of the function at x, if the function is differentiable''' - if out is None: - return self.scalar * self.function.gradient(x) - else: - self.function.gradient(x, out=out) - out *= self.scalar - - def proximal(self, x, tau, out=None): - '''This returns the proximal operator for the function at x, tau - ''' - if out is None: - return self.function.proximal(x, tau*self.scalar) - else: - self.function.proximal(x, tau*self.scalar, out = out) - - def proximal_conjugate(self, x, tau, out = None): - '''This returns the proximal operator for the function at x, tau - ''' - if out is None: - return self.scalar * self.function.proximal_conjugate(x/self.scalar, tau/self.scalar) - else: - self.function.proximal_conjugate(x/self.scalar, tau/self.scalar, out=out) - out *= self.scalar - - def grad(self, x): - '''Alias of gradient(x,None)''' - warnings.warn('''This method will disappear in following - versions of the CIL. Use gradient instead''', DeprecationWarning) - return self.gradient(x, out=None) - - def prox(self, x, tau): - '''Alias of proximal(x, tau, None)''' - warnings.warn('''This method will disappear in following - versions of the CIL. Use proximal instead''', DeprecationWarning) - return self.proximal(x, out=None) - - - -if __name__ == '__main__': - - from ccpi.optimisation.functions import L2NormSquared, MixedL21Norm - from ccpi.framework import ImageGeometry, BlockGeometry - - M, N, K = 2,3,5 - ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N, voxel_num_z = K) - - u = ig.allocate('random_int') - b = ig.allocate('random_int') - - BG = BlockGeometry(ig, ig) - U = BG.allocate('random_int') - - f2 = 0.5 * L2NormSquared(b=b) - f1 = 30 * MixedL21Norm() - tau = 0.355 - - res_no_out1 = f1.proximal_conjugate(U, tau) - res_no_out2 = f2.proximal_conjugate(u, tau) - - -# print( " ######## with out ######## ") - res_out1 = BG.allocate() - res_out2 = ig.allocate() - - f1.proximal_conjugate(U, tau, out = res_out1) - f2.proximal_conjugate(u, tau, out = res_out2) - - - numpy.testing.assert_array_almost_equal(res_no_out1[0].as_array(), \ - res_out1[0].as_array(), decimal=4) - - numpy.testing.assert_array_almost_equal(res_no_out2.as_array(), \ - res_out2.as_array(), decimal=4) - - - - - - - - - - - - - - - - - - - - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFunction.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFunction.py deleted file mode 100644 index a019815..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/ZeroFunction.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.optimisation.functions import Function -from ccpi.framework import BlockDataContainer - -class ZeroFunction(Function): - - ''' ZeroFunction: f(x) = 0 - - - ''' - - def __init__(self): - super(ZeroFunction, self).__init__() - - def __call__(self,x): - return 0 - - def convex_conjugate(self, x): - - ''' This is the support function sup which in fact is the - indicator function for the set = {0} - So 0 if x=0, or inf if x neq 0 - ''' - return x.maximum(0).sum() - - - def proximal(self, x, tau, out=None): - if out is None: - return x.copy() - else: - out.fill(x) - - def proximal_conjugate(self, x, tau, out = None): - if out is None: - return 0 - else: - return 0 diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/functions/__init__.py b/Wrappers/Python/build/lib/ccpi/optimisation/functions/__init__.py deleted file mode 100644 index a82ee3e..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/functions/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- - -from .Function import Function -from .ZeroFunction import ZeroFunction -from .L1Norm import L1Norm -from .L2NormSquared import L2NormSquared -from .ScaledFunction import ScaledFunction -from .BlockFunction import BlockFunction -from .FunctionOperatorComposition import FunctionOperatorComposition -from .MixedL21Norm import MixedL21Norm -from .IndicatorBox import IndicatorBox -from .KullbackLeibler import KullbackLeibler -from .Norm2Sq import Norm2sq diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockOperator.py deleted file mode 100644 index c8bd546..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockOperator.py +++ /dev/null @@ -1,417 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Feb 14 12:36:40 2019 - -@author: ofn77899 -""" -#from ccpi.optimisation.ops import Operator -import numpy -from numbers import Number -import functools -from ccpi.framework import AcquisitionData, ImageData, BlockDataContainer, DataContainer -from ccpi.optimisation.operators import Operator, LinearOperator -from ccpi.optimisation.operators.BlockScaledOperator import BlockScaledOperator -from ccpi.framework import BlockGeometry - -class BlockOperator(Operator): - '''A Block matrix containing Operators - - The Block Framework is a generic strategy to treat variational problems in the - following form: - - .. math:: - - min Regulariser + Fidelity - - - BlockOperators have a generic shape M x N, and when applied on an - Nx1 BlockDataContainer, will yield and Mx1 BlockDataContainer. - Notice: BlockDatacontainer are only allowed to have the shape of N x 1, with - N rows and 1 column. - - User may specify the shape of the block, by default is a row vector - - Operators in a Block are required to have the same domain column-wise and the - same range row-wise. - ''' - __array_priority__ = 1 - def __init__(self, *args, **kwargs): - ''' - Class creator - - Note: - Do not include the `self` parameter in the ``Args`` section. - - Args: - :param: vararg (Operator): Operators in the block. - :param: shape (:obj:`tuple`, optional): If shape is passed the Operators in - vararg are considered input in a row-by-row fashion. - Shape and number of Operators must match. - - Example: - BlockOperator(op0,op1) results in a row block - BlockOperator(op0,op1,shape=(1,2)) results in a column block - ''' - self.operators = args - shape = kwargs.get('shape', None) - if shape is None: - shape = (len(args),1) - self.shape = shape - n_elements = functools.reduce(lambda x,y: x*y, shape, 1) - if len(args) != n_elements: - raise ValueError( - 'Dimension and size do not match: expected {} got {}' - .format(n_elements,len(args))) - # test if operators are compatible - if not self.column_wise_compatible(): - raise ValueError('Operators in each column must have the same domain') - if not self.row_wise_compatible(): - raise ValueError('Operators in each row must have the same range') - - def column_wise_compatible(self): - '''Operators in a Block should have the same domain per column''' - rows, cols = self.shape - compatible = True - for col in range(cols): - column_compatible = True - for row in range(1,rows): - dg0 = self.get_item(row-1,col).domain_geometry() - dg1 = self.get_item(row,col).domain_geometry() - column_compatible = dg0.__dict__ == dg1.__dict__ and column_compatible - compatible = compatible and column_compatible - return compatible - - def row_wise_compatible(self): - '''Operators in a Block should have the same range per row''' - rows, cols = self.shape - compatible = True - for row in range(rows): - row_compatible = True - for col in range(1,cols): - dg0 = self.get_item(row,col-1).range_geometry() - dg1 = self.get_item(row,col).range_geometry() - row_compatible = dg0.__dict__ == dg1.__dict__ and row_compatible - compatible = compatible and row_compatible - return compatible - - def get_item(self, row, col): - '''returns the Operator at specified row and col''' - if row > self.shape[0]: - raise ValueError('Requested row {} > max {}'.format(row, self.shape[0])) - if col > self.shape[1]: - raise ValueError('Requested col {} > max {}'.format(col, self.shape[1])) - - index = row*self.shape[1]+col - return self.operators[index] - - def norm(self): - norm = [op.norm()**2 for op in self.operators] - return numpy.sqrt(sum(norm)) - - def direct(self, x, out=None): - '''Direct operation for the BlockOperator - - BlockOperator work on BlockDataContainer, but they will work on DataContainers - and inherited classes by simple wrapping the input in a BlockDataContainer of shape (1,1) - ''' - - if not isinstance (x, BlockDataContainer): - x_b = BlockDataContainer(x) - else: - x_b = x - shape = self.get_output_shape(x_b.shape) - res = [] - - if out is None: - - for row in range(self.shape[0]): - for col in range(self.shape[1]): - if col == 0: - prod = self.get_item(row,col).direct(x_b.get_item(col)) - else: - prod += self.get_item(row,col).direct(x_b.get_item(col)) - res.append(prod) - return BlockDataContainer(*res, shape=shape) - - else: - - tmp = self.range_geometry().allocate() - for row in range(self.shape[0]): - for col in range(self.shape[1]): - if col == 0: - self.get_item(row,col).direct( - x_b.get_item(col), - out=out.get_item(row)) - else: - a = out.get_item(row) - self.get_item(row,col).direct( - x_b.get_item(col), - out=tmp.get_item(row)) - a += tmp.get_item(row) - - def adjoint(self, x, out=None): - '''Adjoint operation for the BlockOperator - - BlockOperator may contain both LinearOperator and Operator - This method exists in BlockOperator as it is not known what type of - Operator it will contain. - - BlockOperator work on BlockDataContainer, but they will work on DataContainers - and inherited classes by simple wrapping the input in a BlockDataContainer of shape (1,1) - - Raises: ValueError if the contained Operators are not linear - ''' - if not self.is_linear(): - raise ValueError('Not all operators in Block are linear.') - if not isinstance (x, BlockDataContainer): - x_b = BlockDataContainer(x) - else: - x_b = x - shape = self.get_output_shape(x_b.shape, adjoint=True) - if out is None: - res = [] - for col in range(self.shape[1]): - for row in range(self.shape[0]): - if row == 0: - prod = self.get_item(row, col).adjoint(x_b.get_item(row)) - else: - prod += self.get_item(row, col).adjoint(x_b.get_item(row)) - res.append(prod) - if self.shape[1]==1: - return ImageData(*res) - else: - return BlockDataContainer(*res, shape=shape) - else: - #tmp = self.domain_geometry().allocate() - - for col in range(self.shape[1]): - for row in range(self.shape[0]): - if row == 0: - if issubclass(out.__class__, DataContainer): - self.get_item(row, col).adjoint( - x_b.get_item(row), - out=out) - else: - op = self.get_item(row,col) - self.get_item(row, col).adjoint( - x_b.get_item(row), - out=out.get_item(col)) - else: - if issubclass(out.__class__, DataContainer): - out += self.get_item(row,col).adjoint( - x_b.get_item(row)) - else: - a = out.get_item(col) - a += self.get_item(row,col).adjoint( - x_b.get_item(row), - ) - def is_linear(self): - '''returns whether all the elements of the BlockOperator are linear''' - return functools.reduce(lambda x, y: x and y.is_linear(), self.operators, True) - - def get_output_shape(self, xshape, adjoint=False): - '''returns the shape of the output BlockDataContainer - - A(N,M) direct u(M,1) -> N,1 - A(N,M)^T adjoint u(N,1) -> M,1 - ''' - rows , cols = self.shape - xrows, xcols = xshape - if xcols != 1: - raise ValueError('BlockDataContainer cannot have more than 1 column') - if adjoint: - if rows != xrows: - raise ValueError('Incompatible shapes {} {}'.format(self.shape, xshape)) - return (cols,xcols) - if cols != xrows: - raise ValueError('Incompatible shapes {} {}'.format((rows,cols), xshape)) - return (rows,xcols) - - def __rmul__(self, scalar): - '''Defines the left multiplication with a scalar - - Args: scalar (number or iterable containing numbers): - - Returns: a block operator with Scaled Operators inside''' - if isinstance (scalar, list) or isinstance(scalar, tuple) or \ - isinstance(scalar, numpy.ndarray): - if len(scalar) != len(self.operators): - raise ValueError('dimensions of scalars and operators do not match') - scalars = scalar - else: - scalars = [scalar for _ in self.operators] - # create a list of ScaledOperator-s - ops = [ v * op for v,op in zip(scalars, self.operators)] - #return BlockScaledOperator(self, scalars ,shape=self.shape) - return type(self)(*ops, shape=self.shape) - @property - def T(self): - '''Return the transposed of self - - input in a row-by-row''' - newshape = (self.shape[1], self.shape[0]) - oplist = [] - for col in range(newshape[1]): - for row in range(newshape[0]): - oplist.append(self.get_item(col,row)) - return type(self)(*oplist, shape=newshape) - - def domain_geometry(self): - '''returns the domain of the BlockOperator - - If the shape of the BlockOperator is (N,1) the domain is a ImageGeometry or AcquisitionGeometry. - Otherwise it is a BlockGeometry. - ''' - if self.shape[1] == 1: - # column BlockOperator - return self.get_item(0,0).domain_geometry() - else: - # get the geometries column wise - # we need only the geometries from the first row - # since it is compatible from __init__ - tmp = [] - for i in range(self.shape[1]): - tmp.append(self.get_item(0,i).domain_geometry()) - return BlockGeometry(*tmp) - - #shape = (self.shape[0], 1) - #return BlockGeometry(*[el.domain_geometry() for el in self.operators], - # shape=self.shape) - - def range_geometry(self): - '''returns the range of the BlockOperator''' - - tmp = [] - for i in range(self.shape[0]): - tmp.append(self.get_item(i,0).range_geometry()) - return BlockGeometry(*tmp) - - - #shape = (self.shape[1], 1) - #return BlockGeometry(*[el.range_geometry() for el in self.operators], - # shape=shape) - - def sum_abs_row(self): - - res = [] - for row in range(self.shape[0]): - for col in range(self.shape[1]): - if col == 0: - prod = self.get_item(row,col).sum_abs_row() - else: - prod += self.get_item(row,col).sum_abs_row() - res.append(prod) - - if self.shape[1]==1: - tmp = sum(res) - return ImageData(tmp) - else: - - return BlockDataContainer(*res) - - def sum_abs_col(self): - - res = [] - for row in range(self.shape[0]): - for col in range(self.shape[1]): - if col == 0: - prod = self.get_item(row, col).sum_abs_col() - else: - prod += self.get_item(row, col).sum_abs_col() - res.append(prod) - - return BlockDataContainer(*res) - - - -if __name__ == '__main__': - - from ccpi.framework import ImageGeometry - from ccpi.optimisation.operators import Gradient, Identity, \ - SparseFiniteDiff, SymmetrizedGradient, ZeroOperator - - - M, N = 4, 3 - ig = ImageGeometry(M, N) - arr = ig.allocate('random_int') - - G = Gradient(ig) - Id = Identity(ig) - - B = BlockOperator(G, Id) - - print(B.sum_abs_row()) -# - Gx = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - Gy = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - - d1 = abs(Gx.matrix()).toarray().sum(axis=0) - d2 = abs(Gy.matrix()).toarray().sum(axis=0) - d3 = abs(Id.matrix()).toarray().sum(axis=0) - - - d_res = numpy.reshape(d1 + d2 + d3, ig.shape, 'F') - - print(d_res) -# - z1 = abs(Gx.matrix()).toarray().sum(axis=1) - z2 = abs(Gy.matrix()).toarray().sum(axis=1) - z3 = abs(Id.matrix()).toarray().sum(axis=1) -# - z_res = BlockDataContainer(BlockDataContainer(ImageData(numpy.reshape(z2, ig.shape, 'F')),\ - ImageData(numpy.reshape(z1, ig.shape, 'F'))),\ - ImageData(numpy.reshape(z3, ig.shape, 'F'))) -# - ttt = B.sum_abs_col() -# - #TODO this is not working -# numpy.testing.assert_array_almost_equal(z_res[0][0].as_array(), ttt[0][0].as_array(), decimal=4) -# numpy.testing.assert_array_almost_equal(z_res[0][1].as_array(), ttt[0][1].as_array(), decimal=4) -# numpy.testing.assert_array_almost_equal(z_res[1].as_array(), ttt[1].as_array(), decimal=4) - - - u = ig.allocate('random_int') - - z1 = B.direct(u) - res = B.range_geometry().allocate() - - B.direct(u, out = res) - - - - ########################################################################### - # Block Operator for TGV reconstruction - - M, N = 2,3 - ig = ImageGeometry(M, N) - ag = ig - - op11 = Gradient(ig) - op12 = Identity(op11.range_geometry()) - - op22 = SymmetrizedGradient(op11.domain_geometry()) - - op21 = ZeroOperator(ig, op22.range_geometry()) - - - op31 = Identity(ig, ag) - op32 = ZeroOperator(op22.domain_geometry(), ag) - - operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) - - z1 = operator.domain_geometry() - z2 = operator.range_geometry() - - print(z1.shape) - print(z2.shape) - - - - - - - - - - - \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockScaledOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockScaledOperator.py deleted file mode 100644 index aeb6c53..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/BlockScaledOperator.py +++ /dev/null @@ -1,67 +0,0 @@ -from numbers import Number -import numpy -from ccpi.optimisation.operators import ScaledOperator -import functools - -class BlockScaledOperator(ScaledOperator): - '''ScaledOperator - - A class to represent the scalar multiplication of an Operator with a scalar. - It holds an operator and a scalar. Basically it returns the multiplication - of the result of direct and adjoint of the operator with the scalar. - For the rest it behaves like the operator it holds. - - Args: - operator (Operator): a Operator or LinearOperator - scalar (Number): a scalar multiplier - Example: - The scaled operator behaves like the following: - sop = ScaledOperator(operator, scalar) - sop.direct(x) = scalar * operator.direct(x) - sop.adjoint(x) = scalar * operator.adjoint(x) - sop.norm() = operator.norm() - sop.range_geometry() = operator.range_geometry() - sop.domain_geometry() = operator.domain_geometry() - ''' - def __init__(self, operator, scalar, shape=None): - if shape is None: - shape = operator.shape - - if isinstance(scalar, (list, tuple, numpy.ndarray)): - size = functools.reduce(lambda x,y:x*y, shape, 1) - if len(scalar) != size: - raise ValueError('Scalar and operators size do not match: {}!={}' - .format(len(scalar), len(operator))) - self.scalar = scalar[:] - print ("BlockScaledOperator ", self.scalar) - elif isinstance (scalar, Number): - self.scalar = scalar - else: - raise TypeError('expected scalar to be a number of an iterable: got {}'.format(type(scalar))) - self.operator = operator - self.shape = shape - def direct(self, x, out=None): - print ("BlockScaledOperator self.scalar", self.scalar) - #print ("self.scalar", self.scalar[0]* x.get_item(0).as_array()) - return self.scalar * (self.operator.direct(x, out=out)) - def adjoint(self, x, out=None): - if self.operator.is_linear(): - return self.scalar * self.operator.adjoint(x, out=out) - else: - raise TypeError('Operator is not linear') - def norm(self): - return numpy.abs(self.scalar) * self.operator.norm() - def range_geometry(self): - return self.operator.range_geometry() - def domain_geometry(self): - return self.operator.domain_geometry() - @property - def T(self): - '''Return the transposed of self''' - #print ("transpose before" , self.shape) - #shape = (self.shape[1], self.shape[0]) - ##self.shape = shape - ##self.operator.shape = shape - #print ("transpose" , shape) - #return self - return type(self)(self.operator.T, self.scalar) \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator.py deleted file mode 100644 index f459334..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator.py +++ /dev/null @@ -1,372 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 1 22:51:17 2019 - -@author: evangelos -""" - -from ccpi.optimisation.operators import LinearOperator -from ccpi.framework import ImageData, BlockDataContainer -import numpy as np - -class FiniteDiff(LinearOperator): - - # Works for Neum/Symmetric & periodic boundary conditions - # TODO add central differences??? - # TODO not very well optimised, too many conditions - # TODO add discretisation step, should get that from imageGeometry - - # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] - # Grad_order = ['channels', 'direction_y', 'direction_x'] - # Grad_order = ['direction_z', 'direction_y', 'direction_x'] - # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] - - def __init__(self, gm_domain, gm_range=None, direction=0, bnd_cond = 'Neumann'): - '''''' - super(FiniteDiff, self).__init__() - '''FIXME: domain and range should be geometries''' - self.gm_domain = gm_domain - self.gm_range = gm_range - - self.direction = direction - self.bnd_cond = bnd_cond - - # Domain Geometry = Range Geometry if not stated - if self.gm_range is None: - self.gm_range = self.gm_domain - # check direction and "length" of geometry - if self.direction + 1 > len(self.gm_domain.shape): - raise ValueError('Gradient directions more than geometry domain') - - #self.voxel_size = kwargs.get('voxel_size',1) - # this wrongly assumes a homogeneous voxel size -# self.voxel_size = self.gm_domain.voxel_size_x - - - def direct(self, x, out=None): - - x_asarr = x.as_array() - x_sz = len(x.shape) - - if out is None: - out = np.zeros_like(x_asarr) - else: - out = out.as_array() - out[:]=0 - - - - ######################## Direct for 2D ############################### - if x_sz == 2: - - if self.direction == 1: - - np.subtract( x_asarr[:,1:], x_asarr[:,0:-1], out = out[:,0:-1] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0], x_asarr[:,-1], out = out[:,-1] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 0: - - np.subtract( x_asarr[1:], x_asarr[0:-1], out = out[0:-1,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:], x_asarr[-1,:], out = out[-1,:] ) - else: - raise ValueError('No valid boundary conditions') - - ######################## Direct for 3D ############################### - elif x_sz == 3: - - if self.direction == 0: - - np.subtract( x_asarr[1:,:,:], x_asarr[0:-1,:,:], out = out[0:-1,:,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:,:], x_asarr[-1,:,:], out = out[-1,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 1: - - np.subtract( x_asarr[:,1:,:], x_asarr[:,0:-1,:], out = out[:,0:-1,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0,:], x_asarr[:,-1,:], out = out[:,-1,:] ) - else: - raise ValueError('No valid boundary conditions') - - - if self.direction == 2: - - np.subtract( x_asarr[:,:,1:], x_asarr[:,:,0:-1], out = out[:,:,0:-1] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,0], x_asarr[:,:,-1], out = out[:,:,-1] ) - else: - raise ValueError('No valid boundary conditions') - - ######################## Direct for 4D ############################### - elif x_sz == 4: - - if self.direction == 0: - np.subtract( x_asarr[1:,:,:,:], x_asarr[0:-1,:,:,:], out = out[0:-1,:,:,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:,:,:], x_asarr[-1,:,:,:], out = out[-1,:,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 1: - np.subtract( x_asarr[:,1:,:,:], x_asarr[:,0:-1,:,:], out = out[:,0:-1,:,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0,:,:], x_asarr[:,-1,:,:], out = out[:,-1,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 2: - np.subtract( x_asarr[:,:,1:,:], x_asarr[:,:,0:-1,:], out = out[:,:,0:-1,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,0,:], x_asarr[:,:,-1,:], out = out[:,:,-1,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 3: - np.subtract( x_asarr[:,:,:,1:], x_asarr[:,:,:,0:-1], out = out[:,:,:,0:-1] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,:,0], x_asarr[:,:,:,-1], out = out[:,:,:,-1] ) - else: - raise ValueError('No valid boundary conditions') - - else: - raise NotImplementedError - -# res = out #/self.voxel_size - return type(x)(out) - - - def adjoint(self, x, out=None): - - x_asarr = x.as_array() - x_sz = len(x.shape) - - if out is None: - out = np.zeros_like(x_asarr) - else: - out = out.as_array() - out[:]=0 - - - ######################## Adjoint for 2D ############################### - if x_sz == 2: - - if self.direction == 1: - - np.subtract( x_asarr[:,1:], x_asarr[:,0:-1], out = out[:,1:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,0], 0, out = out[:,0] ) - np.subtract( -x_asarr[:,-2], 0, out = out[:,-1] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0], x_asarr[:,-1], out = out[:,0] ) - - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 0: - - np.subtract( x_asarr[1:,:], x_asarr[0:-1,:], out = out[1:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[0,:], 0, out = out[0,:] ) - np.subtract( -x_asarr[-2,:], 0, out = out[-1,:] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:], x_asarr[-1,:], out = out[0,:] ) - - else: - raise ValueError('No valid boundary conditions') - - ######################## Adjoint for 3D ############################### - elif x_sz == 3: - - if self.direction == 0: - - np.subtract( x_asarr[1:,:,:], x_asarr[0:-1,:,:], out = out[1:,:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[0,:,:], 0, out = out[0,:,:] ) - np.subtract( -x_asarr[-2,:,:], 0, out = out[-1,:,:] ) - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:,:], x_asarr[-1,:,:], out = out[0,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 1: - np.subtract( x_asarr[:,1:,:], x_asarr[:,0:-1,:], out = out[:,1:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,0,:], 0, out = out[:,0,:] ) - np.subtract( -x_asarr[:,-2,:], 0, out = out[:,-1,:] ) - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0,:], x_asarr[:,-1,:], out = out[:,0,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 2: - np.subtract( x_asarr[:,:,1:], x_asarr[:,:,0:-1], out = out[:,:,1:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,:,0], 0, out = out[:,:,0] ) - np.subtract( -x_asarr[:,:,-2], 0, out = out[:,:,-1] ) - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,0], x_asarr[:,:,-1], out = out[:,:,0] ) - else: - raise ValueError('No valid boundary conditions') - - ######################## Adjoint for 4D ############################### - elif x_sz == 4: - - if self.direction == 0: - np.subtract( x_asarr[1:,:,:,:], x_asarr[0:-1,:,:,:], out = out[1:,:,:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[0,:,:,:], 0, out = out[0,:,:,:] ) - np.subtract( -x_asarr[-2,:,:,:], 0, out = out[-1,:,:,:] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:,:,:], x_asarr[-1,:,:,:], out = out[0,:,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 1: - np.subtract( x_asarr[:,1:,:,:], x_asarr[:,0:-1,:,:], out = out[:,1:,:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,0,:,:], 0, out = out[:,0,:,:] ) - np.subtract( -x_asarr[:,-2,:,:], 0, out = out[:,-1,:,:] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0,:,:], x_asarr[:,-1,:,:], out = out[:,0,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - - if self.direction == 2: - np.subtract( x_asarr[:,:,1:,:], x_asarr[:,:,0:-1,:], out = out[:,:,1:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,:,0,:], 0, out = out[:,:,0,:] ) - np.subtract( -x_asarr[:,:,-2,:], 0, out = out[:,:,-1,:] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,0,:], x_asarr[:,:,-1,:], out = out[:,:,0,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 3: - np.subtract( x_asarr[:,:,:,1:], x_asarr[:,:,:,0:-1], out = out[:,:,:,1:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,:,:,0], 0, out = out[:,:,:,0] ) - np.subtract( -x_asarr[:,:,:,-2], 0, out = out[:,:,:,-1] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,:,0], x_asarr[:,:,:,-1], out = out[:,:,:,0] ) - else: - raise ValueError('No valid boundary conditions') - - else: - raise NotImplementedError - - out *= -1 #/self.voxel_size - return type(x)(out) - - def range_geometry(self): - '''Returns the range geometry''' - return self.gm_range - - def domain_geometry(self): - '''Returns the domain geometry''' - return self.gm_domain - - def norm(self): - x0 = self.gm_domain.allocate('random_int') - self.s1, sall, svec = LinearOperator.PowerMethod(self, 25, x0) - return self.s1 - - -if __name__ == '__main__': - - from ccpi.framework import ImageGeometry - import numpy - - N, M = 2, 3 - - ig = ImageGeometry(N, M) - - - FD = FiniteDiff(ig, direction = 1, bnd_cond = 'Neumann') - u = FD.domain_geometry().allocate('random_int') - - res = FD.domain_geometry().allocate() - res1 = FD.range_geometry().allocate() - FD.direct(u, out=res) - - z = FD.direct(u) -# print(z.as_array(), res.as_array()) - - for i in range(10): -# - z1 = FD.direct(u) - FD.direct(u, out=res) - - u = ig.allocate('random_int') - res = u - z1 = u - numpy.testing.assert_array_almost_equal(z1.as_array(), \ - res.as_array(), decimal=4) - -# print(z1.as_array(), res.as_array()) - z2 = FD.adjoint(z1) - FD.adjoint(z1, out=res1) - numpy.testing.assert_array_almost_equal(z2.as_array(), \ - res1.as_array(), decimal=4) - - - - - - - - -# w = G.range_geometry().allocate('random_int') - - - - \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py deleted file mode 100644 index 387fb4b..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py +++ /dev/null @@ -1,374 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 1 22:51:17 2019 - -@author: evangelos -""" - -from ccpi.optimisation.operators import LinearOperator -from ccpi.optimisation.ops import PowerMethodNonsquare -from ccpi.framework import ImageData, BlockDataContainer -import numpy as np - -class FiniteDiff(LinearOperator): - - # Works for Neum/Symmetric & periodic boundary conditions - # TODO add central differences??? - # TODO not very well optimised, too many conditions - # TODO add discretisation step, should get that from imageGeometry - - # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] - # Grad_order = ['channels', 'direction_y', 'direction_x'] - # Grad_order = ['direction_z', 'direction_y', 'direction_x'] - # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] - - def __init__(self, gm_domain, gm_range=None, direction=0, bnd_cond = 'Neumann'): - '''''' - super(FiniteDiff, self).__init__() - '''FIXME: domain and range should be geometries''' - self.gm_domain = gm_domain - self.gm_range = gm_range - - self.direction = direction - self.bnd_cond = bnd_cond - - # Domain Geometry = Range Geometry if not stated - if self.gm_range is None: - self.gm_range = self.gm_domain - # check direction and "length" of geometry - if self.direction + 1 > len(self.gm_domain.shape): - raise ValueError('Gradient directions more than geometry domain') - - #self.voxel_size = kwargs.get('voxel_size',1) - # this wrongly assumes a homogeneous voxel size - self.voxel_size = self.gm_domain.voxel_size_x - - - def direct(self, x, out=None): - - x_asarr = x.as_array() - x_sz = len(x.shape) - - if out is None: - out = np.zeros_like(x_asarr) - fd_arr = out - else: - fd_arr = out.as_array() -# fd_arr[:]=0 - -# if out is None: -# out = self.gm_domain.allocate().as_array() -# -# fd_arr = out.as_array() -# fd_arr = self.gm_domain.allocate().as_array() - - ######################## Direct for 2D ############################### - if x_sz == 2: - - if self.direction == 1: - - np.subtract( x_asarr[:,1:], x_asarr[:,0:-1], out = fd_arr[:,0:-1] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0], x_asarr[:,-1], out = fd_arr[:,-1] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 0: - - np.subtract( x_asarr[1:], x_asarr[0:-1], out = fd_arr[0:-1,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:], x_asarr[-1,:], out = fd_arr[-1,:] ) - else: - raise ValueError('No valid boundary conditions') - - ######################## Direct for 3D ############################### - elif x_sz == 3: - - if self.direction == 0: - - np.subtract( x_asarr[1:,:,:], x_asarr[0:-1,:,:], out = fd_arr[0:-1,:,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:,:], x_asarr[-1,:,:], out = fd_arr[-1,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 1: - - np.subtract( x_asarr[:,1:,:], x_asarr[:,0:-1,:], out = fd_arr[:,0:-1,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0,:], x_asarr[:,-1,:], out = fd_arr[:,-1,:] ) - else: - raise ValueError('No valid boundary conditions') - - - if self.direction == 2: - - np.subtract( x_asarr[:,:,1:], x_asarr[:,:,0:-1], out = fd_arr[:,:,0:-1] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,0], x_asarr[:,:,-1], out = fd_arr[:,:,-1] ) - else: - raise ValueError('No valid boundary conditions') - - ######################## Direct for 4D ############################### - elif x_sz == 4: - - if self.direction == 0: - np.subtract( x_asarr[1:,:,:,:], x_asarr[0:-1,:,:,:], out = fd_arr[0:-1,:,:,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:,:,:], x_asarr[-1,:,:,:], out = fd_arr[-1,:,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 1: - np.subtract( x_asarr[:,1:,:,:], x_asarr[:,0:-1,:,:], out = fd_arr[:,0:-1,:,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0,:,:], x_asarr[:,-1,:,:], out = fd_arr[:,-1,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 2: - np.subtract( x_asarr[:,:,1:,:], x_asarr[:,:,0:-1,:], out = fd_arr[:,:,0:-1,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,0,:], x_asarr[:,:,-1,:], out = fd_arr[:,:,-1,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 3: - np.subtract( x_asarr[:,:,:,1:], x_asarr[:,:,:,0:-1], out = fd_arr[:,:,:,0:-1] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,:,0], x_asarr[:,:,:,-1], out = fd_arr[:,:,:,-1] ) - else: - raise ValueError('No valid boundary conditions') - - else: - raise NotImplementedError - -# res = out #/self.voxel_size - return type(x)(out) - - - def adjoint(self, x, out=None): - - x_asarr = x.as_array() - #x_asarr = x - x_sz = len(x.shape) - - if out is None: - out = np.zeros_like(x_asarr) - fd_arr = out - else: - fd_arr = out.as_array() - -# if out is None: -# out = self.gm_domain.allocate().as_array() -# fd_arr = out -# else: -# fd_arr = out.as_array() -## fd_arr = self.gm_domain.allocate().as_array() - - ######################## Adjoint for 2D ############################### - if x_sz == 2: - - if self.direction == 1: - - np.subtract( x_asarr[:,1:], x_asarr[:,0:-1], out = fd_arr[:,1:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,0], 0, out = fd_arr[:,0] ) - np.subtract( -x_asarr[:,-2], 0, out = fd_arr[:,-1] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0], x_asarr[:,-1], out = fd_arr[:,0] ) - - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 0: - - np.subtract( x_asarr[1:,:], x_asarr[0:-1,:], out = fd_arr[1:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[0,:], 0, out = fd_arr[0,:] ) - np.subtract( -x_asarr[-2,:], 0, out = fd_arr[-1,:] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:], x_asarr[-1,:], out = fd_arr[0,:] ) - - else: - raise ValueError('No valid boundary conditions') - - ######################## Adjoint for 3D ############################### - elif x_sz == 3: - - if self.direction == 0: - - np.subtract( x_asarr[1:,:,:], x_asarr[0:-1,:,:], out = fd_arr[1:,:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[0,:,:], 0, out = fd_arr[0,:,:] ) - np.subtract( -x_asarr[-2,:,:], 0, out = fd_arr[-1,:,:] ) - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:,:], x_asarr[-1,:,:], out = fd_arr[0,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 1: - np.subtract( x_asarr[:,1:,:], x_asarr[:,0:-1,:], out = fd_arr[:,1:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,0,:], 0, out = fd_arr[:,0,:] ) - np.subtract( -x_asarr[:,-2,:], 0, out = fd_arr[:,-1,:] ) - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0,:], x_asarr[:,-1,:], out = fd_arr[:,0,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 2: - np.subtract( x_asarr[:,:,1:], x_asarr[:,:,0:-1], out = fd_arr[:,:,1:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,:,0], 0, out = fd_arr[:,:,0] ) - np.subtract( -x_asarr[:,:,-2], 0, out = fd_arr[:,:,-1] ) - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,0], x_asarr[:,:,-1], out = fd_arr[:,:,0] ) - else: - raise ValueError('No valid boundary conditions') - - ######################## Adjoint for 4D ############################### - elif x_sz == 4: - - if self.direction == 0: - np.subtract( x_asarr[1:,:,:,:], x_asarr[0:-1,:,:,:], out = fd_arr[1:,:,:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[0,:,:,:], 0, out = fd_arr[0,:,:,:] ) - np.subtract( -x_asarr[-2,:,:,:], 0, out = fd_arr[-1,:,:,:] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:,:,:], x_asarr[-1,:,:,:], out = fd_arr[0,:,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 1: - np.subtract( x_asarr[:,1:,:,:], x_asarr[:,0:-1,:,:], out = fd_arr[:,1:,:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,0,:,:], 0, out = fd_arr[:,0,:,:] ) - np.subtract( -x_asarr[:,-2,:,:], 0, out = fd_arr[:,-1,:,:] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0,:,:], x_asarr[:,-1,:,:], out = fd_arr[:,0,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - - if self.direction == 2: - np.subtract( x_asarr[:,:,1:,:], x_asarr[:,:,0:-1,:], out = fd_arr[:,:,1:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,:,0,:], 0, out = fd_arr[:,:,0,:] ) - np.subtract( -x_asarr[:,:,-2,:], 0, out = fd_arr[:,:,-1,:] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,0,:], x_asarr[:,:,-1,:], out = fd_arr[:,:,0,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 3: - np.subtract( x_asarr[:,:,:,1:], x_asarr[:,:,:,0:-1], out = fd_arr[:,:,:,1:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,:,:,0], 0, out = fd_arr[:,:,:,0] ) - np.subtract( -x_asarr[:,:,:,-2], 0, out = fd_arr[:,:,:,-1] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,:,0], x_asarr[:,:,:,-1], out = fd_arr[:,:,:,0] ) - else: - raise ValueError('No valid boundary conditions') - - else: - raise NotImplementedError - - out *= -1 #/self.voxel_size - return type(x)(out) - - def range_geometry(self): - '''Returns the range geometry''' - return self.gm_range - - def domain_geometry(self): - '''Returns the domain geometry''' - return self.gm_domain - - def norm(self): - x0 = self.gm_domain.allocate() - x0.fill( np.random.random_sample(x0.shape) ) - self.s1, sall, svec = PowerMethodNonsquare(self, 25, x0) - return self.s1 - - -if __name__ == '__main__': - - from ccpi.framework import ImageGeometry - import numpy - - N, M = 2, 3 - - ig = ImageGeometry(N, M) - - - FD = FiniteDiff(ig, direction = 0, bnd_cond = 'Neumann') - u = FD.domain_geometry().allocate('random_int') - - - res = FD.domain_geometry().allocate() - FD.direct(u, out=res) - - z = FD.direct(u) - print(z.as_array(), res.as_array()) - - for i in range(10): - - z1 = FD.direct(u) - FD.direct(u, out=res) - numpy.testing.assert_array_almost_equal(z1.as_array(), \ - res.as_array(), decimal=4) - - - - - - -# w = G.range_geometry().allocate('random_int') - - - - \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py deleted file mode 100644 index 6ffaf70..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/GradientOperator.py +++ /dev/null @@ -1,242 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 1 22:50:04 2019 - -@author: evangelos -""" - -from ccpi.optimisation.operators import Operator, LinearOperator, ScaledOperator -from ccpi.framework import ImageData, ImageGeometry, BlockGeometry, BlockDataContainer -import numpy -from ccpi.optimisation.operators import FiniteDiff, SparseFiniteDiff - -#%% - -class Gradient(LinearOperator): - - def __init__(self, gm_domain, bnd_cond = 'Neumann', **kwargs): - - super(Gradient, self).__init__() - - self.gm_domain = gm_domain # Domain of Grad Operator - - self.correlation = kwargs.get('correlation','Space') - - if self.correlation=='Space': - if self.gm_domain.channels>1: - self.gm_range = BlockGeometry(*[self.gm_domain for _ in range(self.gm_domain.length-1)] ) - self.ind = numpy.arange(1,self.gm_domain.length) - else: - self.gm_range = BlockGeometry(*[self.gm_domain for _ in range(self.gm_domain.length) ] ) - self.ind = numpy.arange(self.gm_domain.length) - elif self.correlation=='SpaceChannels': - if self.gm_domain.channels>1: - self.gm_range = BlockGeometry(*[self.gm_domain for _ in range(self.gm_domain.length)]) - self.ind = range(self.gm_domain.length) - else: - raise ValueError('No channels to correlate') - - self.bnd_cond = bnd_cond - - # Call FiniteDiff operator - - self.FD = FiniteDiff(self.gm_domain, direction = 0, bnd_cond = self.bnd_cond) - - - def direct(self, x, out=None): - - - if out is not None: - - for i in range(self.gm_range.shape[0]): - self.FD.direction = self.ind[i] - self.FD.direct(x, out = out[i]) - else: - tmp = self.gm_range.allocate() - for i in range(tmp.shape[0]): - self.FD.direction=self.ind[i] - tmp.get_item(i).fill(self.FD.direct(x)) - return tmp - - def adjoint(self, x, out=None): - - if out is not None: - - tmp = self.gm_domain.allocate() - for i in range(x.shape[0]): - self.FD.direction=self.ind[i] - self.FD.adjoint(x.get_item(i), out = tmp) - if i == 0: - out.fill(tmp) - else: - out += tmp - else: - tmp = self.gm_domain.allocate() - for i in range(x.shape[0]): - self.FD.direction=self.ind[i] - - tmp += self.FD.adjoint(x.get_item(i)) - return tmp - - - def domain_geometry(self): - return self.gm_domain - - def range_geometry(self): - return self.gm_range - - def norm(self): - - x0 = self.gm_domain.allocate('random') - self.s1, sall, svec = LinearOperator.PowerMethod(self, 10, x0) - return self.s1 - - def __rmul__(self, scalar): - return ScaledOperator(self, scalar) - - ########################################################################### - ############### For preconditioning ###################################### - ########################################################################### - def matrix(self): - - tmp = self.gm_range.allocate() - - mat = [] - for i in range(tmp.shape[0]): - - spMat = SparseFiniteDiff(self.gm_domain, direction=self.ind[i], bnd_cond=self.bnd_cond) - mat.append(spMat.matrix()) - - return BlockDataContainer(*mat) - - - def sum_abs_col(self): - - tmp = self.gm_range.allocate() - res = self.gm_domain.allocate() - for i in range(tmp.shape[0]): - spMat = SparseFiniteDiff(self.gm_domain, direction=self.ind[i], bnd_cond=self.bnd_cond) - res += spMat.sum_abs_row() - return res - - def sum_abs_row(self): - - tmp = self.gm_range.allocate() - res = [] - for i in range(tmp.shape[0]): - spMat = SparseFiniteDiff(self.gm_domain, direction=self.ind[i], bnd_cond=self.bnd_cond) - res.append(spMat.sum_abs_col()) - return BlockDataContainer(*res) - - -if __name__ == '__main__': - - - from ccpi.optimisation.operators import Identity, BlockOperator - - - M, N = 2, 3 - ig = ImageGeometry(M, N) - arr = ig.allocate('random_int' ) - - # check direct of Gradient and sparse matrix - G = Gradient(ig) - G_sp = G.matrix() - - res1 = G.direct(arr) - res1y = numpy.reshape(G_sp[0].toarray().dot(arr.as_array().flatten('F')), ig.shape, 'F') - - print(res1[0].as_array()) - print(res1y) - - res1x = numpy.reshape(G_sp[1].toarray().dot(arr.as_array().flatten('F')), ig.shape, 'F') - - print(res1[1].as_array()) - print(res1x) - - #check sum abs row - conc_spmat = numpy.abs(numpy.concatenate( (G_sp[0].toarray(), G_sp[1].toarray() ))) - print(numpy.reshape(conc_spmat.sum(axis=0), ig.shape, 'F')) - print(G.sum_abs_row().as_array()) - - print(numpy.reshape(conc_spmat.sum(axis=1), ((2,) + ig.shape), 'F')) - - print(G.sum_abs_col()[0].as_array()) - print(G.sum_abs_col()[1].as_array()) - - # Check Blockoperator sum abs col and row - - op1 = Gradient(ig) - op2 = Identity(ig) - - B = BlockOperator( op1, op2) - - Brow = B.sum_abs_row() - Bcol = B.sum_abs_col() - - concB = numpy.concatenate( (numpy.abs(numpy.concatenate( (G_sp[0].toarray(), G_sp[1].toarray() ))), op2.matrix().toarray())) - - print(numpy.reshape(concB.sum(axis=0), ig.shape, 'F')) - print(Brow.as_array()) - - print(numpy.reshape(concB.sum(axis=1)[0:12], ((2,) + ig.shape), 'F')) - print(Bcol[1].as_array()) - - -# print(numpy.concatene(G_sp[0].toarray()+ )) -# print(G_sp[1].toarray()) -# -# d1 = G.sum_abs_row() -# print(d1.as_array()) -# -# d2 = G_neum.sum_abs_col() -## print(d2) -# -# -# ########################################################### - a = BlockDataContainer( BlockDataContainer(arr, arr), arr) - b = BlockDataContainer( BlockDataContainer(arr+5, arr+3), arr+2) - c = a/b - - print(c[0][0].as_array(), (arr/(arr+5)).as_array()) - print(c[0][1].as_array(), (arr/(arr+3)).as_array()) - print(c[1].as_array(), (arr/(arr+2)).as_array()) - - - a1 = BlockDataContainer( arr, BlockDataContainer(arr, arr)) -# -# c1 = arr + a -# c2 = arr + a -# c2 = a1 + arr - - from ccpi.framework import ImageGeometry -# from ccpi.optimisation.operators import Gradient -# - N, M = 2, 3 -# - ig = ImageGeometry(N, M) -# - G = Gradient(ig) -# - u = G.domain_geometry().allocate('random_int') - w = G.range_geometry().allocate('random_int') - - - print( "################ without out #############") - - print( (G.direct(u)*w).sum(), (u*G.adjoint(w)).sum() ) - - - print( "################ with out #############") - - res = G.range_geometry().allocate() - res1 = G.domain_geometry().allocate() - G.direct(u, out = res) - G.adjoint(w, out = res1) - - print( (res*w).sum(), (u*res1).sum() ) - - - - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/IdentityOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/IdentityOperator.py deleted file mode 100644 index a853b8d..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/IdentityOperator.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Wed Mar 6 19:30:51 2019 - -@author: evangelos -""" - -from ccpi.optimisation.operators import LinearOperator -import scipy.sparse as sp -import numpy as np -from ccpi.framework import ImageData - - -class Identity(LinearOperator): - - def __init__(self, gm_domain, gm_range=None): - - self.gm_domain = gm_domain - self.gm_range = gm_range - if self.gm_range is None: - self.gm_range = self.gm_domain - - super(Identity, self).__init__() - - def direct(self,x,out=None): - if out is None: - return x.copy() - else: - out.fill(x) - - def adjoint(self,x, out=None): - if out is None: - return x.copy() - else: - out.fill(x) - - def norm(self): - return 1.0 - - def domain_geometry(self): - return self.gm_domain - - def range_geometry(self): - return self.gm_range - - def matrix(self): - - return sp.eye(np.prod(self.gm_domain.shape)) - - def sum_abs_row(self): - - return self.gm_range.allocate(1)#ImageData(np.array(np.reshape(abs(self.matrix()).sum(axis=0), self.gm_domain.shape, 'F'))) - - def sum_abs_col(self): - - return self.gm_domain.allocate(1)#ImageData(np.array(np.reshape(abs(self.matrix()).sum(axis=1), self.gm_domain.shape, 'F'))) - - -if __name__ == '__main__': - - from ccpi.framework import ImageGeometry - - M, N = 2, 3 - ig = ImageGeometry(M, N) - arr = ig.allocate('random_int') - - Id = Identity(ig) - d = Id.matrix() - print(d.toarray()) - - d1 = Id.sum_abs_col() - print(d1.as_array()) - - - - - - \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperator.py deleted file mode 100644 index f304cc6..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperator.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Mar 5 15:57:52 2019 - -@author: ofn77899 -""" - -from ccpi.optimisation.operators import Operator -from ccpi.framework import ImageGeometry -import numpy - - -class LinearOperator(Operator): - '''A Linear Operator that maps from a space X <-> Y''' - def __init__(self): - super(LinearOperator, self).__init__() - def is_linear(self): - '''Returns if the operator is linear''' - return True - def adjoint(self,x, out=None): - '''returns the adjoint/inverse operation - - only available to linear operators''' - raise NotImplementedError - - @staticmethod - def PowerMethod(operator, iterations, x_init=None): - '''Power method to calculate iteratively the Lipschitz constant''' - - # Initialise random - if x_init is None: - x0 = operator.domain_geometry().allocate(ImageGeometry.RANDOM_INT) - else: - x0 = x_init.copy() - - x1 = operator.domain_geometry().allocate() - y_tmp = operator.range_geometry().allocate() - s = numpy.zeros(iterations) - # Loop - for it in numpy.arange(iterations): - operator.direct(x0,out=y_tmp) - operator.adjoint(y_tmp,out=x1) - x1norm = x1.norm() - s[it] = x1.dot(x0) / x0.squared_norm() - x1.multiply((1.0/x1norm), out=x0) - return numpy.sqrt(s[-1]), numpy.sqrt(s), x0 - - @staticmethod - def PowerMethodNonsquare(op,numiters , x_init=None): - '''Power method to calculate iteratively the Lipschitz constant''' - - if x_init is None: - x0 = op.allocate_direct() - x0.fill(numpy.random.randn(*x0.shape)) - else: - x0 = x_init.copy() - - s = numpy.zeros(numiters) - # Loop - for it in numpy.arange(numiters): - x1 = op.adjoint(op.direct(x0)) - x1norm = x1.norm() - s[it] = (x1*x0).sum() / (x0.squared_norm()) - x0 = (1.0/x1norm)*x1 - return numpy.sqrt(s[-1]), numpy.sqrt(s), x0 - - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperatorMatrix.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperatorMatrix.py deleted file mode 100644 index 62e22e0..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/LinearOperatorMatrix.py +++ /dev/null @@ -1,51 +0,0 @@ -import numpy -from scipy.sparse.linalg import svds -from ccpi.framework import DataContainer -from ccpi.framework import AcquisitionData -from ccpi.framework import ImageData -from ccpi.framework import ImageGeometry -from ccpi.framework import AcquisitionGeometry -from numbers import Number -from ccpi.optimisation.operators import LinearOperator -class LinearOperatorMatrix(LinearOperator): - def __init__(self,A): - self.A = A - self.s1 = None # Largest singular value, initially unknown - super(LinearOperatorMatrix, self).__init__() - - def direct(self,x, out=None): - if out is None: - return type(x)(numpy.dot(self.A,x.as_array())) - else: - numpy.dot(self.A, x.as_array(), out=out.as_array()) - - - def adjoint(self,x, out=None): - if out is None: - return type(x)(numpy.dot(self.A.transpose(),x.as_array())) - else: - numpy.dot(self.A.transpose(),x.as_array(), out=out.as_array()) - - - def size(self): - return self.A.shape - - def get_max_sing_val(self): - # If unknown, compute and store. If known, simply return it. - if self.s1 is None: - self.s1 = svds(self.A,1,return_singular_vectors=False)[0] - return self.s1 - else: - return self.s1 - def allocate_direct(self): - '''allocates the memory to hold the result of adjoint''' - #numpy.dot(self.A.transpose(),x.as_array()) - M_A, N_A = self.A.shape - out = numpy.zeros((N_A,1)) - return DataContainer(out) - def allocate_adjoint(self): - '''allocate the memory to hold the result of direct''' - #numpy.dot(self.A.transpose(),x.as_array()) - M_A, N_A = self.A.shape - out = numpy.zeros((M_A,1)) - return DataContainer(out) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/Operator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/Operator.py deleted file mode 100644 index 2d2089b..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/Operator.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Mar 5 15:55:56 2019 - -@author: ofn77899 -""" -from ccpi.optimisation.operators.ScaledOperator import ScaledOperator - -class Operator(object): - '''Operator that maps from a space X -> Y''' - def is_linear(self): - '''Returns if the operator is linear''' - return False - def direct(self,x, out=None): - '''Returns the application of the Operator on x''' - raise NotImplementedError - def norm(self): - '''Returns the norm of the Operator''' - raise NotImplementedError - def range_geometry(self): - '''Returns the range of the Operator: Y space''' - raise NotImplementedError - def domain_geometry(self): - '''Returns the domain of the Operator: X space''' - raise NotImplementedError - def __rmul__(self, scalar): - '''Defines the multiplication by a scalar on the left - - returns a ScaledOperator''' - return ScaledOperator(self, scalar) diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ScaledOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ScaledOperator.py deleted file mode 100644 index ba0049e..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ScaledOperator.py +++ /dev/null @@ -1,51 +0,0 @@ -from numbers import Number -import numpy - -class ScaledOperator(object): - '''ScaledOperator - A class to represent the scalar multiplication of an Operator with a scalar. - It holds an operator and a scalar. Basically it returns the multiplication - of the result of direct and adjoint of the operator with the scalar. - For the rest it behaves like the operator it holds. - Args: - operator (Operator): a Operator or LinearOperator - scalar (Number): a scalar multiplier - Example: - The scaled operator behaves like the following: - sop = ScaledOperator(operator, scalar) - sop.direct(x) = scalar * operator.direct(x) - sop.adjoint(x) = scalar * operator.adjoint(x) - sop.norm() = operator.norm() - sop.range_geometry() = operator.range_geometry() - sop.domain_geometry() = operator.domain_geometry() - ''' - def __init__(self, operator, scalar): - super(ScaledOperator, self).__init__() - if not isinstance (scalar, Number): - raise TypeError('expected scalar: got {}'.format(type(scalar))) - self.scalar = scalar - self.operator = operator - def direct(self, x, out=None): - if out is None: - return self.scalar * self.operator.direct(x, out=out) - else: - self.operator.direct(x, out=out) - out *= self.scalar - def adjoint(self, x, out=None): - if self.operator.is_linear(): - if out is None: - return self.scalar * self.operator.adjoint(x, out=out) - else: - self.operator.adjoint(x, out=out) - out *= self.scalar - else: - raise TypeError('Operator is not linear') - def norm(self): - return numpy.abs(self.scalar) * self.operator.norm() - def range_geometry(self): - return self.operator.range_geometry() - def domain_geometry(self): - return self.operator.domain_geometry() - def is_linear(self): - return self.operator.is_linear() - diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ShrinkageOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ShrinkageOperator.py deleted file mode 100644 index f47c655..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ShrinkageOperator.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Wed Mar 6 19:30:51 2019 - -@author: evangelos -""" - -from ccpi.framework import DataContainer - -class ShrinkageOperator(): - - def __init__(self): - pass - - def __call__(self, x, tau, out=None): - - return x.sign() * (x.abs() - tau).maximum(0) - \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/SparseFiniteDiff.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/SparseFiniteDiff.py deleted file mode 100644 index c5c2ec9..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/SparseFiniteDiff.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Tue Apr 2 14:06:15 2019 - -@author: vaggelis -""" - -import scipy.sparse as sp -import numpy as np -from ccpi.framework import ImageData - -class SparseFiniteDiff(): - - def __init__(self, gm_domain, gm_range=None, direction=0, bnd_cond = 'Neumann'): - - super(SparseFiniteDiff, self).__init__() - self.gm_domain = gm_domain - self.gm_range = gm_range - self.direction = direction - self.bnd_cond = bnd_cond - - if self.gm_range is None: - self.gm_range = self.gm_domain - - self.get_dims = [i for i in gm_domain.shape] - - if self.direction + 1 > len(self.gm_domain.shape): - raise ValueError('Gradient directions more than geometry domain') - - def matrix(self): - - i = self.direction - - mat = sp.spdiags(np.vstack([-np.ones((1,self.get_dims[i])),np.ones((1,self.get_dims[i]))]), [0,1], self.get_dims[i], self.get_dims[i], format = 'lil') - - if self.bnd_cond == 'Neumann': - mat[-1,:] = 0 - elif self.bnd_cond == 'Periodic': - mat[-1,0] = 1 - - tmpGrad = mat if i == 0 else sp.eye(self.get_dims[0]) - - for j in range(1, self.gm_domain.length): - - tmpGrad = sp.kron(mat, tmpGrad ) if j == i else sp.kron(sp.eye(self.get_dims[j]), tmpGrad ) - - return tmpGrad - - def T(self): - return self.matrix().T - - def direct(self, x): - - x_asarr = x.as_array() - res = np.reshape( self.matrix() * x_asarr.flatten('F'), self.gm_domain.shape, 'F') - return type(x)(res) - - def adjoint(self, x): - - x_asarr = x.as_array() - res = np.reshape( self.matrix().T * x_asarr.flatten('F'), self.gm_domain.shape, 'F') - return type(x)(res) - - def sum_abs_row(self): - - res = np.array(np.reshape(abs(self.matrix()).sum(axis=0), self.gm_domain.shape, 'F')) - #res[res==0]=0 - return ImageData(res) - - def sum_abs_col(self): - - res = np.array(np.reshape(abs(self.matrix()).sum(axis=1), self.gm_domain.shape, 'F') ) - #res[res==0]=0 - return ImageData(res) - -if __name__ == '__main__': - - from ccpi.framework import ImageGeometry - from ccpi.optimisation.operators import FiniteDiff - - # 2D - M, N= 2, 3 - ig = ImageGeometry(M, N) - arr = ig.allocate('random_int') - - for i in [0,1]: - - # Neumann - sFD_neum = SparseFiniteDiff(ig, direction=i, bnd_cond='Neumann') - G_neum = FiniteDiff(ig, direction=i, bnd_cond='Neumann') - - # Periodic - sFD_per = SparseFiniteDiff(ig, direction=i, bnd_cond='Periodic') - G_per = FiniteDiff(ig, direction=i, bnd_cond='Periodic') - - u_neum_direct = G_neum.direct(arr) - u_neum_sp_direct = sFD_neum.direct(arr) - np.testing.assert_array_almost_equal(u_neum_direct.as_array(), u_neum_sp_direct.as_array(), decimal=4) - - u_neum_adjoint = G_neum.adjoint(arr) - u_neum_sp_adjoint = sFD_neum.adjoint(arr) - np.testing.assert_array_almost_equal(u_neum_adjoint.as_array(), u_neum_sp_adjoint.as_array(), decimal=4) - - u_per_direct = G_neum.direct(arr) - u_per_sp_direct = sFD_neum.direct(arr) - np.testing.assert_array_almost_equal(u_per_direct.as_array(), u_per_sp_direct.as_array(), decimal=4) - - u_per_adjoint = G_per.adjoint(arr) - u_per_sp_adjoint = sFD_per.adjoint(arr) - np.testing.assert_array_almost_equal(u_per_adjoint.as_array(), u_per_sp_adjoint.as_array(), decimal=4) - - # 3D - M, N, K = 2, 3, 4 - ig3D = ImageGeometry(M, N, K) - arr3D = ig3D.allocate('random_int') - - for i in [0,1,2]: - - # Neumann - sFD_neum3D = SparseFiniteDiff(ig3D, direction=i, bnd_cond='Neumann') - G_neum3D = FiniteDiff(ig3D, direction=i, bnd_cond='Neumann') - - # Periodic - sFD_per3D = SparseFiniteDiff(ig3D, direction=i, bnd_cond='Periodic') - G_per3D = FiniteDiff(ig3D, direction=i, bnd_cond='Periodic') - - u_neum_direct3D = G_neum3D.direct(arr3D) - u_neum_sp_direct3D = sFD_neum3D.direct(arr3D) - np.testing.assert_array_almost_equal(u_neum_direct3D.as_array(), u_neum_sp_direct3D.as_array(), decimal=4) - - u_neum_adjoint3D = G_neum3D.adjoint(arr3D) - u_neum_sp_adjoint3D = sFD_neum3D.adjoint(arr3D) - np.testing.assert_array_almost_equal(u_neum_adjoint3D.as_array(), u_neum_sp_adjoint3D.as_array(), decimal=4) - - u_per_direct3D = G_neum3D.direct(arr3D) - u_per_sp_direct3D = sFD_neum3D.direct(arr3D) - np.testing.assert_array_almost_equal(u_per_direct3D.as_array(), u_per_sp_direct3D.as_array(), decimal=4) - - u_per_adjoint3D = G_per3D.adjoint(arr3D) - u_per_sp_adjoint3D = sFD_per3D.adjoint(arr3D) - np.testing.assert_array_almost_equal(u_per_adjoint3D.as_array(), u_per_sp_adjoint3D.as_array(), decimal=4) - - \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/SymmetrizedGradientOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/SymmetrizedGradientOperator.py deleted file mode 100644 index 243809b..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/SymmetrizedGradientOperator.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 1 22:53:55 2019 - -@author: evangelos -""" - -from ccpi.optimisation.operators import Gradient, Operator, LinearOperator, ScaledOperator -from ccpi.framework import ImageData, ImageGeometry, BlockGeometry, BlockDataContainer -import numpy -from ccpi.optimisation.operators import FiniteDiff, SparseFiniteDiff - - -class SymmetrizedGradient(Gradient): - - ''' Symmetrized Gradient, denoted by E: V --> W - where V is the Range of the Gradient Operator - and W is the Range of the Symmetrized Gradient. - ''' - - - def __init__(self, gm_domain, bnd_cond = 'Neumann', **kwargs): - - super(SymmetrizedGradient, self).__init__(gm_domain, bnd_cond, **kwargs) - - ''' - Domain of SymGrad is the Range of Gradient - ''' - - self.gm_domain = self.gm_range - self.bnd_cond = bnd_cond - - self.channels = self.gm_range.get_item(0).channels - - tmp_gm = len(self.gm_domain.geometries)*self.gm_domain.geometries - - self.gm_range = BlockGeometry(*tmp_gm) - - self.FD = FiniteDiff(self.gm_domain, direction = 0, bnd_cond = self.bnd_cond) - - if self.gm_domain.shape[0]==2: - self.order_ind = [0,2,1,3] - else: - self.order_ind = [0,3,6,1,4,7,2,5,8] - - - def direct(self, x, out=None): - - if out is None: - - tmp = [] - for i in range(self.gm_domain.shape[0]): - for j in range(x.shape[0]): - self.FD.direction = i - tmp.append(self.FD.adjoint(x.get_item(j))) - - tmp1 = [tmp[i] for i in self.order_ind] - - res = [0.5 * sum(x) for x in zip(tmp, tmp1)] - - return BlockDataContainer(*res) - - else: - - ind = 0 - for i in range(self.gm_domain.shape[0]): - for j in range(x.shape[0]): - self.FD.direction = i - self.FD.adjoint(x.get_item(j), out=out[ind]) - ind+=1 - out1 = BlockDataContainer(*[out[i] for i in self.order_ind]) - out.fill( 0.5 * (out + out1) ) - - - def adjoint(self, x, out=None): - - if out is None: - - tmp = [None]*self.gm_domain.shape[0] - i = 0 - - for k in range(self.gm_domain.shape[0]): - tmp1 = 0 - for j in range(self.gm_domain.shape[0]): - self.FD.direction = j - tmp1 += self.FD.direct(x[i]) - i+=1 - tmp[k] = tmp1 - return BlockDataContainer(*tmp) - - - else: - - tmp = self.gm_domain.allocate() - i = 0 - for k in range(self.gm_domain.shape[0]): - tmp1 = 0 - for j in range(self.gm_domain.shape[0]): - self.FD.direction = j - self.FD.direct(x[i], out=tmp[j]) - i+=1 - tmp1+=tmp[j] - out[k].fill(tmp1) -# tmp = self.adjoint(x) -# out.fill(tmp) - - - def domain_geometry(self): - return self.gm_domain - - def range_geometry(self): - return self.gm_range - - def norm(self): - -# TODO need dot method for BlockDataContainer -# return numpy.sqrt(4*self.gm_domain.shape[0]) - -# x0 = self.gm_domain.allocate('random') - self.s1, sall, svec = LinearOperator.PowerMethod(self, 50) - return self.s1 - - - -if __name__ == '__main__': - - ########################################################################### - ## Symmetrized Gradient Tests - from ccpi.framework import DataContainer - from ccpi.optimisation.operators import Gradient, BlockOperator, FiniteDiff - import numpy as np - - N, M = 2, 3 - K = 2 - C = 2 - - ig1 = ImageGeometry(N, M) - ig2 = ImageGeometry(N, M, channels=C) - - E1 = SymmetrizedGradient(ig1, correlation = 'Space', bnd_cond='Neumann') - - try: - E1 = SymmetrizedGradient(ig1, correlation = 'SpaceChannels', bnd_cond='Neumann') - except: - print("No Channels to correlate") - - E2 = SymmetrizedGradient(ig2, correlation = 'SpaceChannels', bnd_cond='Neumann') - - print(E1.domain_geometry().shape, E1.range_geometry().shape) - print(E2.domain_geometry().shape, E2.range_geometry().shape) - - #check Linear operator property - - u1 = E1.domain_geometry().allocate('random_int') - u2 = E2.domain_geometry().allocate('random_int') - - # Need to allocate random_int at the Range of SymGradient - - #a1 = ig1.allocate('random_int') - #a2 = ig1.allocate('random_int') - #a3 = ig1.allocate('random_int') - - #a4 = ig1.allocate('random_int') - #a5 = ig1.allocate('random_int') - #a6 = ig1.allocate('random_int') - - # TODO allocate has to create this symmetry by default!!!!! - #w1 = BlockDataContainer(*[a1, a2, \ - # a2, a3]) - w1 = E1.range_geometry().allocate('random_int',symmetry=True) - - LHS = (E1.direct(u1) * w1).sum() - RHS = (u1 * E1.adjoint(w1)).sum() - - numpy.testing.assert_equal(LHS, RHS) - - u2 = E2.gm_domain.allocate('random_int') - - #aa1 = ig2.allocate('random_int') - #aa2 = ig2.allocate('random_int') - #aa3 = ig2.allocate('random_int') - #aa4 = ig2.allocate('random_int') - #aa5 = ig2.allocate('random_int') - #aa6 = ig2.allocate('random_int') - - #w2 = BlockDataContainer(*[aa1, aa2, aa3, \ - # aa2, aa4, aa5, \ - # aa3, aa5, aa6]) - w2 = E2.range_geometry().allocate('random_int',symmetry=True) - - - LHS1 = (E2.direct(u2) * w2).sum() - RHS1 = (u2 * E2.adjoint(w2)).sum() - - numpy.testing.assert_equal(LHS1, RHS1) - - out = E1.range_geometry().allocate() - E1.direct(u1, out=out) - a1 = E1.direct(u1) - numpy.testing.assert_array_equal(a1[0].as_array(), out[0].as_array()) - numpy.testing.assert_array_equal(a1[1].as_array(), out[1].as_array()) - numpy.testing.assert_array_equal(a1[2].as_array(), out[2].as_array()) - numpy.testing.assert_array_equal(a1[3].as_array(), out[3].as_array()) - - - out1 = E1.domain_geometry().allocate() - E1.adjoint(w1, out=out1) - b1 = E1.adjoint(w1) - - LHS_out = (out * w1).sum() - RHS_out = (u1 * out1).sum() - print(LHS_out, RHS_out) - - - out2 = E2.range_geometry().allocate() - E2.direct(u2, out=out2) - a2 = E2.direct(u2) - - out21 = E2.domain_geometry().allocate() - E2.adjoint(w2, out=out21) - b2 = E2.adjoint(w2) - - LHS_out = (out2 * w2).sum() - RHS_out = (u2 * out21).sum() - print(LHS_out, RHS_out) - - - out = E1.range_geometry().allocate() - E1.direct(u1, out=out) - E1.adjoint(out, out=out1) - - print(E1.norm()) - print(E2.norm()) - - - - - - -# -# -# -# \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ZeroOperator.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/ZeroOperator.py deleted file mode 100644 index 8168f0b..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/ZeroOperator.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Wed Mar 6 19:25:53 2019 - -@author: evangelos -""" - -import numpy as np -from ccpi.framework import ImageData -from ccpi.optimisation.operators import LinearOperator - -class ZeroOperator(LinearOperator): - - def __init__(self, gm_domain, gm_range=None): - - super(ZeroOperator, self).__init__() - - self.gm_domain = gm_domain - self.gm_range = gm_range - if self.gm_range is None: - self.gm_range = self.gm_domain - - - def direct(self,x,out=None): - if out is None: - return self.gm_range.allocate() - else: - out.fill(self.gm_range.allocate()) - - def adjoint(self,x, out=None): - if out is None: - return self.gm_domain.allocate() - else: - out.fill(self.gm_domain.allocate()) - - def norm(self): - return 0 - - def domain_geometry(self): - return self.gm_domain - - def range_geometry(self): - return self.gm_range \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/optimisation/operators/__init__.py b/Wrappers/Python/build/lib/ccpi/optimisation/operators/__init__.py deleted file mode 100644 index 23222d4..0000000 --- a/Wrappers/Python/build/lib/ccpi/optimisation/operators/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Mar 5 15:56:27 2019 - -@author: ofn77899 -""" - -from .Operator import Operator -from .LinearOperator import LinearOperator -from .ScaledOperator import ScaledOperator -from .BlockOperator import BlockOperator -from .BlockScaledOperator import BlockScaledOperator - -from .SparseFiniteDiff import SparseFiniteDiff -from .ShrinkageOperator import ShrinkageOperator - -from .FiniteDifferenceOperator import FiniteDiff -from .GradientOperator import Gradient -from .SymmetrizedGradientOperator import SymmetrizedGradient -from .IdentityOperator import Identity -from .ZeroOperator import ZeroOperator -from .LinearOperatorMatrix import LinearOperatorMatrix - diff --git a/Wrappers/Python/build/lib/ccpi/processors/CenterOfRotationFinder.py b/Wrappers/Python/build/lib/ccpi/processors/CenterOfRotationFinder.py deleted file mode 100644 index 936dc05..0000000 --- a/Wrappers/Python/build/lib/ccpi/processors/CenterOfRotationFinder.py +++ /dev/null @@ -1,408 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License - -from ccpi.framework import DataProcessor, DataContainer, AcquisitionData,\ - AcquisitionGeometry, ImageGeometry, ImageData -import numpy -from scipy import ndimage - -class CenterOfRotationFinder(DataProcessor): - '''Processor to find the center of rotation in a parallel beam experiment - - This processor read in a AcquisitionDataSet and finds the center of rotation - based on Nghia Vo's method. https://doi.org/10.1364/OE.22.019078 - - Input: AcquisitionDataSet - - Output: float. center of rotation in pixel coordinate - ''' - - def __init__(self): - kwargs = { - - } - - #DataProcessor.__init__(self, **kwargs) - super(CenterOfRotationFinder, self).__init__(**kwargs) - - def check_input(self, dataset): - if dataset.number_of_dimensions == 3: - if dataset.geometry.geom_type == 'parallel': - return True - else: - raise ValueError('{0} is suitable only for parallel beam geometry'\ - .format(self.__class__.__name__)) - else: - raise ValueError("Expected input dimensions is 3, got {0}"\ - .format(dataset.number_of_dimensions)) - - - # ######################################################################### - # Copyright (c) 2015, UChicago Argonne, LLC. All rights reserved. # - # # - # Copyright 2015. UChicago Argonne, LLC. This software was produced # - # under U.S. Government contract DE-AC02-06CH11357 for Argonne National # - # Laboratory (ANL), which is operated by UChicago Argonne, LLC for the # - # U.S. Department of Energy. The U.S. Government has rights to use, # - # reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR # - # UChicago Argonne, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR # - # ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is # - # modified to produce derivative works, such modified software should # - # be clearly marked, so as not to confuse it with the version available # - # from ANL. # - # # - # Additionally, redistribution and use in source and binary forms, with # - # or without modification, are permitted provided that the following # - # conditions are met: # - # # - # * Redistributions of source code must retain the above copyright # - # notice, this list of conditions and the following disclaimer. # - # # - # * Redistributions in binary form must reproduce the above copyright # - # notice, this list of conditions and the following disclaimer in # - # the documentation and/or other materials provided with the # - # distribution. # - # # - # * Neither the name of UChicago Argonne, LLC, Argonne National # - # Laboratory, ANL, the U.S. Government, nor the names of its # - # contributors may be used to endorse or promote products derived # - # from this software without specific prior written permission. # - # # - # THIS SOFTWARE IS PROVIDED BY UChicago Argonne, LLC AND CONTRIBUTORS # - # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # - # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # - # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UChicago # - # Argonne, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # - # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # - # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # - # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # - # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # - # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # - # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # - # POSSIBILITY OF SUCH DAMAGE. # - # ######################################################################### - - @staticmethod - def as_ndarray(arr, dtype=None, copy=False): - if not isinstance(arr, numpy.ndarray): - arr = numpy.array(arr, dtype=dtype, copy=copy) - return arr - - @staticmethod - def as_dtype(arr, dtype, copy=False): - if not arr.dtype == dtype: - arr = numpy.array(arr, dtype=dtype, copy=copy) - return arr - - @staticmethod - def as_float32(arr): - arr = CenterOfRotationFinder.as_ndarray(arr, numpy.float32) - return CenterOfRotationFinder.as_dtype(arr, numpy.float32) - - - - - @staticmethod - def find_center_vo(tomo, ind=None, smin=-40, smax=40, srad=10, step=0.5, - ratio=2., drop=20): - """ - Find rotation axis location using Nghia Vo's method. :cite:`Vo:14`. - - Parameters - ---------- - tomo : ndarray - 3D tomographic data. - ind : int, optional - Index of the slice to be used for reconstruction. - smin, smax : int, optional - Reference to the horizontal center of the sinogram. - srad : float, optional - Fine search radius. - step : float, optional - Step of fine searching. - ratio : float, optional - The ratio between the FOV of the camera and the size of object. - It's used to generate the mask. - drop : int, optional - Drop lines around vertical center of the mask. - - Returns - ------- - float - Rotation axis location. - - Notes - ----- - The function may not yield a correct estimate, if: - - - the sample size is bigger than the field of view of the camera. - In this case the ``ratio`` argument need to be set larger - than the default of 2.0. - - - there is distortion in the imaging hardware. If there's - no correction applied, the center of the projection image may - yield a better estimate. - - - the sample contrast is weak. Paganin's filter need to be applied - to overcome this. - - - the sample was changed during the scan. - """ - tomo = CenterOfRotationFinder.as_float32(tomo) - - if ind is None: - ind = tomo.shape[1] // 2 - _tomo = tomo[:, ind, :] - - - - # Reduce noise by smooth filters. Use different filters for coarse and fine search - _tomo_cs = ndimage.filters.gaussian_filter(_tomo, (3, 1)) - _tomo_fs = ndimage.filters.median_filter(_tomo, (2, 2)) - - # Coarse and fine searches for finding the rotation center. - if _tomo.shape[0] * _tomo.shape[1] > 4e6: # If data is large (>2kx2k) - #_tomo_coarse = downsample(numpy.expand_dims(_tomo_cs,1), level=2)[:, 0, :] - #init_cen = _search_coarse(_tomo_coarse, smin, smax, ratio, drop) - #fine_cen = _search_fine(_tomo_fs, srad, step, init_cen*4, ratio, drop) - init_cen = CenterOfRotationFinder._search_coarse(_tomo_cs, smin, - smax, ratio, drop) - fine_cen = CenterOfRotationFinder._search_fine(_tomo_fs, srad, - step, init_cen, - ratio, drop) - else: - init_cen = CenterOfRotationFinder._search_coarse(_tomo_cs, - smin, smax, - ratio, drop) - fine_cen = CenterOfRotationFinder._search_fine(_tomo_fs, srad, - step, init_cen, - ratio, drop) - - #logger.debug('Rotation center search finished: %i', fine_cen) - return fine_cen - - - @staticmethod - def _search_coarse(sino, smin, smax, ratio, drop): - """ - Coarse search for finding the rotation center. - """ - (Nrow, Ncol) = sino.shape - centerfliplr = (Ncol - 1.0) / 2.0 - - # Copy the sinogram and flip left right, the purpose is to - # make a full [0;2Pi] sinogram - _copy_sino = numpy.fliplr(sino[1:]) - - # This image is used for compensating the shift of sinogram 2 - temp_img = numpy.zeros((Nrow - 1, Ncol), dtype='float32') - temp_img[:] = sino[-1] - - # Start coarse search in which the shift step is 1 - listshift = numpy.arange(smin, smax + 1) - listmetric = numpy.zeros(len(listshift), dtype='float32') - mask = CenterOfRotationFinder._create_mask(2 * Nrow - 1, Ncol, - 0.5 * ratio * Ncol, drop) - for i in listshift: - _sino = numpy.roll(_copy_sino, i, axis=1) - if i >= 0: - _sino[:, 0:i] = temp_img[:, 0:i] - else: - _sino[:, i:] = temp_img[:, i:] - listmetric[i - smin] = numpy.sum(numpy.abs(numpy.fft.fftshift( - #pyfftw.interfaces.numpy_fft.fft2( - # numpy.vstack((sino, _sino))) - numpy.fft.fft2(numpy.vstack((sino, _sino))) - )) * mask) - minpos = numpy.argmin(listmetric) - return centerfliplr + listshift[minpos] / 2.0 - - @staticmethod - def _search_fine(sino, srad, step, init_cen, ratio, drop): - """ - Fine search for finding the rotation center. - """ - Nrow, Ncol = sino.shape - centerfliplr = (Ncol + 1.0) / 2.0 - 1.0 - # Use to shift the sinogram 2 to the raw CoR. - shiftsino = numpy.int16(2 * (init_cen - centerfliplr)) - _copy_sino = numpy.roll(numpy.fliplr(sino[1:]), shiftsino, axis=1) - if init_cen <= centerfliplr: - lefttake = numpy.int16(numpy.ceil(srad + 1)) - righttake = numpy.int16(numpy.floor(2 * init_cen - srad - 1)) - else: - lefttake = numpy.int16(numpy.ceil( - init_cen - (Ncol - 1 - init_cen) + srad + 1)) - righttake = numpy.int16(numpy.floor(Ncol - 1 - srad - 1)) - Ncol1 = righttake - lefttake + 1 - mask = CenterOfRotationFinder._create_mask(2 * Nrow - 1, Ncol1, - 0.5 * ratio * Ncol, drop) - numshift = numpy.int16((2 * srad) / step) + 1 - listshift = numpy.linspace(-srad, srad, num=numshift) - listmetric = numpy.zeros(len(listshift), dtype='float32') - factor1 = numpy.mean(sino[-1, lefttake:righttake]) - num1 = 0 - for i in listshift: - _sino = ndimage.interpolation.shift( - _copy_sino, (0, i), prefilter=False) - factor2 = numpy.mean(_sino[0,lefttake:righttake]) - _sino = _sino * factor1 / factor2 - sinojoin = numpy.vstack((sino, _sino)) - listmetric[num1] = numpy.sum(numpy.abs(numpy.fft.fftshift( - #pyfftw.interfaces.numpy_fft.fft2( - # sinojoin[:, lefttake:righttake + 1]) - numpy.fft.fft2(sinojoin[:, lefttake:righttake + 1]) - )) * mask) - num1 = num1 + 1 - minpos = numpy.argmin(listmetric) - return init_cen + listshift[minpos] / 2.0 - - @staticmethod - def _create_mask(nrow, ncol, radius, drop): - du = 1.0 / ncol - dv = (nrow - 1.0) / (nrow * 2.0 * numpy.pi) - centerrow = numpy.ceil(nrow / 2) - 1 - centercol = numpy.ceil(ncol / 2) - 1 - # added by Edoardo Pasca - centerrow = int(centerrow) - centercol = int(centercol) - mask = numpy.zeros((nrow, ncol), dtype='float32') - for i in range(nrow): - num1 = numpy.round(((i - centerrow) * dv / radius) / du) - (p1, p2) = numpy.int16(numpy.clip(numpy.sort( - (-num1 + centercol, num1 + centercol)), 0, ncol - 1)) - mask[i, p1:p2 + 1] = numpy.ones(p2 - p1 + 1, dtype='float32') - if drop < centerrow: - mask[centerrow - drop:centerrow + drop + 1, - :] = numpy.zeros((2 * drop + 1, ncol), dtype='float32') - mask[:,centercol-1:centercol+2] = numpy.zeros((nrow, 3), dtype='float32') - return mask - - def process(self, out=None): - - projections = self.get_input() - - cor = CenterOfRotationFinder.find_center_vo(projections.as_array()) - - return cor - - -class AcquisitionDataPadder(DataProcessor): - '''Normalization based on flat and dark - - This processor read in a AcquisitionData and normalises it based on - the instrument reading with and without incident photons or neutrons. - - Input: AcquisitionData - Parameter: 2D projection with flat field (or stack) - 2D projection with dark field (or stack) - Output: AcquisitionDataSetn - ''' - - def __init__(self, - center_of_rotation = None, - acquisition_geometry = None, - pad_value = 1e-5): - kwargs = { - 'acquisition_geometry' : acquisition_geometry, - 'center_of_rotation' : center_of_rotation, - 'pad_value' : pad_value - } - - super(AcquisitionDataPadder, self).__init__(**kwargs) - - def check_input(self, dataset): - if self.acquisition_geometry is None: - self.acquisition_geometry = dataset.geometry - if dataset.number_of_dimensions == 3: - return True - else: - raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ - .format(dataset.number_of_dimensions)) - - def process(self, out=None): - projections = self.get_input() - w = projections.get_dimension_size('horizontal') - delta = w - 2 * self.center_of_rotation - - padded_width = int ( - numpy.ceil(abs(delta)) + w - ) - delta_pix = padded_width - w - - voxel_per_pixel = 1 - geom = pbalg.pb_setup_geometry_from_acquisition(projections.as_array(), - self.acquisition_geometry.angles, - self.center_of_rotation, - voxel_per_pixel ) - - padded_geometry = self.acquisition_geometry.clone() - - padded_geometry.pixel_num_h = geom['n_h'] - padded_geometry.pixel_num_v = geom['n_v'] - - delta_pix_h = padded_geometry.pixel_num_h - self.acquisition_geometry.pixel_num_h - delta_pix_v = padded_geometry.pixel_num_v - self.acquisition_geometry.pixel_num_v - - if delta_pix_h == 0: - delta_pix_h = delta_pix - padded_geometry.pixel_num_h = padded_width - #initialize a new AcquisitionData with values close to 0 - out = AcquisitionData(geometry=padded_geometry) - out = out + self.pad_value - - - #pad in the horizontal-vertical plane -> slice on angles - if delta > 0: - #pad left of middle - command = "out.array[" - for i in range(out.number_of_dimensions): - if out.dimension_labels[i] == 'horizontal': - value = '{0}:{1}'.format(delta_pix_h, delta_pix_h+w) - command = command + str(value) - else: - if out.dimension_labels[i] == 'vertical' : - value = '{0}:'.format(delta_pix_v) - command = command + str(value) - else: - command = command + ":" - if i < out.number_of_dimensions -1: - command = command + ',' - command = command + '] = projections.array' - #print (command) - else: - #pad right of middle - command = "out.array[" - for i in range(out.number_of_dimensions): - if out.dimension_labels[i] == 'horizontal': - value = '{0}:{1}'.format(0, w) - command = command + str(value) - else: - if out.dimension_labels[i] == 'vertical' : - value = '{0}:'.format(delta_pix_v) - command = command + str(value) - else: - command = command + ":" - if i < out.number_of_dimensions -1: - command = command + ',' - command = command + '] = projections.array' - #print (command) - #cleaned = eval(command) - exec(command) - return out \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/processors/Normalizer.py b/Wrappers/Python/build/lib/ccpi/processors/Normalizer.py deleted file mode 100644 index da65e5c..0000000 --- a/Wrappers/Python/build/lib/ccpi/processors/Normalizer.py +++ /dev/null @@ -1,124 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License - -from ccpi.framework import DataProcessor, DataContainer, AcquisitionData,\ - AcquisitionGeometry, ImageGeometry, ImageData -import numpy - -class Normalizer(DataProcessor): - '''Normalization based on flat and dark - - This processor read in a AcquisitionData and normalises it based on - the instrument reading with and without incident photons or neutrons. - - Input: AcquisitionData - Parameter: 2D projection with flat field (or stack) - 2D projection with dark field (or stack) - Output: AcquisitionDataSetn - ''' - - def __init__(self, flat_field = None, dark_field = None, tolerance = 1e-5): - kwargs = { - 'flat_field' : flat_field, - 'dark_field' : dark_field, - # very small number. Used when there is a division by zero - 'tolerance' : tolerance - } - - #DataProcessor.__init__(self, **kwargs) - super(Normalizer, self).__init__(**kwargs) - if not flat_field is None: - self.set_flat_field(flat_field) - if not dark_field is None: - self.set_dark_field(dark_field) - - def check_input(self, dataset): - if dataset.number_of_dimensions == 3 or\ - dataset.number_of_dimensions == 2: - return True - else: - raise ValueError("Expected input dimensions is 2 or 3, got {0}"\ - .format(dataset.number_of_dimensions)) - - def set_dark_field(self, df): - if type(df) is numpy.ndarray: - if len(numpy.shape(df)) == 3: - raise ValueError('Dark Field should be 2D') - elif len(numpy.shape(df)) == 2: - self.dark_field = df - elif issubclass(type(df), DataContainer): - self.dark_field = self.set_dark_field(df.as_array()) - - def set_flat_field(self, df): - if type(df) is numpy.ndarray: - if len(numpy.shape(df)) == 3: - raise ValueError('Flat Field should be 2D') - elif len(numpy.shape(df)) == 2: - self.flat_field = df - elif issubclass(type(df), DataContainer): - self.flat_field = self.set_flat_field(df.as_array()) - - @staticmethod - def normalize_projection(projection, flat, dark, tolerance): - a = (projection - dark) - b = (flat-dark) - with numpy.errstate(divide='ignore', invalid='ignore'): - c = numpy.true_divide( a, b ) - c[ ~ numpy.isfinite( c )] = tolerance # set to not zero if 0/0 - return c - - @staticmethod - def estimate_normalised_error(projection, flat, dark, delta_flat, delta_dark): - '''returns the estimated relative error of the normalised projection - - n = (projection - dark) / (flat - dark) - Dn/n = (flat-dark + projection-dark)/((flat-dark)*(projection-dark))*(Df/f + Dd/d) - ''' - a = (projection - dark) - b = (flat-dark) - df = delta_flat / flat - dd = delta_dark / dark - rel_norm_error = (b + a) / (b * a) * (df + dd) - return rel_norm_error - - def process(self, out=None): - - projections = self.get_input() - dark = self.dark_field - flat = self.flat_field - - if projections.number_of_dimensions == 3: - if not (projections.shape[1:] == dark.shape and \ - projections.shape[1:] == flat.shape): - raise ValueError('Flats/Dark and projections size do not match.') - - - a = numpy.asarray( - [ Normalizer.normalize_projection( - projection, flat, dark, self.tolerance) \ - for projection in projections.as_array() ] - ) - elif projections.number_of_dimensions == 2: - a = Normalizer.normalize_projection(projections.as_array(), - flat, dark, self.tolerance) - y = type(projections)( a , True, - dimension_labels=projections.dimension_labels, - geometry=projections.geometry) - return y - \ No newline at end of file diff --git a/Wrappers/Python/build/lib/ccpi/processors/__init__.py b/Wrappers/Python/build/lib/ccpi/processors/__init__.py deleted file mode 100644 index f8d050e..0000000 --- a/Wrappers/Python/build/lib/ccpi/processors/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Apr 30 13:51:09 2019 - -@author: ofn77899 -""" - -from .CenterOfRotationFinder import CenterOfRotationFinder -from .Normalizer import Normalizer diff --git a/Wrappers/Python/ccpi/optimisation/__pycache__/__init__.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index c7dd009..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockOperator.cpython-36.pyc deleted file mode 100644 index b4da27c..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockOperator.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockScaledOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockScaledOperator.cpython-36.pyc deleted file mode 100644 index 32b7f28..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/BlockScaledOperator.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/FiniteDifferenceOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/FiniteDifferenceOperator.cpython-36.pyc deleted file mode 100644 index 8484ac0..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/FiniteDifferenceOperator.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/GradientOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/GradientOperator.cpython-36.pyc deleted file mode 100644 index 727b35a..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/GradientOperator.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/IdentityOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/IdentityOperator.cpython-36.pyc deleted file mode 100644 index 95297d5..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/IdentityOperator.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperator.cpython-36.pyc deleted file mode 100644 index 83bc87b..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperator.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperatorMatrix.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperatorMatrix.cpython-36.pyc deleted file mode 100644 index 16c7bdc..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/LinearOperatorMatrix.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/Operator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/Operator.cpython-36.pyc deleted file mode 100644 index 3d1ae4e..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/Operator.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ScaledOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ScaledOperator.cpython-36.pyc deleted file mode 100644 index 980891e..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ScaledOperator.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ShrinkageOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ShrinkageOperator.cpython-36.pyc deleted file mode 100644 index 3087afc..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ShrinkageOperator.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/SparseFiniteDiff.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/SparseFiniteDiff.cpython-36.pyc deleted file mode 100644 index 93f4f2d..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/SparseFiniteDiff.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/SymmetrizedGradientOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/SymmetrizedGradientOperator.cpython-36.pyc deleted file mode 100644 index ff62cae..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/SymmetrizedGradientOperator.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ZeroOperator.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ZeroOperator.cpython-36.pyc deleted file mode 100644 index 21a2a3a..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/ZeroOperator.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/__init__.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/operators/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index c5db258..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/operators/__pycache__/__init__.cpython-36.pyc and /dev/null differ -- cgit v1.2.3 From a705b74b0109b7c2f3412954a39c913dc255003f Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Wed, 8 May 2019 14:05:56 +0100 Subject: removed pycache and DS_Store files --- Wrappers/Python/.DS_Store | Bin 6148 -> 0 bytes .../algorithms/__pycache__/Algorithm.cpython-36.pyc | Bin 6306 -> 0 bytes .../algorithms/__pycache__/CGLS.cpython-36.pyc | Bin 1800 -> 0 bytes .../algorithms/__pycache__/FBPD.cpython-36.pyc | Bin 1698 -> 0 bytes .../algorithms/__pycache__/FISTA.cpython-36.pyc | Bin 2808 -> 0 bytes .../__pycache__/GradientDescent.cpython-36.pyc | Bin 2193 -> 0 bytes .../algorithms/__pycache__/PDHG.cpython-36.pyc | Bin 3820 -> 0 bytes .../algorithms/__pycache__/SIRT.cpython-36.pyc | Bin 2009 -> 0 bytes .../algorithms/__pycache__/__init__.cpython-36.pyc | Bin 514 -> 0 bytes .../functions/__pycache__/BlockFunction.cpython-36.pyc | Bin 4382 -> 0 bytes .../functions/__pycache__/Function.cpython-36.pyc | Bin 2591 -> 0 bytes .../FunctionOperatorComposition.cpython-36.pyc | Bin 1982 -> 0 bytes .../functions/__pycache__/IndicatorBox.cpython-36.pyc | Bin 1813 -> 0 bytes .../__pycache__/KullbackLeibler.cpython-36.pyc | Bin 3347 -> 0 bytes .../functions/__pycache__/L1Norm.cpython-36.pyc | Bin 3138 -> 0 bytes .../functions/__pycache__/L2NormSquared.cpython-36.pyc | Bin 5327 -> 0 bytes .../functions/__pycache__/MixedL21Norm.cpython-36.pyc | Bin 3888 -> 0 bytes .../functions/__pycache__/Norm2Sq.cpython-36.pyc | Bin 2013 -> 0 bytes .../functions/__pycache__/ScaledFunction.cpython-36.pyc | Bin 3993 -> 0 bytes .../functions/__pycache__/ZeroFunction.cpython-36.pyc | Bin 1547 -> 0 bytes .../functions/__pycache__/__init__.cpython-36.pyc | Bin 604 -> 0 bytes Wrappers/Python/wip/.DS_Store | Bin 12292 -> 0 bytes 22 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Wrappers/Python/.DS_Store delete mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/Algorithm.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/CGLS.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FBPD.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FISTA.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/GradientDescent.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/PDHG.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/SIRT.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/__init__.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/BlockFunction.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/Function.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/FunctionOperatorComposition.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/IndicatorBox.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/KullbackLeibler.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/L1Norm.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/L2NormSquared.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/MixedL21Norm.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/Norm2Sq.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/ScaledFunction.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/ZeroFunction.cpython-36.pyc delete mode 100644 Wrappers/Python/ccpi/optimisation/functions/__pycache__/__init__.cpython-36.pyc delete mode 100644 Wrappers/Python/wip/.DS_Store diff --git a/Wrappers/Python/.DS_Store b/Wrappers/Python/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/Wrappers/Python/.DS_Store and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/Algorithm.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/Algorithm.cpython-36.pyc deleted file mode 100644 index 3a68266..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/Algorithm.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/CGLS.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/CGLS.cpython-36.pyc deleted file mode 100644 index 1b83229..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/CGLS.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FBPD.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FBPD.cpython-36.pyc deleted file mode 100644 index 1bb0153..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FBPD.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FISTA.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FISTA.cpython-36.pyc deleted file mode 100644 index 15a3317..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/FISTA.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/GradientDescent.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/GradientDescent.cpython-36.pyc deleted file mode 100644 index 46d6d4a..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/GradientDescent.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/PDHG.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/PDHG.cpython-36.pyc deleted file mode 100644 index 45abb22..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/PDHG.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/SIRT.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/SIRT.cpython-36.pyc deleted file mode 100644 index 96b3e83..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/SIRT.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/__init__.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index a713e47..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/algorithms/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/BlockFunction.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/BlockFunction.cpython-36.pyc deleted file mode 100644 index 941a262..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/BlockFunction.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/Function.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/Function.cpython-36.pyc deleted file mode 100644 index 5f52020..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/Function.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/FunctionOperatorComposition.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/FunctionOperatorComposition.cpython-36.pyc deleted file mode 100644 index dd14b60..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/FunctionOperatorComposition.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/IndicatorBox.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/IndicatorBox.cpython-36.pyc deleted file mode 100644 index 5b12cc6..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/IndicatorBox.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/KullbackLeibler.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/KullbackLeibler.cpython-36.pyc deleted file mode 100644 index b6d85de..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/KullbackLeibler.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/L1Norm.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/L1Norm.cpython-36.pyc deleted file mode 100644 index 05b5010..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/L1Norm.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/L2NormSquared.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/L2NormSquared.cpython-36.pyc deleted file mode 100644 index 61a4e29..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/L2NormSquared.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/MixedL21Norm.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/MixedL21Norm.cpython-36.pyc deleted file mode 100644 index 623f7d7..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/MixedL21Norm.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/Norm2Sq.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/Norm2Sq.cpython-36.pyc deleted file mode 100644 index 8d4779a..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/Norm2Sq.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/ScaledFunction.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/ScaledFunction.cpython-36.pyc deleted file mode 100644 index ba77722..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/ScaledFunction.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/ZeroFunction.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/ZeroFunction.cpython-36.pyc deleted file mode 100644 index 93b93ed..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/ZeroFunction.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/__init__.cpython-36.pyc b/Wrappers/Python/ccpi/optimisation/functions/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index f0d07a4..0000000 Binary files a/Wrappers/Python/ccpi/optimisation/functions/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/Wrappers/Python/wip/.DS_Store b/Wrappers/Python/wip/.DS_Store deleted file mode 100644 index a9a83e2..0000000 Binary files a/Wrappers/Python/wip/.DS_Store and /dev/null differ -- cgit v1.2.3 From 6d55ee76936a20af5b71c067e8f3ff6f9441d15e Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 9 May 2019 10:55:47 +0100 Subject: fix proximal conj --- .../ccpi/optimisation/functions/KullbackLeibler.py | 26 +++++++++++----- .../Python/demos/PDHG_TV_Denoising_Gaussian.py | 35 +++++++++------------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index cf1a244..3096191 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -106,17 +106,27 @@ class KullbackLeibler(Function): z = x + tau * self.bnoise return 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) else: - - z_m = x + tau * self.bnoise -1 - self.b.multiply(4*tau, out=out) - z_m.multiply(z_m, out=z_m) - out += z_m - + + tmp = x + tau * self.bnoise + self.b.multiply(4*tau, out=out) + out.add((tmp-1)**2, out=out) out.sqrt(out=out) - out *= -1 - out += tmp2 + out.add(tmp+1, out=out) out *= 0.5 + + + +# z_m = x + tau * self.bnoise -1 +# self.b.multiply(4*tau, out=out) +# z_m.multiply(z_m, out=z_m) +# out += z_m +# +# out.sqrt(out=out) +# +# out *= -1 +# out += tmp2 +# out *= 0.5 diff --git a/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian.py index c830025..afdb6a2 100644 --- a/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian.py +++ b/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian.py @@ -18,14 +18,10 @@ # limitations under the License. # #========================================================================= - - """ Total Variation Denoising using PDHG algorithm: - min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) - Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} @@ -35,12 +31,12 @@ Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2} g: Noisy Data with Gaussian Noise - Method = 0: K = [ \nabla, - Identity] + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + - Method = 1: K = \nabla - - + Method = 1 (PDHG - explicit ): K = \nabla + """ from ccpi.framework import ImageData, ImageGeometry @@ -54,20 +50,17 @@ from ccpi.optimisation.algorithms import PDHG from ccpi.optimisation.operators import BlockOperator, Identity, Gradient from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ MixedL21Norm, BlockFunction - - -from ccpi.data import camera - - -# Load Data -data = camera(size=(256,256)) - -N, M = data.shape - -# Image and Acquitition Geometries + +# Load Data +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) ag = ig - + # Create Noisy data. Add Gaussian noise np.random.seed(10) noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.1, size=ig.shape) ) -- cgit v1.2.3 From dce9efae0f01374a0f1c71ea03baab3bb1b0947d Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 9 May 2019 11:44:12 +0100 Subject: fix PDHG denoising examples --- Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py | 96 ++++---- .../PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py | 245 +++++++++++++++++++++ .../PDHG_examples/PDHG_TV_Denoising_Gaussian.py | 204 +++++++++++++++++ .../PDHG_examples/PDHG_TV_Denoising_Poisson.py | 213 ++++++++++++++++++ .../PDHG_examples/PDHG_TV_Denoising_SaltPepper.py | 213 ++++++++++++++++++ .../demos/PDHG_examples/PDHG_Tikhonov_Denoising.py | 210 ++++++++++++++++++ 6 files changed, 1136 insertions(+), 45 deletions(-) create mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py create mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py create mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Poisson.py create mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_SaltPepper.py create mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Denoising.py diff --git a/Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py index 70f6b9b..0db8c29 100644 --- a/Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py +++ b/Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py @@ -1,41 +1,43 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 STFC, University of Manchester - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= """ Total Variation Denoising using PDHG algorithm: - min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) - - Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + \int x - g * log(x) - \nabla: Gradient operator - g: Noisy Data with Poisson Noise \alpha: Regularization parameter - Method = 0: K = [ \nabla, - Identity] - - Method = 1: K = \nabla + \nabla: Gradient operator + + g: Noisy Data with Poisson Noise + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + """ from ccpi.framework import ImageData, ImageGeometry @@ -66,10 +68,25 @@ ag = ig n1 = random_noise(data.as_array(), mode = 'poisson', seed = 10) noisy_data = ImageData(n1) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(15,15)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +#%% + # Regularisation Parameter alpha = 2 -method = '1' +method = '0' if method == '0': @@ -99,26 +116,15 @@ else: # Compute operator Norm normK = operator.norm() -# Primal & dual stepsizes +# Primal & Dual stepsizes sigma = 1 tau = 1/(sigma*normK**2) -opt = {'niter':2000, 'memopt': True} - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 - -def pdgap_objectives(niter, objective, solution): - - - print( "{:04}/{:04} {:<5} {:.4f} {:<5} {:.4f} {:<5} {:.4f}".\ - format(niter, pdhg.max_iteration,'', \ - objective[0],'',\ - objective[1],'',\ - objective[2])) -pdhg.run(2000, callback = pdgap_objectives) +# Setup and Run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 3000 +pdhg.update_objective_interval = 200 +pdhg.run(3000, verbose=False) plt.figure(figsize=(15,15)) diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py new file mode 100644 index 0000000..57f6fcd --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py @@ -0,0 +1,245 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= +""" + +Total Generalised Variation (TGV) Denoising using PDHG algorithm: + + +Problem: min_{x} \alpha * ||\nabla x - w||_{2,1} + + \beta * || E w ||_{2,1} + + \frac{1}{2} * || x - g ||_{2}^{2} + + \alpha: Regularization parameter + \alpha: Regularization parameter + + \nabla: Gradient operator + E: Symmetrized Gradient operator + + g: Noisy Data with Salt & Pepper Noise + + Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity + ZeroOperator, E + Identity, ZeroOperator] + + + Method = 1 (PDHG - explicit ): K = [ \nabla, - Identity + ZeroOperator, E ] + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, \ + Gradient, SymmetrizedGradient, ZeroOperator +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TGV SaltPepper denoising + +N = 100 + +data = np.zeros((N,N)) + +x1 = np.linspace(0, int(N/2), N) +x2 = np.linspace(int(N/2), 0., N) +xv, yv = np.meshgrid(x1, x2) + +xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T + +data = xv +data = ImageData(data/data.max()) + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Add Gaussian noise +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(15,15)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameters +alpha = 0.8 +beta = numpy.sqrt(2)* alpha + +method = '1' + +if method == '0': + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + op31 = Identity(ig, ag) + op32 = ZeroOperator(op22.domain_geometry(), ag) + + operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + f3 = L1Norm(b=noisy_data) + f = BlockFunction(f1, f2, f3) + g = ZeroFunction() + +else: + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + operator = BlockOperator(op11, -1*op12, op21, op22, shape=(2,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + + f = BlockFunction(f1, f2) + g = BlockFunction(L1Norm(b=noisy_data), ZeroFunction()) + +## Compute operator Norm +normK = operator.norm() +# +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000, verbose = False) + +#%% +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output()[0].as_array()) +plt.title('TGV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + +if cvx_not_installable: + + u = Variable(ig.shape) + w1 = Variable((N, N)) + w2 = Variable((N, N)) + + # create TGV regulariser + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ + DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ + beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) + + constraints = [] + fidelity = pnorm(u - noisy_data.as_array(),1) + solver = MOSEK + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output()[0].as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output()[0].as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py new file mode 100644 index 0000000..afdb6a2 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py @@ -0,0 +1,204 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= +""" + +Total Variation Denoising using PDHG algorithm: + + +Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Gaussian Noise + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +# Load Data +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create Noisy data. Add Gaussian noise +np.random.seed(10) +noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.1, size=ig.shape) ) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(15,15)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = 0.2 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + f1 = alpha * MixedL21Norm() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = 0.5 * L2NormSquared(b = noisy_data) + +# Compute Operator Norm +normK = operator.norm() + +# Primal & Dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + +# Setup and Run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 3000 +pdhg.update_objective_interval = 200 +pdhg.run(3000, verbose=False) + +# Show Results +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() + +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = MOSEK) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'Truth') + + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Poisson.py new file mode 100644 index 0000000..4d53635 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Poisson.py @@ -0,0 +1,213 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation Denoising using PDHG algorithm: + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + \int x - g * log(x) + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Poisson Noise + + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Poisson denoising +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Apply Poisson noise +n1 = random_noise(data.as_array(), mode = 'poisson', seed = 10) +noisy_data = ImageData(n1) + + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(15,15)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +#%% + +# Regularisation Parameter +alpha = 2 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = KullbackLeibler(noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = KullbackLeibler(noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & Dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + +# Setup and Run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 3000 +pdhg.update_objective_interval = 200 +pdhg.run(3000, verbose=False) + + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u1 = Variable(ig.shape) + q = Variable() + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) + + fidelity = sum( u1 - multiply(noisy_data.as_array(), log(u1)) ) + constraints = [q>= fidelity, u1>=0] + + solver = ECOS + obj = Minimize( regulariser + q) + prob = Problem(obj, constraints) + result = prob.solve(verbose = True, solver = solver) + + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u1.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u1.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_SaltPepper.py new file mode 100644 index 0000000..c5709c3 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_SaltPepper.py @@ -0,0 +1,213 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation Denoising using PDHG algorithm: + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + ||x-g||_{1} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Salt & Pepper Noise + + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Salt & Pepper denoising +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Apply Salt & Pepper noise +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(15,15)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = 2 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + f1 = alpha * MixedL21Norm() + f2 = L1Norm(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = L1Norm(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +opt = {'niter':2000, 'memopt': True} + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = pnorm( u - noisy_data.as_array(),1) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Denoising.py b/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Denoising.py new file mode 100644 index 0000000..7b73c1a --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Denoising.py @@ -0,0 +1,210 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= +""" + +Tikhonov Denoising using PDHG algorithm: + + +Problem: min_{x} \alpha * ||\nabla x||_{2}^{2} + \frac{1}{2} * || x - g ||_{2}^{2} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Gaussian Noise + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Salt & Pepper denoising +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Apply Salt & Pepper noise +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(15,15)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = 4 + +method = '1' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * L2NormSquared() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * L2NormSquared() + g = 0.5 * L2NormSquared(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +opt = {'niter':2000, 'memopt': True} + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('Tikhonov Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + + regulariser = alpha * sum_squares(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + -- cgit v1.2.3 From d158b57e6bf7893288ef7bc0551b267c4b9f42f3 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 9 May 2019 11:49:06 +0100 Subject: move demos --- .../Python/demos/PDHG_TGV_Denoising_SaltPepper.py | 194 ------------------- .../Python/demos/PDHG_TV_Denoising_Gaussian.py | 204 -------------------- Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py | 213 --------------------- .../Python/demos/PDHG_TV_Denoising_SaltPepper.py | 198 ------------------- Wrappers/Python/demos/PDHG_Tikhonov_Denoising.py | 176 ----------------- 5 files changed, 985 deletions(-) delete mode 100644 Wrappers/Python/demos/PDHG_TGV_Denoising_SaltPepper.py delete mode 100644 Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian.py delete mode 100644 Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py delete mode 100644 Wrappers/Python/demos/PDHG_TV_Denoising_SaltPepper.py delete mode 100644 Wrappers/Python/demos/PDHG_Tikhonov_Denoising.py diff --git a/Wrappers/Python/demos/PDHG_TGV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_TGV_Denoising_SaltPepper.py deleted file mode 100644 index 7b65c31..0000000 --- a/Wrappers/Python/demos/PDHG_TGV_Denoising_SaltPepper.py +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 22 14:53:03 2019 - -@author: evangelos -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, \ - Gradient, SymmetrizedGradient, ZeroOperator -from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TGV SaltPepper denoising - -N = 100 - -data = np.zeros((N,N)) - -x1 = np.linspace(0, int(N/2), N) -x2 = np.linspace(int(N/2), 0., N) -xv, yv = np.meshgrid(x1, x2) - -xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T - -data = xv -data = ImageData(data/data.max()) - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Add Gaussian noise -n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) -noisy_data = ImageData(n1) - -# Regularisation Parameters -alpha = 0.8 -beta = numpy.sqrt(2)* alpha - -method = '1' - -if method == '0': - - # Create operators - op11 = Gradient(ig) - op12 = Identity(op11.range_geometry()) - - op22 = SymmetrizedGradient(op11.domain_geometry()) - op21 = ZeroOperator(ig, op22.range_geometry()) - - op31 = Identity(ig, ag) - op32 = ZeroOperator(op22.domain_geometry(), ag) - - operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) - - f1 = alpha * MixedL21Norm() - f2 = beta * MixedL21Norm() - f3 = L1Norm(b=noisy_data) - f = BlockFunction(f1, f2, f3) - g = ZeroFunction() - -else: - - # Create operators - op11 = Gradient(ig) - op12 = Identity(op11.range_geometry()) - op22 = SymmetrizedGradient(op11.domain_geometry()) - op21 = ZeroOperator(ig, op22.range_geometry()) - - operator = BlockOperator(op11, -1*op12, op21, op22, shape=(2,2) ) - - f1 = alpha * MixedL21Norm() - f2 = beta * MixedL21Norm() - - f = BlockFunction(f1, f2) - g = BlockFunction(L1Norm(b=noisy_data), ZeroFunction()) - -## Compute operator Norm -normK = operator.norm() -# -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - -#%% -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output()[0].as_array()) -plt.title('TGV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - -if cvx_not_installable: - - u = Variable(ig.shape) - w1 = Variable((N, N)) - w2 = Variable((N, N)) - - # create TGV regulariser - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ - DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ - beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ - 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ - 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) - - constraints = [] - fidelity = pnorm(u - noisy_data.as_array(),1) - solver = MOSEK - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output()[0].as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output()[0].as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - diff --git a/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian.py deleted file mode 100644 index afdb6a2..0000000 --- a/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian.py +++ /dev/null @@ -1,204 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= -""" - -Total Variation Denoising using PDHG algorithm: - - -Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Gaussian Noise - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity] - - - Method = 1 (PDHG - explicit ): K = \nabla - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -# Load Data -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create Noisy data. Add Gaussian noise -np.random.seed(10) -noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.1, size=ig.shape) ) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(15,15)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Regularisation Parameter -alpha = 0.2 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - f1 = alpha * MixedL21Norm() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = 0.5 * L2NormSquared(b = noisy_data) - -# Compute Operator Norm -normK = operator.norm() - -# Primal & Dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -# Setup and Run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 3000 -pdhg.update_objective_interval = 200 -pdhg.run(3000, verbose=False) - -# Show Results -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() - -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = MOSEK) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'Truth') - - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - diff --git a/Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py deleted file mode 100644 index 0db8c29..0000000 --- a/Wrappers/Python/demos/PDHG_TV_Denoising_Poisson.py +++ /dev/null @@ -1,213 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation Denoising using PDHG algorithm: - -Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + \int x - g * log(x) - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Poisson Noise - - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity] - - - Method = 1 (PDHG - explicit ): K = \nabla - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Poisson denoising -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Apply Poisson noise -n1 = random_noise(data.as_array(), mode = 'poisson', seed = 10) -noisy_data = ImageData(n1) - - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(15,15)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -#%% - -# Regularisation Parameter -alpha = 2 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * MixedL21Norm() - f2 = KullbackLeibler(noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = KullbackLeibler(noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & Dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -# Setup and Run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 3000 -pdhg.update_objective_interval = 200 -pdhg.run(3000, verbose=False) - - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u1 = Variable(ig.shape) - q = Variable() - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) - - fidelity = sum( u1 - multiply(noisy_data.as_array(), log(u1)) ) - constraints = [q>= fidelity, u1>=0] - - solver = ECOS - obj = Minimize( regulariser + q) - prob = Problem(obj, constraints) - result = prob.solve(verbose = True, solver = solver) - - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u1.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u1.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - diff --git a/Wrappers/Python/demos/PDHG_TV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_TV_Denoising_SaltPepper.py deleted file mode 100644 index f5d4ce4..0000000 --- a/Wrappers/Python/demos/PDHG_TV_Denoising_SaltPepper.py +++ /dev/null @@ -1,198 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" - -Total Variation Denoising using PDHG algorithm: - - min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + ||x-g||_{1} - - \nabla: Gradient operator - g: Noisy Data with Salt & Pepper Noise - \alpha: Regularization parameter - - Method = 0: K = [ \nabla, - Identity] - - Method = 1: K = \nabla - - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Salt & Pepper denoising -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Apply Salt & Pepper noise -n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) -noisy_data = ImageData(n1) - -# Regularisation Parameter -alpha = 2 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * MixedL21Norm() - f2 = L1Norm(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = L1Norm(b = noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -opt = {'niter':2000, 'memopt': True} - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -##%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = pnorm( u - noisy_data.as_array(),1) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - diff --git a/Wrappers/Python/demos/PDHG_Tikhonov_Denoising.py b/Wrappers/Python/demos/PDHG_Tikhonov_Denoising.py deleted file mode 100644 index 041d4ee..0000000 --- a/Wrappers/Python/demos/PDHG_Tikhonov_Denoising.py +++ /dev/null @@ -1,176 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Salt & Pepper denoising -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Apply Salt & Pepper noise -n1 = random_noise(data.as_array(), mode = 'gaussian', mean=0, var = 0.05, seed=10) -noisy_data = ImageData(n1) - -# Regularisation Parameter -alpha = 4 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * L2NormSquared() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * L2NormSquared() - g = 0.5 * L2NormSquared(b = noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -opt = {'niter':2000, 'memopt': True} - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('Tikhonov Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -##%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - - regulariser = alpha * sum_squares(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - -- cgit v1.2.3 From 7ad2cb13b7727bda42956585daf789257f606ae9 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 9 May 2019 11:51:15 +0100 Subject: updates --- Wrappers/Python/ccpi/data/__init__.py | 66 ----------------------- Wrappers/Python/ccpi/data/boat.tiff | Bin 262278 -> 0 bytes Wrappers/Python/ccpi/data/camera.png | Bin 114228 -> 0 bytes Wrappers/Python/ccpi/data/peppers.tiff | Bin 786572 -> 0 bytes Wrappers/Python/ccpi/data/test_show_data.py | 30 ----------- Wrappers/Python/ccpi/framework/TestData.py | 79 ++++++++++++++++++++++++++++ Wrappers/Python/ccpi/framework/__init__.py | 2 + Wrappers/Python/conda-recipe/meta.yaml | 1 + Wrappers/Python/data/boat.tiff | Bin 0 -> 262278 bytes Wrappers/Python/data/camera.png | Bin 0 -> 114228 bytes Wrappers/Python/data/peppers.tiff | Bin 0 -> 786572 bytes Wrappers/Python/data/test_show_data.py | 30 +++++++++++ Wrappers/Python/setup.py | 9 ++-- Wrappers/Python/test/test_functions.py | 2 +- 14 files changed, 119 insertions(+), 100 deletions(-) delete mode 100644 Wrappers/Python/ccpi/data/__init__.py delete mode 100644 Wrappers/Python/ccpi/data/boat.tiff delete mode 100644 Wrappers/Python/ccpi/data/camera.png delete mode 100644 Wrappers/Python/ccpi/data/peppers.tiff delete mode 100644 Wrappers/Python/ccpi/data/test_show_data.py create mode 100755 Wrappers/Python/ccpi/framework/TestData.py create mode 100644 Wrappers/Python/data/boat.tiff create mode 100644 Wrappers/Python/data/camera.png create mode 100644 Wrappers/Python/data/peppers.tiff create mode 100644 Wrappers/Python/data/test_show_data.py diff --git a/Wrappers/Python/ccpi/data/__init__.py b/Wrappers/Python/ccpi/data/__init__.py deleted file mode 100644 index 2884108..0000000 --- a/Wrappers/Python/ccpi/data/__init__.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from ccpi.framework import ImageData -import numpy -from PIL import Image -import os -import os.path - -data_dir = os.path.abspath(os.path.dirname(__file__)) - -def camera(**kwargs): - - tmp = Image.open(os.path.join(data_dir, 'camera.png')) - - size = kwargs.get('size',(512, 512)) - - data = numpy.array(tmp.resize(size)) - - data = data/data.max() - - return ImageData(data) - - -def boat(**kwargs): - - tmp = Image.open(os.path.join(data_dir, 'boat.tiff')) - - size = kwargs.get('size',(512, 512)) - - data = numpy.array(tmp.resize(size)) - - data = data/data.max() - - return ImageData(data) - - -def peppers(**kwargs): - - tmp = Image.open(os.path.join(data_dir, 'peppers.tiff')) - - size = kwargs.get('size',(512, 512)) - - data = numpy.array(tmp.resize(size)) - - data = data/data.max() - - return ImageData(data) - diff --git a/Wrappers/Python/ccpi/data/boat.tiff b/Wrappers/Python/ccpi/data/boat.tiff deleted file mode 100644 index fc1205a..0000000 Binary files a/Wrappers/Python/ccpi/data/boat.tiff and /dev/null differ diff --git a/Wrappers/Python/ccpi/data/camera.png b/Wrappers/Python/ccpi/data/camera.png deleted file mode 100644 index 49be869..0000000 Binary files a/Wrappers/Python/ccpi/data/camera.png and /dev/null differ diff --git a/Wrappers/Python/ccpi/data/peppers.tiff b/Wrappers/Python/ccpi/data/peppers.tiff deleted file mode 100644 index 8c956f8..0000000 Binary files a/Wrappers/Python/ccpi/data/peppers.tiff and /dev/null differ diff --git a/Wrappers/Python/ccpi/data/test_show_data.py b/Wrappers/Python/ccpi/data/test_show_data.py deleted file mode 100644 index 7325c27..0000000 --- a/Wrappers/Python/ccpi/data/test_show_data.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Tue May 7 20:43:48 2019 - -@author: evangelos -""" - -from ccpi.data import camera, boat, peppers -import matplotlib.pyplot as plt - - -d = camera(size=(256,256)) - -plt.imshow(d.as_array()) -plt.colorbar() -plt.show() - -d1 = boat(size=(256,256)) - -plt.imshow(d1.as_array()) -plt.colorbar() -plt.show() - - -d2 = peppers(size=(256,256)) - -plt.imshow(d2.as_array()) -plt.colorbar() -plt.show() \ No newline at end of file diff --git a/Wrappers/Python/ccpi/framework/TestData.py b/Wrappers/Python/ccpi/framework/TestData.py new file mode 100755 index 0000000..61ed4df --- /dev/null +++ b/Wrappers/Python/ccpi/framework/TestData.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +from ccpi.framework import ImageData +import numpy +from PIL import Image +import os +import os.path + +data_dir = os.path.abspath(os.path.join( + os.path.dirname(__file__), + '../data/') +) + +class TestData(object): + BOAT = 'boat.tiff' + CAMERA = 'camera.png' + PEPPERS = 'peppers.tiff' + + def __init__(self, **kwargs): + self.data_dir = kwargs.get('data_dir', data_dir) + + def load(self, which, size=(512,512), scale=(0,1), **kwargs): + if which not in [TestData.BOAT, TestData.CAMERA, TestData.PEPPERS]: + raise ValueError('Unknown TestData {}.'.format(which)) + tmp = Image.open(os.path.join(data_dir, which)) + + data = numpy.array(tmp.resize(size)) + + if scale is not None: + dmax = data.max() + dmin = data.min() + + data = data -dmin / (dmax - dmin) + + if scale != (0,1): + #data = (data-dmin)/(dmax-dmin) * (scale[1]-scale[0]) +scale[0]) + data *= (scale[1]-scale[0]) + data += scale[0] + + return ImageData(data) + + + def camera(**kwargs): + + tmp = Image.open(os.path.join(data_dir, 'camera.png')) + + size = kwargs.get('size',(512, 512)) + + data = numpy.array(tmp.resize(size)) + + data = data/data.max() + + return ImageData(data) + + + def boat(**kwargs): + + tmp = Image.open(os.path.join(data_dir, 'boat.tiff')) + + size = kwargs.get('size',(512, 512)) + + data = numpy.array(tmp.resize(size)) + + data = data/data.max() + + return ImageData(data) + + + def peppers(**kwargs): + + tmp = Image.open(os.path.join(data_dir, 'peppers.tiff')) + + size = kwargs.get('size',(512, 512)) + + data = numpy.array(tmp.resize(size)) + + data = data/data.max() + + return ImageData(data) + diff --git a/Wrappers/Python/ccpi/framework/__init__.py b/Wrappers/Python/ccpi/framework/__init__.py index 229edb5..8926897 100755 --- a/Wrappers/Python/ccpi/framework/__init__.py +++ b/Wrappers/Python/ccpi/framework/__init__.py @@ -24,3 +24,5 @@ from .framework import DataProcessor from .framework import AX, PixelByPixelDataProcessor, CastDataContainer from .BlockDataContainer import BlockDataContainer from .BlockGeometry import BlockGeometry + +from .TestData import TestData diff --git a/Wrappers/Python/conda-recipe/meta.yaml b/Wrappers/Python/conda-recipe/meta.yaml index 6564014..9d03220 100644 --- a/Wrappers/Python/conda-recipe/meta.yaml +++ b/Wrappers/Python/conda-recipe/meta.yaml @@ -35,6 +35,7 @@ requirements: - scipy - matplotlib - h5py + - pillow about: home: http://www.ccpi.ac.uk diff --git a/Wrappers/Python/data/boat.tiff b/Wrappers/Python/data/boat.tiff new file mode 100644 index 0000000..fc1205a Binary files /dev/null and b/Wrappers/Python/data/boat.tiff differ diff --git a/Wrappers/Python/data/camera.png b/Wrappers/Python/data/camera.png new file mode 100644 index 0000000..49be869 Binary files /dev/null and b/Wrappers/Python/data/camera.png differ diff --git a/Wrappers/Python/data/peppers.tiff b/Wrappers/Python/data/peppers.tiff new file mode 100644 index 0000000..8c956f8 Binary files /dev/null and b/Wrappers/Python/data/peppers.tiff differ diff --git a/Wrappers/Python/data/test_show_data.py b/Wrappers/Python/data/test_show_data.py new file mode 100644 index 0000000..7325c27 --- /dev/null +++ b/Wrappers/Python/data/test_show_data.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Tue May 7 20:43:48 2019 + +@author: evangelos +""" + +from ccpi.data import camera, boat, peppers +import matplotlib.pyplot as plt + + +d = camera(size=(256,256)) + +plt.imshow(d.as_array()) +plt.colorbar() +plt.show() + +d1 = boat(size=(256,256)) + +plt.imshow(d1.as_array()) +plt.colorbar() +plt.show() + + +d2 = peppers(size=(256,256)) + +plt.imshow(d2.as_array()) +plt.colorbar() +plt.show() \ No newline at end of file diff --git a/Wrappers/Python/setup.py b/Wrappers/Python/setup.py index bceea46..6c76eff 100644 --- a/Wrappers/Python/setup.py +++ b/Wrappers/Python/setup.py @@ -31,7 +31,7 @@ if cil_version == '': setup( name="ccpi-framework", version=cil_version, - packages=['ccpi' , 'ccpi.io', 'ccpi.data', + packages=['ccpi' , 'ccpi.io', 'ccpi.framework', 'ccpi.optimisation', 'ccpi.optimisation.operators', 'ccpi.optimisation.algorithms', @@ -39,6 +39,8 @@ setup( 'ccpi.processors', 'ccpi.contrib','ccpi.contrib.optimisation', 'ccpi.contrib.optimisation.algorithms'], + data_file = [('share/ccpi', ['data/boat.tiff', 'data/peppers.tiff', + 'data/camera.png'])], # Project uses reStructuredText, so ensure that the docutils get # installed or upgraded on the target machine @@ -53,8 +55,9 @@ setup( # zip_safe = False, # metadata for upload to PyPI - author="Edoardo Pasca", - author_email="edoardo.pasca@stfc.ac.uk", + author="CCPi developers", + maintainer="Edoardo Pasca", + maintainer_email="edoardo.pasca@stfc.ac.uk", description='CCPi Core Imaging Library - Python Framework Module', license="Apache v2.0", keywords="Python Framework", diff --git a/Wrappers/Python/test/test_functions.py b/Wrappers/Python/test/test_functions.py index af419c7..082548b 100644 --- a/Wrappers/Python/test/test_functions.py +++ b/Wrappers/Python/test/test_functions.py @@ -299,7 +299,7 @@ class TestFunction(unittest.TestCase): A = 0.5 * Identity(ig) old_chisq = Norm2sq(A, b, 1.0) - new_chisq = FunctionOperatorComposition(A, L2NormSquared(b=b)) + new_chisq = FunctionOperatorComposition(L2NormSquared(b=b),A) yold = old_chisq(u) ynew = new_chisq(u) -- cgit v1.2.3 From aef8bcccaad00a63ae51d052f93b9efb548e1b6a Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 9 May 2019 11:52:27 +0100 Subject: remove comments --- .../Python/ccpi/optimisation/functions/KullbackLeibler.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index 3096191..e298d92 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -114,20 +114,7 @@ class KullbackLeibler(Function): out *= -1 out.add(tmp+1, out=out) out *= 0.5 - - - -# z_m = x + tau * self.bnoise -1 -# self.b.multiply(4*tau, out=out) -# z_m.multiply(z_m, out=z_m) -# out += z_m -# -# out.sqrt(out=out) -# -# out *= -1 -# out += tmp2 -# out *= 0.5 - + def __rmul__(self, scalar): -- cgit v1.2.3 From b377d100af892052c86c21ebac3b3ab29918b080 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 9 May 2019 12:33:12 +0100 Subject: save files as data_files --- .../ccpi/optimisation/functions/KullbackLeibler.py | 27 +++++++++++++--------- Wrappers/Python/setup.py | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index 3096191..ceea5ce 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -107,26 +107,31 @@ class KullbackLeibler(Function): return 0.5*((z + 1) - ((z-1)**2 + 4 * tau * self.b).sqrt()) else: - tmp = x + tau * self.bnoise - self.b.multiply(4*tau, out=out) - out.add((tmp-1)**2, out=out) + #tmp = x + tau * self.bnoise + tmp = tau * self.bnoise + tmp += x + tmp -= 1 + + self.b.multiply(4*tau, out=out) + + out.add((tmp)**2, out=out) out.sqrt(out=out) out *= -1 - out.add(tmp+1, out=out) + tmp += 2 + out += tmp out *= 0.5 - - -# z_m = x + tau * self.bnoise -1 +# z_m = x + tau * self.bnoise - 1 # self.b.multiply(4*tau, out=out) # z_m.multiply(z_m, out=z_m) # out += z_m -# # out.sqrt(out=out) -# +# # z = z_m + 2 +# z_m.sqrt(out=z_m) +# z_m += 2 # out *= -1 -# out += tmp2 -# out *= 0.5 +# out += z_m + diff --git a/Wrappers/Python/setup.py b/Wrappers/Python/setup.py index 6c76eff..44da471 100644 --- a/Wrappers/Python/setup.py +++ b/Wrappers/Python/setup.py @@ -39,7 +39,7 @@ setup( 'ccpi.processors', 'ccpi.contrib','ccpi.contrib.optimisation', 'ccpi.contrib.optimisation.algorithms'], - data_file = [('share/ccpi', ['data/boat.tiff', 'data/peppers.tiff', + data_files = [('share/ccpi', ['data/boat.tiff', 'data/peppers.tiff', 'data/camera.png'])], # Project uses reStructuredText, so ensure that the docutils get -- cgit v1.2.3 From 6b7a0783a37d8c3309e7acf65be8644d1c6e0164 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 9 May 2019 12:41:24 +0100 Subject: set proper directory on creation --- Wrappers/Python/ccpi/framework/TestData.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/framework/TestData.py b/Wrappers/Python/ccpi/framework/TestData.py index 61ed4df..7e8362e 100755 --- a/Wrappers/Python/ccpi/framework/TestData.py +++ b/Wrappers/Python/ccpi/framework/TestData.py @@ -21,7 +21,7 @@ class TestData(object): def load(self, which, size=(512,512), scale=(0,1), **kwargs): if which not in [TestData.BOAT, TestData.CAMERA, TestData.PEPPERS]: raise ValueError('Unknown TestData {}.'.format(which)) - tmp = Image.open(os.path.join(data_dir, which)) + tmp = Image.open(os.path.join(self.data_dir, which)) data = numpy.array(tmp.resize(size)) -- cgit v1.2.3 From 2cd641cbf8a21c31fd861c122239c70d1d3f494d Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 9 May 2019 12:45:46 +0100 Subject: TV tomo2D demos --- Wrappers/Python/demos/PDHG_TV_Tomo2D_gaussian.py | 204 ++++++++++++++++++ Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py | 251 +++++++++++++++++++++++ 2 files changed, 455 insertions(+) create mode 100644 Wrappers/Python/demos/PDHG_TV_Tomo2D_gaussian.py create mode 100644 Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py diff --git a/Wrappers/Python/demos/PDHG_TV_Tomo2D_gaussian.py b/Wrappers/Python/demos/PDHG_TV_Tomo2D_gaussian.py new file mode 100644 index 0000000..dc473a8 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_TV_Tomo2D_gaussian.py @@ -0,0 +1,204 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation Denoising using PDHG algorithm: + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + \frac{1}{2}||Ax - g||^{2} + + \nabla: Gradient operator + + A: Projection Matrix + g: Noisy sinogram corrupted with Gaussian Noise + + \alpha: Regularization parameter + +""" + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple + + + +# Create phantom for TV 2D tomography +N = 200 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +n1 = np.random.normal(0, 3, size=ig.shape) +noisy_data = AcquisitionData(n1 + sin.as_array(), ag) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = 50 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = 0.5 * L2NormSquared(b=noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 10 +tau = 1/(sigma*normK**2) + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff +import astra +import numpy + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + +if cvx_not_installable: + + ##Construct problem + u = Variable(N*N) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + + # create matrix representation for Astra operator + vol_geom = astra.create_vol_geom(N, N) + proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + + proj_id = astra.create_projector('strip', proj_geom, vol_geom) + + matrix_id = astra.projector.matrix(proj_id) + + ProjMat = astra.matrix.get(matrix_id) + + tmp = noisy_data.as_array().ravel() + + fidelity = 0.5 * sum_squares(ProjMat * u - tmp) + + solver = MOSEK + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - np.reshape(u.value, (N,N) )) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(np.reshape(u.value, (N, N))) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N,N) )[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py b/Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py new file mode 100644 index 0000000..b6d7725 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py @@ -0,0 +1,251 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple + +""" + +Total Variation Denoising using PDHG algorithm: + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + int A x -g log(Ax + \eta) + + \nabla: Gradient operator + + A: Projection Matrix + g: Noisy sinogram corrupted with Poisson Noise + + \eta: Background Noise + \alpha: Regularization parameter + +""" + +# Create phantom for TV 2D tomography +N = 50 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +scale = 0.5 +n1 = scale * np.random.poisson(sin.as_array()/scale) +noisy_data = AcquisitionData(n1, ag) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + + +# Regularisation Parameter +alpha = 0.5 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 10 +tau = 1/(sigma*normK**2) + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff +import astra +import numpy + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + + ##Construct problem + u = Variable(N*N) + #q = Variable() + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + + # create matrix representation for Astra operator + + vol_geom = astra.create_vol_geom(N, N) + proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + + proj_id = astra.create_projector('strip', proj_geom, vol_geom) + + matrix_id = astra.projector.matrix(proj_id) + + ProjMat = astra.matrix.get(matrix_id) + + tmp = noisy_data.as_array().ravel('F') + +# fidelity = sum( ProjMat * u - tmp * log(ProjMat * u + 1e-6)) + #constraints = [q>= fidelity, u>=0] + constraints = [] + + fidelity = sum(kl_div(tmp, ProjMat * u + 1e-6)) +# fidelity = kl_div(cp.multiply(alpha, W), +# cp.multiply(alpha, W + cp.multiply(beta, P))) - \ +# cp.multiply(alpha, cp.multiply(beta, P)) + + + + solver = SCS + obj = Minimize( regulariser + fidelity) + prob = Problem(obj, constraints) + result = prob.solve(verbose = True, solver = solver) + + +###%% Check with CVX solution +# +#from ccpi.optimisation.operators import SparseFiniteDiff +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +# +#if cvx_not_installable: +# +# ##Construct problem +# u = Variable(ig.shape) +# +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# # Define Total Variation as a regulariser +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) +# fidelity = pnorm( u - noisy_data.as_array(),1) +# +# # choose solver +# if 'MOSEK' in installed_solvers(): +# solver = MOSEK +# else: +# solver = SCS +# +# obj = Minimize( regulariser + fidelity) +# prob = Problem(obj) +# result = prob.solve(verbose = True, solver = solver) +# +# + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(np.reshape(u.value, (N, N))) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file -- cgit v1.2.3 From 042d2dbd0eec13fe9d55197e9792e03ca788ab20 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 9 May 2019 13:13:45 +0100 Subject: add 3D denoising --- .../PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py | 178 +++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py new file mode 100644 index 0000000..dbf81e2 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py @@ -0,0 +1,178 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= +""" + +Total Variation (3D) Denoising using PDHG algorithm: + + +Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Gaussian Noise + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Gaussian denoising +import timeit +import os +from tomophantom import TomoP3D +import tomophantom + +print ("Building 3D phantom using TomoPhantom software") +tic=timeit.default_timer() +model = 13 # select a model number from the library +N = 64 # Define phantom dimensions using a scalar value (cubic phantom) +path = os.path.dirname(tomophantom.__file__) +path_library3D = os.path.join(path, "Phantom3DLibrary.dat") + +#This will generate a N x N x N phantom (3D) +phantom_tm = TomoP3D.Model(model, N, path_library3D) + +# Create noisy data. Add Gaussian noise +ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N, voxel_num_z=N) +ag = ig +n1 = random_noise(phantom_tm, mode = 'gaussian', mean=0, var = 0.001, seed=10) +noisy_data = ImageData(n1) + +sliceSel = int(0.5*N) +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.title('Axial View') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.title('Coronal View') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.title('Sagittal View') +plt.colorbar() +plt.show() + + +# Regularisation Parameter +alpha = 0.05 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = 0.5 * L2NormSquared(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000, verbose = True) + + +#%% +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) +fig.suptitle('TV Reconstruction',fontsize=20) + + +plt.subplot(2,3,1) +plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Axial View') + +plt.subplot(2,3,2) +plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Coronal View') + +plt.subplot(2,3,3) +plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.axis('off') +plt.title('Sagittal View') + + +plt.subplot(2,3,4) +plt.imshow(pdhg.get_output().as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.axis('off') +plt.subplot(2,3,5) +plt.imshow(pdhg.get_output().as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.axis('off') +plt.subplot(2,3,6) +plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.axis('off') +im = plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + -- cgit v1.2.3 From 5ee6940e8ab81161819cfd623c8647be8fa1a7af Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 9 May 2019 13:31:15 +0100 Subject: 2D tomo gaussian --- .../demos/PDHG_examples/PDHG_TV_Tomo2D_gaussian.py | 212 +++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_gaussian.py diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_gaussian.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_gaussian.py new file mode 100644 index 0000000..6acbfcc --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_gaussian.py @@ -0,0 +1,212 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation 2D Tomography Reconstruction using PDHG algorithm: + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + \frac{1}{2}||Ax - g||^{2} + + \nabla: Gradient operator + + A: Projection Matrix + g: Noisy sinogram corrupted with Gaussian Noise + + \alpha: Regularization parameter + +""" + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple + + + +# Create phantom for TV 2D tomography +N = 100 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) + +device = input('Available device: GPU==1 / CPU==0 ') + +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + +Aop = AstraProjectorSimple(ig, ag, dev) +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +n1 = np.random.normal(0, 3, size=ig.shape) +noisy_data = AcquisitionData(n1 + sin.as_array(), ag) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = 50 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = 0.5 * L2NormSquared(b=noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 10 +tau = 1/(sigma*normK**2) + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff +import astra +import numpy + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + +if cvx_not_installable: + + ##Construct problem + u = Variable(N*N) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + + # create matrix representation for Astra operator + vol_geom = astra.create_vol_geom(N, N) + proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + + proj_id = astra.create_projector('strip', proj_geom, vol_geom) + + matrix_id = astra.projector.matrix(proj_id) + + ProjMat = astra.matrix.get(matrix_id) + + tmp = noisy_data.as_array().ravel() + + fidelity = 0.5 * sum_squares(ProjMat * u - tmp) + + solver = MOSEK + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - np.reshape(u.value, (N,N) )) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(np.reshape(u.value, (N, N))) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N,N) )[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file -- cgit v1.2.3 From 76b441f8e1182eeff98cd56a903e67f3f46c8128 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 9 May 2019 13:33:30 +0100 Subject: delete/move demos --- .../Python/demos/PDHG_TV_Denoising_Gaussian_3D.py | 155 ------------- Wrappers/Python/demos/PDHG_TV_Tomo2D.py | 245 --------------------- Wrappers/Python/demos/PDHG_TV_Tomo2D_gaussian.py | 204 ----------------- 3 files changed, 604 deletions(-) delete mode 100644 Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian_3D.py delete mode 100644 Wrappers/Python/demos/PDHG_TV_Tomo2D.py delete mode 100644 Wrappers/Python/demos/PDHG_TV_Tomo2D_gaussian.py diff --git a/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian_3D.py b/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian_3D.py deleted file mode 100644 index c86ddc9..0000000 --- a/Wrappers/Python/demos/PDHG_TV_Denoising_Gaussian_3D.py +++ /dev/null @@ -1,155 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Gaussian denoising -import timeit -import os -from tomophantom import TomoP3D -import tomophantom - -print ("Building 3D phantom using TomoPhantom software") -tic=timeit.default_timer() -model = 13 # select a model number from the library -N = 64 # Define phantom dimensions using a scalar value (cubic phantom) -path = os.path.dirname(tomophantom.__file__) -path_library3D = os.path.join(path, "Phantom3DLibrary.dat") - -#This will generate a N x N x N phantom (3D) -phantom_tm = TomoP3D.Model(model, N, path_library3D) - -# Create noisy data. Add Gaussian noise -ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N, voxel_num_z=N) -ag = ig -n1 = random_noise(phantom_tm, mode = 'gaussian', mean=0, var = 0.001, seed=10) -noisy_data = ImageData(n1) - -sliceSel = int(0.5*N) -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) -plt.title('Axial View') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) -plt.title('Coronal View') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) -plt.title('Sagittal View') -plt.colorbar() -plt.show() - - -# Regularisation Parameter -alpha = 0.05 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * MixedL21Norm() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = 0.5 * L2NormSquared(b = noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000) - -fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) - -plt.subplot(2,3,1) -plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Axial View') - -plt.subplot(2,3,2) -plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Coronal View') - -plt.subplot(2,3,3) -plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) -plt.axis('off') -plt.title('Sagittal View') - - -plt.subplot(2,3,4) -plt.imshow(pdhg.get_output().as_array()[sliceSel,:,:],vmin=0, vmax=1) -plt.axis('off') -plt.subplot(2,3,5) -plt.imshow(pdhg.get_output().as_array()[:,sliceSel,:],vmin=0, vmax=1) -plt.axis('off') -plt.subplot(2,3,6) -plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) -plt.axis('off') -im = plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) - - -fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, - wspace=0.02, hspace=0.02) - -cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) -cbar = fig.colorbar(im, cax=cb_ax) - - -plt.show() - diff --git a/Wrappers/Python/demos/PDHG_TV_Tomo2D.py b/Wrappers/Python/demos/PDHG_TV_Tomo2D.py deleted file mode 100644 index 87d5328..0000000 --- a/Wrappers/Python/demos/PDHG_TV_Tomo2D.py +++ /dev/null @@ -1,245 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ - MixedL21Norm, BlockFunction - -from ccpi.astra.ops import AstraProjectorSimple - -""" - -Total Variation Denoising using PDHG algorithm: - - min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + int A x -g log(Ax + \eta) - - \nabla: Gradient operator - - A: Projection Matrix - g: Noisy sinogram corrupted with Poisson Noise - - \eta: Background Noise - \alpha: Regularization parameter - -""" - -# Create phantom for TV 2D tomography -N = 75 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -detectors = N -angles = np.linspace(0, np.pi, N, dtype=np.float32) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -# Create noisy data. Apply Poisson noise -scale = 2 -n1 = scale * np.random.poisson(sin.as_array()/scale) -noisy_data = AcquisitionData(n1, ag) - -# Regularisation Parameter -alpha = 5 - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Create BlockOperator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -# Create functions - -f1 = alpha * MixedL21Norm() -f2 = KullbackLeibler(noisy_data) -f = BlockFunction(f1, f2) - -g = ZeroFunction() - -diag_precon = True - -if diag_precon: - - def tau_sigma_precond(operator): - - tau = 1/operator.sum_abs_row() - sigma = 1/ operator.sum_abs_col() - - return tau, sigma - - tau, sigma = tau_sigma_precond(operator) - -else: - # Compute operator Norm - normK = operator.norm() - # Primal & dual stepsizes - sigma = 10 - tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - - -#%% -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff -import astra -import numpy - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - - ##Construct problem - u = Variable(N*N) - #q = Variable() - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - - # create matrix representation for Astra operator - - vol_geom = astra.create_vol_geom(N, N) - proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) - - proj_id = astra.create_projector('strip', proj_geom, vol_geom) - - matrix_id = astra.projector.matrix(proj_id) - - ProjMat = astra.matrix.get(matrix_id) - - fidelity = sum( ProjMat * u - noisy_data.as_array().ravel() * log(ProjMat * u)) - #constraints = [q>= fidelity, u>=0] - constraints = [u>=0] - - solver = SCS - obj = Minimize( regulariser + fidelity) - prob = Problem(obj, constraints) - result = prob.solve(verbose = True, solver = solver) - - -##%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = pnorm( u - noisy_data.as_array(),1) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(np.reshape(u.value, (N, N))) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_TV_Tomo2D_gaussian.py b/Wrappers/Python/demos/PDHG_TV_Tomo2D_gaussian.py deleted file mode 100644 index dc473a8..0000000 --- a/Wrappers/Python/demos/PDHG_TV_Tomo2D_gaussian.py +++ /dev/null @@ -1,204 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation Denoising using PDHG algorithm: - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + \frac{1}{2}||Ax - g||^{2} - - \nabla: Gradient operator - - A: Projection Matrix - g: Noisy sinogram corrupted with Gaussian Noise - - \alpha: Regularization parameter - -""" - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from ccpi.astra.ops import AstraProjectorSimple - - - -# Create phantom for TV 2D tomography -N = 200 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -detectors = N -angles = np.linspace(0, np.pi, N) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -# Create noisy data. Apply Poisson noise -n1 = np.random.normal(0, 3, size=ig.shape) -noisy_data = AcquisitionData(n1 + sin.as_array(), ag) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,10)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Regularisation Parameter -alpha = 50 - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Create BlockOperator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -# Create functions - -f1 = alpha * MixedL21Norm() -f2 = 0.5 * L2NormSquared(b=noisy_data) -f = BlockFunction(f1, f2) - -g = ZeroFunction() - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 10 -tau = 1/(sigma*normK**2) - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000) - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff -import astra -import numpy - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - -if cvx_not_installable: - - ##Construct problem - u = Variable(N*N) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - - # create matrix representation for Astra operator - vol_geom = astra.create_vol_geom(N, N) - proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) - - proj_id = astra.create_projector('strip', proj_geom, vol_geom) - - matrix_id = astra.projector.matrix(proj_id) - - ProjMat = astra.matrix.get(matrix_id) - - tmp = noisy_data.as_array().ravel() - - fidelity = 0.5 * sum_squares(ProjMat * u - tmp) - - solver = MOSEK - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - np.reshape(u.value, (N,N) )) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(np.reshape(u.value, (N, N))) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N,N) )[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file -- cgit v1.2.3 From c242f21287cfe04995aadb5140f307e8777e3a9b Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 9 May 2019 14:34:07 +0100 Subject: reduce downloads on test --- Wrappers/Python/data/resolution_chart.tiff | Bin 0 -> 65670 bytes Wrappers/Python/test/test_NexusReader.py | 26 +++++++++++++------------- 2 files changed, 13 insertions(+), 13 deletions(-) create mode 100755 Wrappers/Python/data/resolution_chart.tiff diff --git a/Wrappers/Python/data/resolution_chart.tiff b/Wrappers/Python/data/resolution_chart.tiff new file mode 100755 index 0000000..d09cef3 Binary files /dev/null and b/Wrappers/Python/data/resolution_chart.tiff differ diff --git a/Wrappers/Python/test/test_NexusReader.py b/Wrappers/Python/test/test_NexusReader.py index 55543ba..a498d71 100755 --- a/Wrappers/Python/test/test_NexusReader.py +++ b/Wrappers/Python/test/test_NexusReader.py @@ -21,67 +21,67 @@ class TestNexusReader(unittest.TestCase): def tearDown(self): os.remove(self.filename) - - def testGetDimensions(self): + def testAll(self): + # def testGetDimensions(self): nr = NexusReader(self.filename) self.assertEqual(nr.get_sinogram_dimensions(), (135, 91, 160), "Sinogram dimensions are not correct") - def testGetProjectionDimensions(self): + # def testGetProjectionDimensions(self): nr = NexusReader(self.filename) self.assertEqual(nr.get_projection_dimensions(), (91,135,160), "Projection dimensions are not correct") - def testLoadProjectionWithoutDimensions(self): + # def testLoadProjectionWithoutDimensions(self): nr = NexusReader(self.filename) projections = nr.load_projection() self.assertEqual(projections.shape, (91,135,160), "Loaded projection data dimensions are not correct") - def testLoadProjectionWithDimensions(self): + # def testLoadProjectionWithDimensions(self): nr = NexusReader(self.filename) projections = nr.load_projection((slice(0,1), slice(0,135), slice(0,160))) self.assertEqual(projections.shape, (1,135,160), "Loaded projection data dimensions are not correct") - def testLoadProjectionCompareSingle(self): + # def testLoadProjectionCompareSingle(self): nr = NexusReader(self.filename) projections_full = nr.load_projection() projections_part = nr.load_projection((slice(0,1), slice(0,135), slice(0,160))) numpy.testing.assert_array_equal(projections_part, projections_full[0:1,:,:]) - def testLoadProjectionCompareMulti(self): + # def testLoadProjectionCompareMulti(self): nr = NexusReader(self.filename) projections_full = nr.load_projection() projections_part = nr.load_projection((slice(0,3), slice(0,135), slice(0,160))) numpy.testing.assert_array_equal(projections_part, projections_full[0:3,:,:]) - def testLoadProjectionCompareRandom(self): + # def testLoadProjectionCompareRandom(self): nr = NexusReader(self.filename) projections_full = nr.load_projection() projections_part = nr.load_projection((slice(1,8), slice(5,10), slice(8,20))) numpy.testing.assert_array_equal(projections_part, projections_full[1:8,5:10,8:20]) - def testLoadProjectionCompareFull(self): + # def testLoadProjectionCompareFull(self): nr = NexusReader(self.filename) projections_full = nr.load_projection() projections_part = nr.load_projection((slice(None,None), slice(None,None), slice(None,None))) numpy.testing.assert_array_equal(projections_part, projections_full[:,:,:]) - def testLoadFlatCompareFull(self): + # def testLoadFlatCompareFull(self): nr = NexusReader(self.filename) flats_full = nr.load_flat() flats_part = nr.load_flat((slice(None,None), slice(None,None), slice(None,None))) numpy.testing.assert_array_equal(flats_part, flats_full[:,:,:]) - def testLoadDarkCompareFull(self): + # def testLoadDarkCompareFull(self): nr = NexusReader(self.filename) darks_full = nr.load_dark() darks_part = nr.load_dark((slice(None,None), slice(None,None), slice(None,None))) numpy.testing.assert_array_equal(darks_part, darks_full[:,:,:]) - def testProjectionAngles(self): + # def testProjectionAngles(self): nr = NexusReader(self.filename) angles = nr.get_projection_angles() self.assertEqual(angles.shape, (91,), "Loaded projection number of angles are not correct") - def test_get_acquisition_data_subset(self): + # def test_get_acquisition_data_subset(self): nr = NexusReader(self.filename) key = nr.get_image_keys() sl = nr.get_acquisition_data_subset(0,10) -- cgit v1.2.3 From 8e15cddc4d91b3bdc39f476ff83841f092994413 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 9 May 2019 16:01:34 +0100 Subject: delete demos --- Wrappers/Python/wip/Compare_Algs/FISTA_vs_PDHG.py | 117 ------------ .../wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py | 197 --------------------- Wrappers/Python/wip/Compare_Algs/PDHG_vs_CGLS.py | 127 ------------- .../Python/wip/Compare_Algs/Tikhonov_CGLS_PDHG.py | 152 ---------------- 4 files changed, 593 deletions(-) delete mode 100644 Wrappers/Python/wip/Compare_Algs/FISTA_vs_PDHG.py delete mode 100644 Wrappers/Python/wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py delete mode 100644 Wrappers/Python/wip/Compare_Algs/PDHG_vs_CGLS.py delete mode 100644 Wrappers/Python/wip/Compare_Algs/Tikhonov_CGLS_PDHG.py diff --git a/Wrappers/Python/wip/Compare_Algs/FISTA_vs_PDHG.py b/Wrappers/Python/wip/Compare_Algs/FISTA_vs_PDHG.py deleted file mode 100644 index eb62761..0000000 --- a/Wrappers/Python/wip/Compare_Algs/FISTA_vs_PDHG.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -""" -Compare FISTA & PDHG classes - - -Problem: min_x alpha * ||\grad x ||^{2}_{2} + || x - g ||_{1} - - A: Projection operator - g: Sinogram - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import FISTA, PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient, Identity -from ccpi.optimisation.functions import L2NormSquared, L1Norm, \ - FunctionOperatorComposition, BlockFunction, ZeroFunction - -from skimage.util import random_noise - - -# Create Ground truth and noisy data - -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) -noisy_data = ImageData(n1) - -# Regularisation Parameter -alpha = 5 - - -# Setup and run the FISTA algorithm -operator = Gradient(ig) -fidelity = L1Norm(b=noisy_data) -regulariser = FunctionOperatorComposition(alpha * L2NormSquared(), operator) - -x_init = ig.allocate() -opt = {'memopt':True} -fista = FISTA(x_init=x_init , f=regulariser, g=fidelity, opt=opt) -fista.max_iteration = 2000 -fista.update_objective_interval = 50 -fista.run(2000, verbose=False) - -# Setup and run the PDHG algorithm -op1 = Gradient(ig) -op2 = Identity(ig, ag) - -operator = BlockOperator(op1, op2, shape=(2,1) ) -f = BlockFunction(alpha * L2NormSquared(), fidelity) -g = ZeroFunction() - -normK = operator.norm() - -sigma = 1 -tau = 1/(sigma*normK**2) - -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000, verbose=False) - -#%% -# Show results - -plt.figure(figsize=(15,15)) - -plt.subplot(1,2,1) -plt.imshow(pdhg.get_output().as_array()) -plt.title('PDHG reconstruction') - -plt.subplot(1,2,2) -plt.imshow(fista.get_output().as_array()) -plt.title('FISTA reconstruction') - -plt.show() - -diff1 = pdhg.get_output() - fista.get_output() - -plt.imshow(diff1.abs().as_array()) -plt.title('Diff PDHG vs CGLS') -plt.colorbar() -plt.show() - - diff --git a/Wrappers/Python/wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py b/Wrappers/Python/wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py deleted file mode 100644 index 39f0907..0000000 --- a/Wrappers/Python/wip/Compare_Algs/LeastSq_CGLS_FISTA_PDHG.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -""" -Compare Least Squares minimization problem using FISTA, PDHG, CGLS classes -and Astra Built-in CGLS - -Problem: min_x || A x - g ||_{2}^{2} - - A: Projection operator - g: Sinogram - -""" - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, CGLS, FISTA - -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, FunctionOperatorComposition -from ccpi.astra.ops import AstraProjectorSimple -import astra - -# Create Ground truth phantom and Sinogram - -N = 128 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -detectors = N -angles = np.linspace(0, np.pi, N, dtype=np.float32) - -ag = AcquisitionGeometry('parallel','2D', angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -noisy_data = sin - -# Setup and run Astra CGLS algorithm -vol_geom = astra.create_vol_geom(N, N) -proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) - -proj_id = astra.create_projector('strip', proj_geom, vol_geom) - -# Create a sinogram from a phantom -sinogram_id, sinogram = astra.create_sino(x, proj_id) - -# Create a data object for the reconstruction -rec_id = astra.data2d.create('-vol', vol_geom) - -cgls_astra = astra.astra_dict('CGLS') -cgls_astra['ReconstructionDataId'] = rec_id -cgls_astra['ProjectionDataId'] = sinogram_id -cgls_astra['ProjectorId'] = proj_id - -# Create the algorithm object from the configuration structure -alg_id = astra.algorithm.create(cgls_astra) - -astra.algorithm.run(alg_id, 1000) - -recon_cgls_astra = astra.data2d.get(rec_id) - -# Setup and run the CGLS algorithm - -x_init = ig.allocate() - -cgls = CGLS(x_init=x_init, operator=Aop, data=noisy_data) -cgls.max_iteration = 1000 -cgls.update_objective_interval = 200 -cgls.run(1000) - -# Setup and run the PDHG algorithm - -operator = Aop -f = L2NormSquared(b = noisy_data) -g = ZeroFunction() - -## Compute operator Norm -normK = operator.norm() - -## Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 1000 -pdhg.update_objective_interval = 200 -pdhg.run(1000) - -# Setup and run the FISTA algorithm - -fidelity = FunctionOperatorComposition(L2NormSquared(b=noisy_data), Aop) -regularizer = ZeroFunction() - -opt = {'memopt':True} -fista = FISTA(x_init=x_init , f=fidelity, g=regularizer, opt=opt) -fista.max_iteration = 1000 -fista.update_objective_interval = 200 -fista.run(1000, verbose=True) - -#%% Show results - -plt.figure(figsize=(15,15)) -plt.suptitle('Reconstructions ') - -plt.subplot(2,2,1) -plt.imshow(cgls.get_output().as_array()) -plt.title('CGLS reconstruction') - -plt.subplot(2,2,2) -plt.imshow(fista.get_output().as_array()) -plt.title('FISTA reconstruction') - -plt.subplot(2,2,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('PDHG reconstruction') - -plt.subplot(2,2,4) -plt.imshow(recon_cgls_astra) -plt.title('CGLS astra') - -#%% -diff1 = pdhg.get_output() - cgls.get_output() -diff2 = fista.get_output() - cgls.get_output() -diff3 = ImageData(recon_cgls_astra) - cgls.get_output() - -print( diff1.squared_norm()) -print( diff2.squared_norm()) -print( diff3.squared_norm()) - -plt.figure(figsize=(15,15)) - -plt.subplot(3,1,1) -plt.imshow(diff1.abs().as_array()) -plt.title('Diff PDHG vs CGLS') -plt.colorbar() - -plt.subplot(3,1,2) -plt.imshow(diff2.abs().as_array()) -plt.title('Diff FISTA vs CGLS') -plt.colorbar() - -plt.subplot(3,1,3) -plt.imshow(diff3.abs().as_array()) -plt.title('Diff CLGS astra vs CGLS') -plt.colorbar() - - - - - - - - - - - - - - - - - - - -# -# -# -# -# -# -# -# diff --git a/Wrappers/Python/wip/Compare_Algs/PDHG_vs_CGLS.py b/Wrappers/Python/wip/Compare_Algs/PDHG_vs_CGLS.py deleted file mode 100644 index 3155654..0000000 --- a/Wrappers/Python/wip/Compare_Algs/PDHG_vs_CGLS.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, CGLS - -from ccpi.optimisation.operators import Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, FunctionOperatorComposition -from skimage.util import random_noise -from ccpi.astra.ops import AstraProjectorSimple - -#%% - -N = 128 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -detectors = N -angles = np.linspace(0, np.pi, N, dtype=np.float32) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -noisy_data = sin - -x_init = ig.allocate() - -## Setup and run the CGLS algorithm -cgls = CGLS(x_init=x_init, operator=Aop, data=noisy_data) -cgls.max_iteration = 500 -cgls.update_objective_interval = 50 -cgls.run(500, verbose=True) - -# Create BlockOperator -operator = Aop -f = 0.5 * L2NormSquared(b = noisy_data) -g = ZeroFunction() - -## Compute operator Norm -normK = operator.norm() - -## Primal & dual stepsizes -sigma = 0.1 -tau = 1/(sigma*normK**2) -# -# -## Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - -#%% - -diff = pdhg.get_output() - cgls.get_output() -print( diff.norm()) -# -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(pdhg.get_output().as_array()) -plt.title('PDHG reconstruction') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(cgls.get_output().as_array()) -plt.title('CGLS reconstruction') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(diff.abs().as_array()) -plt.title('Difference reconstruction') -plt.colorbar() -plt.show() - - - - - - - - - - - - - - - - - - - - - - -# -# -# -# -# -# -# -# diff --git a/Wrappers/Python/wip/Compare_Algs/Tikhonov_CGLS_PDHG.py b/Wrappers/Python/wip/Compare_Algs/Tikhonov_CGLS_PDHG.py deleted file mode 100644 index 984fca4..0000000 --- a/Wrappers/Python/wip/Compare_Algs/Tikhonov_CGLS_PDHG.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Compare Tikhonov with PDHG, CGLS classes - - -Problem: min_x alpha * ||\grad x ||^{2}_{2} + || A x - g ||_{2}^{2} - - A: Projection operator - g: Sinogram - -""" - - -from ccpi.framework import ImageData, ImageGeometry, \ - AcquisitionGeometry, BlockDataContainer, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, CGLS -from ccpi.optimisation.operators import BlockOperator, Gradient - -from ccpi.optimisation.functions import ZeroFunction, BlockFunction, L2NormSquared -from ccpi.astra.ops import AstraProjectorSimple - -# Create Ground truth phantom and Sinogram - -N = 128 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -detectors = N -angles = np.linspace(0, np.pi, N, dtype=np.float32) - -ag = AcquisitionGeometry('parallel','2D', angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -noisy_data = AcquisitionData( sin.as_array() + np.random.normal(0,3,ig.shape)) - -# Setup and run the CGLS algorithm - -alpha = 50 -Grad = Gradient(ig) - -# Form Tikhonov as a Block CGLS structure - -#%% -op_CGLS = BlockOperator( Aop, alpha * Grad, shape=(2,1)) -block_data = BlockDataContainer(noisy_data, Grad.range_geometry().allocate()) - -x_init = ig.allocate() - -cgls = CGLS(x_init=x_init, operator=op_CGLS, data=block_data) -cgls.max_iteration = 1000 -cgls.update_objective_interval = 200 -cgls.run(1000) - -#%% -#Setup and run the PDHG algorithm - -# Create BlockOperator -op_PDHG = BlockOperator(Grad, Aop, shape=(2,1) ) - -# Create functions - -f1 = 0.5 * alpha**2 * L2NormSquared() -f2 = 0.5 * L2NormSquared(b = noisy_data) -f = BlockFunction(f1, f2) -g = ZeroFunction() - -## Compute operator Norm -normK = op_PDHG.norm() - -## Primal & dual stepsizes -sigma = 10 -tau = 1/(sigma*normK**2) - -pdhg = PDHG(f=f,g=g,operator=op_PDHG, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 1000 -pdhg.update_objective_interval = 200 -pdhg.run(1000) - -#%% -# Show results - -plt.figure(figsize=(15,15)) - -plt.subplot(1,2,1) -plt.imshow(cgls.get_output().as_array()) -plt.title('CGLS reconstruction') - -plt.subplot(1,2,2) -plt.imshow(pdhg.get_output().as_array()) -plt.title('PDHG reconstruction') - -plt.show() - -diff1 = pdhg.get_output() - cgls.get_output() - -plt.imshow(diff1.abs().as_array()) -plt.title('Diff PDHG vs CGLS') -plt.colorbar() -plt.show() - -#%% - -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') -plt.plot(np.linspace(0,N,N), cgls.get_output().as_array()[int(N/2),:], label = 'CGLS') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - - - - - - - - -# -# -# -# -# -# -# -# -- cgit v1.2.3 From b50ba2974db188b3ddfdd6bdccace0b76d781d8c Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 9 May 2019 16:08:48 +0100 Subject: add Algs comparisons --- .../CGLS_FISTA_PDHG_LeastSquares.py | 194 +++++++++++++++++++++ .../demos/CompareAlgorithms/CGLS_PDHG_Tikhonov.py | 153 ++++++++++++++++ .../demos/CompareAlgorithms/FISTA_vs_PDHG.py | 124 +++++++++++++ 3 files changed, 471 insertions(+) create mode 100644 Wrappers/Python/demos/CompareAlgorithms/CGLS_FISTA_PDHG_LeastSquares.py create mode 100644 Wrappers/Python/demos/CompareAlgorithms/CGLS_PDHG_Tikhonov.py create mode 100644 Wrappers/Python/demos/CompareAlgorithms/FISTA_vs_PDHG.py diff --git a/Wrappers/Python/demos/CompareAlgorithms/CGLS_FISTA_PDHG_LeastSquares.py b/Wrappers/Python/demos/CompareAlgorithms/CGLS_FISTA_PDHG_LeastSquares.py new file mode 100644 index 0000000..0875c20 --- /dev/null +++ b/Wrappers/Python/demos/CompareAlgorithms/CGLS_FISTA_PDHG_LeastSquares.py @@ -0,0 +1,194 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" +Compare solutions of FISTA & PDHG + & CGLS & Astra Built-in algorithms for Least Squares + + +Problem: min_x || A x - g ||_{2}^{2} + + A: Projection operator + g: Sinogram + +""" + + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, CGLS, FISTA + +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, FunctionOperatorComposition +from ccpi.astra.ops import AstraProjectorSimple +import astra + +# Create Ground truth phantom and Sinogram + +N = 128 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +device = input('Available device: GPU==1 / CPU==0 ') +ag = AcquisitionGeometry('parallel','2D', angles, detectors) +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + +Aop = AstraProjectorSimple(ig, ag, dev) +sin = Aop.direct(data) + +noisy_data = sin + +# Setup and run Astra CGLS algorithm +vol_geom = astra.create_vol_geom(N, N) +proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) +proj_id = astra.create_projector('strip', proj_geom, vol_geom) + +# Create a sinogram from a phantom +sinogram_id, sinogram = astra.create_sino(x, proj_id) + +# Create a data object for the reconstruction +rec_id = astra.data2d.create('-vol', vol_geom) + +cgls_astra = astra.astra_dict('CGLS') +cgls_astra['ReconstructionDataId'] = rec_id +cgls_astra['ProjectionDataId'] = sinogram_id +cgls_astra['ProjectorId'] = proj_id + +# Create the algorithm object from the configuration structure +alg_id = astra.algorithm.create(cgls_astra) + +astra.algorithm.run(alg_id, 1000) + +recon_cgls_astra = astra.data2d.get(rec_id) + +# Setup and run the CGLS algorithm +x_init = ig.allocate() +cgls = CGLS(x_init=x_init, operator=Aop, data=noisy_data) +cgls.max_iteration = 1000 +cgls.update_objective_interval = 200 +cgls.run(1000, verbose=False) + +# Setup and run the PDHG algorithm +operator = Aop +f = L2NormSquared(b = noisy_data) +g = ZeroFunction() +## Compute operator Norm +normK = operator.norm() +## Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 1000 +pdhg.update_objective_interval = 200 +pdhg.run(1000, verbose=False) + +# Setup and run the FISTA algorithm +fidelity = FunctionOperatorComposition(L2NormSquared(b=noisy_data), Aop) +regularizer = ZeroFunction() + +opt = {'memopt':True} +fista = FISTA(x_init=x_init , f=fidelity, g=regularizer, opt=opt) +fista.max_iteration = 1000 +fista.update_objective_interval = 200 +fista.run(1000, verbose=False) + +#%% Show results + +plt.figure(figsize=(10,10)) +plt.suptitle('Reconstructions ', fontsize=16) + +plt.subplot(2,2,1) +plt.imshow(cgls.get_output().as_array()) +plt.title('CGLS reconstruction') + +plt.subplot(2,2,2) +plt.imshow(fista.get_output().as_array()) +plt.title('FISTA reconstruction') + +plt.subplot(2,2,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('PDHG reconstruction') + +plt.subplot(2,2,4) +plt.imshow(recon_cgls_astra) +plt.title('CGLS astra') + +diff1 = pdhg.get_output() - cgls.get_output() +diff2 = fista.get_output() - cgls.get_output() +diff3 = ImageData(recon_cgls_astra) - cgls.get_output() + +plt.figure(figsize=(15,15)) + +plt.subplot(3,1,1) +plt.imshow(diff1.abs().as_array()) +plt.title('Diff PDHG vs CGLS') +plt.colorbar() + +plt.subplot(3,1,2) +plt.imshow(diff2.abs().as_array()) +plt.title('Diff FISTA vs CGLS') +plt.colorbar() + +plt.subplot(3,1,3) +plt.imshow(diff3.abs().as_array()) +plt.title('Diff CLGS astra vs CGLS') +plt.colorbar() + + + + + + + + + + + + + + + + + + + +# +# +# +# +# +# +# +# diff --git a/Wrappers/Python/demos/CompareAlgorithms/CGLS_PDHG_Tikhonov.py b/Wrappers/Python/demos/CompareAlgorithms/CGLS_PDHG_Tikhonov.py new file mode 100644 index 0000000..942d328 --- /dev/null +++ b/Wrappers/Python/demos/CompareAlgorithms/CGLS_PDHG_Tikhonov.py @@ -0,0 +1,153 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" +Compare solutions of PDHG & "Block CGLS" algorithms for + + +Problem: min_x alpha * ||\grad x ||^{2}_{2} + || A x - g ||_{2}^{2} + + + A: Projection operator + g: Sinogram + +""" + + +from ccpi.framework import ImageData, ImageGeometry, \ + AcquisitionGeometry, BlockDataContainer, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, CGLS +from ccpi.optimisation.operators import BlockOperator, Gradient + +from ccpi.optimisation.functions import ZeroFunction, BlockFunction, L2NormSquared + +# Create Ground truth phantom and Sinogram +N = 128 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D', angles, detectors) +device = input('Available device: GPU==1 / CPU==0 ') +ag = AcquisitionGeometry('parallel','2D', angles, detectors) +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + +sin = Aop.direct(data) + +noisy_data = AcquisitionData( sin.as_array() + np.random.normal(0,3,ig.shape)) + +# Setup and run the CGLS algorithm +alpha = 50 +Grad = Gradient(ig) + +# Form Tikhonov as a Block CGLS structure +op_CGLS = BlockOperator( Aop, alpha * Grad, shape=(2,1)) +block_data = BlockDataContainer(noisy_data, Grad.range_geometry().allocate()) + +x_init = ig.allocate() + +cgls = CGLS(x_init=x_init, operator=op_CGLS, data=block_data) +cgls.max_iteration = 1000 +cgls.update_objective_interval = 200 +cgls.run(1000,verbose=False) + + +#Setup and run the PDHG algorithm + +# Create BlockOperator +op_PDHG = BlockOperator(Grad, Aop, shape=(2,1) ) + +# Create functions +f1 = 0.5 * alpha**2 * L2NormSquared() +f2 = 0.5 * L2NormSquared(b = noisy_data) +f = BlockFunction(f1, f2) +g = ZeroFunction() + +## Compute operator Norm +normK = op_PDHG.norm() + +## Primal & dual stepsizes +sigma = 10 +tau = 1/(sigma*normK**2) + +pdhg = PDHG(f=f,g=g,operator=op_PDHG, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 1000 +pdhg.update_objective_interval = 200 +pdhg.run(1000, verbose=False) + + +#%% +# Show results +plt.figure(figsize=(10,10)) + +plt.subplot(2,1,1) +plt.imshow(cgls.get_output().as_array()) +plt.title('CGLS reconstruction') + +plt.subplot(2,1,2) +plt.imshow(pdhg.get_output().as_array()) +plt.title('PDHG reconstruction') + +plt.show() + +diff1 = pdhg.get_output() - cgls.get_output() + +plt.imshow(diff1.abs().as_array()) +plt.title('Diff PDHG vs CGLS') +plt.colorbar() +plt.show() + +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') +plt.plot(np.linspace(0,N,N), cgls.get_output().as_array()[int(N/2),:], label = 'CGLS') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + + + + + + + + +# +# +# +# +# +# +# +# diff --git a/Wrappers/Python/demos/CompareAlgorithms/FISTA_vs_PDHG.py b/Wrappers/Python/demos/CompareAlgorithms/FISTA_vs_PDHG.py new file mode 100644 index 0000000..c24ebac --- /dev/null +++ b/Wrappers/Python/demos/CompareAlgorithms/FISTA_vs_PDHG.py @@ -0,0 +1,124 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + + +""" +Compare solutions of FISTA & PDHG algorithms + + +Problem: min_x alpha * ||\grad x ||^{2}_{2} + || x - g ||_{1} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Salt & Pepper Noise + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import FISTA, PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient, Identity +from ccpi.optimisation.functions import L2NormSquared, L1Norm, \ + FunctionOperatorComposition, BlockFunction, ZeroFunction + +from skimage.util import random_noise + + +# Create Ground truth and Noisy data +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Regularisation Parameter +alpha = 5 + +############################################################################### +# Setup and run the FISTA algorithm +operator = Gradient(ig) +fidelity = L1Norm(b=noisy_data) +regulariser = FunctionOperatorComposition(alpha * L2NormSquared(), operator) + +x_init = ig.allocate() +opt = {'memopt':True} +fista = FISTA(x_init=x_init , f=regulariser, g=fidelity, opt=opt) +fista.max_iteration = 2000 +fista.update_objective_interval = 50 +fista.run(2000, verbose=False) +############################################################################### + + +############################################################################### +# Setup and run the PDHG algorithm +op1 = Gradient(ig) +op2 = Identity(ig, ag) + +operator = BlockOperator(op1, op2, shape=(2,1) ) +f = BlockFunction(alpha * L2NormSquared(), fidelity) +g = ZeroFunction() + +normK = operator.norm() + +sigma = 1 +tau = 1/(sigma*normK**2) + +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000, verbose=False) +############################################################################### + +# Show results + +plt.figure(figsize=(10,10)) + +plt.subplot(2,1,1) +plt.imshow(pdhg.get_output().as_array()) +plt.title('PDHG reconstruction') + +plt.subplot(2,1,2) +plt.imshow(fista.get_output().as_array()) +plt.title('FISTA reconstruction') + +plt.show() + +diff1 = pdhg.get_output() - fista.get_output() + +plt.imshow(diff1.abs().as_array()) +plt.title('Diff PDHG vs FISTA') +plt.colorbar() +plt.show() + + -- cgit v1.2.3 From 0be5952b3d84abf7e998303bf5613dfce675e725 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 9 May 2019 16:13:42 +0100 Subject: delete old demos from wip --- Wrappers/Python/wip/fista_TV_denoising.py | 72 ----- Wrappers/Python/wip/pdhg_TGV_denoising.py | 230 ------------- Wrappers/Python/wip/pdhg_TGV_tomography2D.py | 200 ------------ Wrappers/Python/wip/pdhg_TV_denoising.py | 204 ------------ Wrappers/Python/wip/pdhg_TV_denoising3D.py | 360 --------------------- .../Python/wip/pdhg_TV_denoising_salt_pepper.py | 268 --------------- Wrappers/Python/wip/pdhg_TV_tomography2D.py | 231 ------------- Wrappers/Python/wip/pdhg_TV_tomography2D_time.py | 152 --------- Wrappers/Python/wip/pdhg_tv_denoising_poisson.py | 187 ----------- 9 files changed, 1904 deletions(-) delete mode 100644 Wrappers/Python/wip/fista_TV_denoising.py delete mode 100644 Wrappers/Python/wip/pdhg_TGV_denoising.py delete mode 100644 Wrappers/Python/wip/pdhg_TGV_tomography2D.py delete mode 100755 Wrappers/Python/wip/pdhg_TV_denoising.py delete mode 100644 Wrappers/Python/wip/pdhg_TV_denoising3D.py delete mode 100644 Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py delete mode 100644 Wrappers/Python/wip/pdhg_TV_tomography2D.py delete mode 100644 Wrappers/Python/wip/pdhg_TV_tomography2D_time.py delete mode 100644 Wrappers/Python/wip/pdhg_tv_denoising_poisson.py diff --git a/Wrappers/Python/wip/fista_TV_denoising.py b/Wrappers/Python/wip/fista_TV_denoising.py deleted file mode 100644 index a9712fc..0000000 --- a/Wrappers/Python/wip/fista_TV_denoising.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 22 14:53:03 2019 - -@author: evangelos -""" - -from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer - -import numpy as np -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, PDHG_old, FISTA - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, FunctionOperatorComposition, BlockFunction, ScaledFunction - -from ccpi.optimisation.algs import FISTA - -from skimage.util import random_noise - -from timeit import default_timer as timer -def dt(steps): - return steps[-1] - steps[-2] - -# Create phantom for TV denoising - -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Add Gaussian noise -n1 = random_noise(data, mode = 'gaussian', mean=0, var = 0.05, seed=10) -noisy_data = ImageData(n1) - - -plt.imshow(noisy_data.as_array()) -plt.title('Noisy data') -plt.show() - -# Regularisation Parameter -alpha = 2 - -operator = Gradient(ig) -g = alpha * MixedL21Norm() -f = 0.5 * L2NormSquared(b = noisy_data) - -x_init = ig.allocate() -opt = {'niter':2000} - - -x = FISTA(x_init, f, g, opt) - -#fista = FISTA() -#fista.set_up(x_init, f, g, opt ) -#fista.max_iteration = 10 -# -#fista.run(2000) -#plt.figure(figsize=(15,15)) -#plt.subplot(3,1,1) -#plt.imshow(fista.get_output().as_array()) -#plt.title('no memopt class') - - - diff --git a/Wrappers/Python/wip/pdhg_TGV_denoising.py b/Wrappers/Python/wip/pdhg_TGV_denoising.py deleted file mode 100644 index 1c570cb..0000000 --- a/Wrappers/Python/wip/pdhg_TGV_denoising.py +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 22 14:53:03 2019 - -@author: evangelos -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, PDHG_old - -from ccpi.optimisation.operators import BlockOperator, Identity, \ - Gradient, SymmetrizedGradient, ZeroOperator -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -from timeit import default_timer as timer -#def dt(steps): -# return steps[-1] - steps[-2] - -# Create phantom for TGV Gaussian denoising - -N = 100 - -data = np.zeros((N,N)) - -x1 = np.linspace(0, int(N/2), N) -x2 = np.linspace(int(N/2), 0., N) -xv, yv = np.meshgrid(x1, x2) - -xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T - -data = xv -data = data/data.max() - -plt.imshow(data) -plt.show() - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Add Gaussian noise -n1 = random_noise(data, mode = 'gaussian', mean=0, var = 0.005, seed=10) -noisy_data = ImageData(n1) - - -plt.imshow(noisy_data.as_array()) -plt.title('Noisy data') -plt.show() - -alpha, beta = 0.1, 0.5 - -#method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") -method = '1' - -if method == '0': - - # Create operators - op11 = Gradient(ig) - op12 = Identity(op11.range_geometry()) - - op22 = SymmetrizedGradient(op11.domain_geometry()) - op21 = ZeroOperator(ig, op22.range_geometry()) - - op31 = Identity(ig, ag) - op32 = ZeroOperator(op22.domain_geometry(), ag) - - operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) - - - f1 = alpha * MixedL21Norm() - f2 = beta * MixedL21Norm() - f3 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2, f3) - g = ZeroFunction() - - -else: - - # Create operators - op11 = Gradient(ig) - op12 = Identity(op11.range_geometry()) - op22 = SymmetrizedGradient(op11.domain_geometry()) - op21 = ZeroOperator(ig, op22.range_geometry()) - - operator = BlockOperator(op11, -1*op12, op21, op22, shape=(2,2) ) - - f1 = alpha * MixedL21Norm() - f2 = beta * MixedL21Norm() - - f = BlockFunction(f1, f2) - g = BlockFunction(0.5 * L2NormSquared(b = noisy_data), ZeroFunction()) - -## Compute operator Norm -normK = operator.norm() -# -## Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -## -opt = {'niter':500} -opt1 = {'niter':500, 'memopt': True} -# -t1 = timer() -res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) -t2 = timer() -# -t3 = timer() -res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) -t4 = timer() -# -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(res[0].as_array()) -plt.title('no memopt') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(res1[0].as_array()) -plt.title('memopt') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow((res1[0] - res[0]).abs().as_array()) -plt.title('diff') -plt.colorbar() -plt.show() - -print("NoMemopt/Memopt is {}/{}".format(t2-t1, t4-t3)) - - -###### - -#%% - -#def update_plot(it_update, objective, x): -# -## sol = pdhg.get_output() -# plt.figure() -# plt.imshow(x[0].as_array()) -# plt.show() -# -# -##def stop_criterion(x,) -# -#pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -#pdhg.max_iteration = 2000 -#pdhg.update_objective_interval = 100 -# -#pdhg.run(4000, verbose=False, callback=update_plot) - - -#%% - - - - - - - - -#%% Check with CVX solution - -#from ccpi.optimisation.operators import SparseFiniteDiff -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -#if cvx_not_installable: -# -# u = Variable(ig.shape) -# w1 = Variable((N, N)) -# w2 = Variable((N, N)) -# -# # create TGV regulariser -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ -# DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ -# beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ -# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ -# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) -# -# constraints = [] -# fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) -# solver = MOSEK -# -# obj = Minimize( regulariser + fidelity) -# prob = Problem(obj) -# result = prob.solve(verbose = True, solver = solver) -# -# diff_cvx = numpy.abs( res[0].as_array() - u.value ) -# -# # Show result -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(res[0].as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# -# plt.subplot(3,1,2) -# plt.imshow(u.value) -# plt.title('CVX solution') -# plt.colorbar() -# -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), res[0].as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') -# plt.legend() -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(primal[-1])) -# print('Min/Max of absolute difference {}/{}'.format(diff_cvx.min(), diff_cvx.max())) -# - - diff --git a/Wrappers/Python/wip/pdhg_TGV_tomography2D.py b/Wrappers/Python/wip/pdhg_TGV_tomography2D.py deleted file mode 100644 index ee3b089..0000000 --- a/Wrappers/Python/wip/pdhg_TGV_tomography2D.py +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 22 14:53:03 2019 - -@author: evangelos -""" - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, PDHG_old - -from ccpi.optimisation.operators import BlockOperator, Identity, \ - Gradient, SymmetrizedGradient, ZeroOperator -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -from timeit import default_timer as timer -from ccpi.astra.ops import AstraProjectorSimple - -#def dt(steps): -# return steps[-1] - steps[-2] - -# Create phantom for TGV Gaussian denoising - -N = 100 - -data = np.zeros((N,N)) - -x1 = np.linspace(0, int(N/2), N) -x2 = np.linspace(int(N/2), 0., N) -xv, yv = np.meshgrid(x1, x2) - -xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T - -data = xv -data = ImageData(data/data.max()) - -plt.imshow(data.as_array()) -plt.show() - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -detectors = N -angles = np.linspace(0,np.pi,N) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -plt.imshow(sin.as_array()) -plt.title('Sinogram') -plt.colorbar() -plt.show() - -# Add Gaussian noise to the sinogram data -np.random.seed(10) -n1 = np.random.random(sin.shape) - -noisy_data = sin + ImageData(5*n1) - -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Sinogram') -plt.colorbar() -plt.show() - -#%% - -alpha, beta = 20, 50 - - -# Create operators -op11 = Gradient(ig) -op12 = Identity(op11.range_geometry()) - -op22 = SymmetrizedGradient(op11.domain_geometry()) -op21 = ZeroOperator(ig, op22.range_geometry()) - -op31 = Aop -op32 = ZeroOperator(op22.domain_geometry(), ag) - -operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) - - -f1 = alpha * MixedL21Norm() -f2 = beta * MixedL21Norm() -f3 = 0.5 * L2NormSquared(b = noisy_data) -f = BlockFunction(f1, f2, f3) -g = ZeroFunction() - -## Compute operator Norm -normK = operator.norm() -# -## Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -## -opt = {'niter':5000} -opt1 = {'niter':5000, 'memopt': True} -# -t1 = timer() -res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) -t2 = timer() -# -plt.imshow(res[0].as_array()) -plt.show() - - -#t3 = timer() -#res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) -#t4 = timer() -# -#plt.figure(figsize=(15,15)) -#plt.subplot(3,1,1) -#plt.imshow(res[0].as_array()) -#plt.title('no memopt') -#plt.colorbar() -#plt.subplot(3,1,2) -#plt.imshow(res1[0].as_array()) -#plt.title('memopt') -#plt.colorbar() -#plt.subplot(3,1,3) -#plt.imshow((res1[0] - res[0]).abs().as_array()) -#plt.title('diff') -#plt.colorbar() -#plt.show() -# -#print("NoMemopt/Memopt is {}/{}".format(t2-t1, t4-t3)) - - -#%% Check with CVX solution - -#from ccpi.optimisation.operators import SparseFiniteDiff -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -#if cvx_not_installable: -# -# u = Variable(ig.shape) -# w1 = Variable((N, N)) -# w2 = Variable((N, N)) -# -# # create TGV regulariser -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ -# DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ -# beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ -# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ -# 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) -# -# constraints = [] -# fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) -# solver = MOSEK -# -# obj = Minimize( regulariser + fidelity) -# prob = Problem(obj) -# result = prob.solve(verbose = True, solver = solver) -# -# diff_cvx = numpy.abs( res[0].as_array() - u.value ) -# -# # Show result -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(res[0].as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# -# plt.subplot(3,1,2) -# plt.imshow(u.value) -# plt.title('CVX solution') -# plt.colorbar() -# -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), res[0].as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') -# plt.legend() -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(primal[-1])) -# print('Min/Max of absolute difference {}/{}'.format(diff_cvx.min(), diff_cvx.max())) - - - diff --git a/Wrappers/Python/wip/pdhg_TV_denoising.py b/Wrappers/Python/wip/pdhg_TV_denoising.py deleted file mode 100755 index b16e8f9..0000000 --- a/Wrappers/Python/wip/pdhg_TV_denoising.py +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 22 14:53:03 2019 - -@author: evangelos -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, PDHG_old - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -from timeit import default_timer as timer -#def dt(steps): -# return steps[-1] - steps[-2] - -# Create phantom for TV Gaussian denoising - -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Add Gaussian noise -n1 = random_noise(data, mode = 'gaussian', mean=0, var = 0.05, seed=10) -noisy_data = ImageData(n1) - - -plt.imshow(noisy_data.as_array()) -plt.title('Noisy data') -plt.show() - -# Regularisation Parameter -alpha = 0.5 - -#method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") -method = '1' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Form Composite Operator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - #### Create functions - - f1 = alpha * MixedL21Norm() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - ########################################################################### - # No Composite # - ########################################################################### - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = 0.5 * L2NormSquared(b = noisy_data) - - -diag_precon = False - -if diag_precon: - - def tau_sigma_precond(operator): - - tau = 1/operator.sum_abs_row() - sigma = 1/ operator.sum_abs_col() - - return tau, sigma - - tau, sigma = tau_sigma_precond(operator) - -else: - # Compute operator Norm - normK = operator.norm() - - # Primal & dual stepsizes - sigma = 1 - tau = 1/(sigma*normK**2) - - -opt = {'niter':5000} -opt1 = {'niter':5000, 'memopt': True} - -t1 = timer() -res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) -t2 = timer() - -t3 = timer() -res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) -t4 = timer() - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(res.as_array()) -plt.title('no memopt') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(res1.as_array()) -plt.title('memopt') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow((res1 - res).abs().as_array()) -plt.title('diff') -plt.colorbar() -plt.show() -# -plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'memopt') -plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'no memopt') -plt.legend() -plt.show() -# -print ("Time: No memopt in {}s, \n Time: Memopt in {}s ".format(t2-t1, t4 -t3)) -diff = (res1 - res).abs().as_array().max() -# -print(" Max of abs difference is {}".format(diff)) - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( res.as_array() - u.value ) - - # Show result - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(res.as_array()) - plt.title('PDHG solution') - plt.colorbar() - - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - - - - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(primal[-1])) - - - - diff --git a/Wrappers/Python/wip/pdhg_TV_denoising3D.py b/Wrappers/Python/wip/pdhg_TV_denoising3D.py deleted file mode 100644 index 06ecfa2..0000000 --- a/Wrappers/Python/wip/pdhg_TV_denoising3D.py +++ /dev/null @@ -1,360 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 22 14:53:03 2019 - -@author: evangelos -""" - -from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer - -import numpy as np -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, PDHG_old - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, FunctionOperatorComposition, BlockFunction - -from skimage.util import random_noise - -from timeit import default_timer as timer -def dt(steps): - return steps[-1] - steps[-2] - -#%% - -# Create phantom for TV denoising - -import timeit -import os -from tomophantom import TomoP3D -import tomophantom - -print ("Building 3D phantom using TomoPhantom software") -tic=timeit.default_timer() -model = 13 # select a model number from the library -N_size = 64 # Define phantom dimensions using a scalar value (cubic phantom) -path = os.path.dirname(tomophantom.__file__) -path_library3D = os.path.join(path, "Phantom3DLibrary.dat") -#This will generate a N_size x N_size x N_size phantom (3D) -phantom_tm = TomoP3D.Model(model, N_size, path_library3D) -#toc=timeit.default_timer() -#Run_time = toc - tic -#print("Phantom has been built in {} seconds".format(Run_time)) -# -#sliceSel = int(0.5*N_size) -##plt.gray() -#plt.figure() -#plt.subplot(131) -#plt.imshow(phantom_tm[sliceSel,:,:],vmin=0, vmax=1) -#plt.title('3D Phantom, axial view') -# -#plt.subplot(132) -#plt.imshow(phantom_tm[:,sliceSel,:],vmin=0, vmax=1) -#plt.title('3D Phantom, coronal view') -# -#plt.subplot(133) -#plt.imshow(phantom_tm[:,:,sliceSel],vmin=0, vmax=1) -#plt.title('3D Phantom, sagittal view') -#plt.show() - -#%% - -N = N_size -ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N, voxel_num_z=N) - -n1 = random_noise(phantom_tm, mode = 'gaussian', mean=0, var = 0.001, seed=10) -noisy_data = ImageData(n1) -#plt.imshow(noisy_data.as_array()[:,:,32]) - -#%% - -# Regularisation Parameter -alpha = 0.02 - -#method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig) - - # Form Composite Operator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - #### Create functions - - f1 = alpha * MixedL21Norm() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - ########################################################################### - # No Composite # - ########################################################################### - operator = Gradient(ig) - f = alpha * FunctionOperatorComposition(operator, MixedL21Norm()) - g = L2NormSquared(b = noisy_data) - - ########################################################################### -#%% - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -opt = {'niter':2000} -opt1 = {'niter':2000, 'memopt': True} - -#t1 = timer() -#res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) -#t2 = timer() - - -t3 = timer() -res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) -t4 = timer() - -#import cProfile -#cProfile.run('res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) ') -### -print ("No memopt in {}s, memopt in {}s ".format(t2-t1, t4 -t3)) -# -## -##%% -# -#plt.figure(figsize=(10,10)) -#plt.subplot(311) -#plt.imshow(res1.as_array()[sliceSel,:,:]) -#plt.colorbar() -#plt.title('3D Phantom, axial view') -# -#plt.subplot(312) -#plt.imshow(res1.as_array()[:,sliceSel,:]) -#plt.colorbar() -#plt.title('3D Phantom, coronal view') -# -#plt.subplot(313) -#plt.imshow(res1.as_array()[:,:,sliceSel]) -#plt.colorbar() -#plt.title('3D Phantom, sagittal view') -#plt.show() -# -#plt.figure(figsize=(10,10)) -#plt.subplot(311) -#plt.imshow(res.as_array()[sliceSel,:,:]) -#plt.colorbar() -#plt.title('3D Phantom, axial view') -# -#plt.subplot(312) -#plt.imshow(res.as_array()[:,sliceSel,:]) -#plt.colorbar() -#plt.title('3D Phantom, coronal view') -# -#plt.subplot(313) -#plt.imshow(res.as_array()[:,:,sliceSel]) -#plt.colorbar() -#plt.title('3D Phantom, sagittal view') -#plt.show() -# -#diff = (res1 - res).abs() -# -#plt.figure(figsize=(10,10)) -#plt.subplot(311) -#plt.imshow(diff.as_array()[sliceSel,:,:]) -#plt.colorbar() -#plt.title('3D Phantom, axial view') -# -#plt.subplot(312) -#plt.imshow(diff.as_array()[:,sliceSel,:]) -#plt.colorbar() -#plt.title('3D Phantom, coronal view') -# -#plt.subplot(313) -#plt.imshow(diff.as_array()[:,:,sliceSel]) -#plt.colorbar() -#plt.title('3D Phantom, sagittal view') -#plt.show() -# -# -# -# -##%% -#pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -#pdhg.max_iteration = 2000 -#pdhg.update_objective_interval = 100 -#### -#pdhgo = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -#pdhgo.max_iteration = 2000 -#pdhgo.update_objective_interval = 100 -#### -#steps = [timer()] -#pdhgo.run(2000) -#steps.append(timer()) -#t1 = dt(steps) -## -#pdhg.run(2000) -#steps.append(timer()) -#t2 = dt(steps) -# -#print ("Time difference {}s {}s {}s Speedup {:.2f}".format(t1,t2,t2-t1, t2/t1)) -#res = pdhg.get_output() -#res1 = pdhgo.get_output() - -#%% -#plt.figure(figsize=(15,15)) -#plt.subplot(3,1,1) -#plt.imshow(res.as_array()) -#plt.title('no memopt') -#plt.colorbar() -#plt.subplot(3,1,2) -#plt.imshow(res1.as_array()) -#plt.title('memopt') -#plt.colorbar() -#plt.subplot(3,1,3) -#plt.imshow((res1 - res).abs().as_array()) -#plt.title('diff') -#plt.colorbar() -#plt.show() - - -#plt.figure(figsize=(15,15)) -#plt.subplot(3,1,1) -#plt.imshow(pdhg.get_output().as_array()) -#plt.title('no memopt class') -#plt.colorbar() -#plt.subplot(3,1,2) -#plt.imshow(res.as_array()) -#plt.title('no memopt') -#plt.colorbar() -#plt.subplot(3,1,3) -#plt.imshow((pdhg.get_output() - res).abs().as_array()) -#plt.title('diff') -#plt.colorbar() -#plt.show() -# -# -# -#plt.figure(figsize=(15,15)) -#plt.subplot(3,1,1) -#plt.imshow(pdhgo.get_output().as_array()) -#plt.title('memopt class') -#plt.colorbar() -#plt.subplot(3,1,2) -#plt.imshow(res1.as_array()) -#plt.title('no memopt') -#plt.colorbar() -#plt.subplot(3,1,3) -#plt.imshow((pdhgo.get_output() - res1).abs().as_array()) -#plt.title('diff') -#plt.colorbar() -#plt.show() - - - - - -# print ("Time difference {}s {}s {}s Speedup {:.2f}".format(t1,t2,t2-t1, t2/t1)) -# res = pdhg.get_output() -# res1 = pdhgo.get_output() -# -# diff = (res-res1) -# print ("diff norm {} max {}".format(diff.norm(), diff.abs().as_array().max())) -# print ("Sum ( abs(diff) ) {}".format(diff.abs().sum())) -# -# -# plt.figure(figsize=(5,5)) -# plt.subplot(1,3,1) -# plt.imshow(res.as_array()) -# plt.colorbar() -# #plt.show() -# -# #plt.figure(figsize=(5,5)) -# plt.subplot(1,3,2) -# plt.imshow(res1.as_array()) -# plt.colorbar() - -#plt.show() - - - -#======= -## opt = {'niter':2000, 'memopt': True} -# -## res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) -# -#>>>>>>> origin/pdhg_fix -# -# -## opt = {'niter':2000, 'memopt': False} -## res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) -# -## plt.figure(figsize=(5,5)) -## plt.subplot(1,3,1) -## plt.imshow(res.as_array()) -## plt.title('memopt') -## plt.colorbar() -## plt.subplot(1,3,2) -## plt.imshow(res1.as_array()) -## plt.title('no memopt') -## plt.colorbar() -## plt.subplot(1,3,3) -## plt.imshow((res1 - res).abs().as_array()) -## plt.title('diff') -## plt.colorbar() -## plt.show() -#pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -#pdhg.max_iteration = 2000 -#pdhg.update_objective_interval = 100 -# -# -#pdhgo = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -#pdhgo.max_iteration = 2000 -#pdhgo.update_objective_interval = 100 -# -#steps = [timer()] -#pdhgo.run(200) -#steps.append(timer()) -#t1 = dt(steps) -# -#pdhg.run(200) -#steps.append(timer()) -#t2 = dt(steps) -# -#print ("Time difference {} {} {}".format(t1,t2,t2-t1)) -#sol = pdhg.get_output().as_array() -##sol = result.as_array() -## -#fig = plt.figure() -#plt.subplot(1,3,1) -#plt.imshow(noisy_data.as_array()) -#plt.colorbar() -#plt.subplot(1,3,2) -#plt.imshow(sol) -#plt.colorbar() -#plt.subplot(1,3,3) -#plt.imshow(pdhgo.get_output().as_array()) -#plt.colorbar() -# -#plt.show() -### -## -#### -##plt.plot(np.linspace(0,N,N), data[int(N/2),:], label = 'GTruth') -##plt.plot(np.linspace(0,N,N), sol[int(N/2),:], label = 'Recon') -##plt.legend() -##plt.show() -# -# -##%% -## diff --git a/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py b/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py deleted file mode 100644 index bd330fc..0000000 --- a/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py +++ /dev/null @@ -1,268 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 22 14:53:03 2019 - -@author: evangelos -""" - -from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, PDHG_old - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ - MixedL21Norm, FunctionOperatorComposition, BlockFunction, ScaledFunction - -from skimage.util import random_noise - -from timeit import default_timer as timer - - - -# ############################################################################ -# Create phantom for TV denoising - -N = 100 -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Add Gaussian noise -n1 = random_noise(data, mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) -noisy_data = ImageData(n1) - -plt.imshow(noisy_data.as_array()) -plt.colorbar() -plt.show() - -#%% - -# Regularisation Parameter -alpha = 2 - -#method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") -method = '0' -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - operator = BlockOperator(op1, op2, shape=(2,1) ) - - f1 = alpha * MixedL21Norm() - f2 = L1Norm(b = noisy_data) - - f = BlockFunction(f1, f2 ) - g = ZeroFunction() - -else: - - ########################################################################### - # No Composite # - ########################################################################### - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = L1Norm(b = noisy_data) - ########################################################################### -#%% - -diag_precon = False - -if diag_precon: - - def tau_sigma_precond(operator): - - tau = 1/operator.sum_abs_row() - sigma = 1/ operator.sum_abs_col() - - return tau, sigma - - tau, sigma = tau_sigma_precond(operator) - -else: - # Compute operator Norm - normK = operator.norm() - - # Primal & dual stepsizes - sigma = 1 - tau = 1/(sigma*normK**2) - - -opt = {'niter':5000} -opt1 = {'niter':5000, 'memopt': True} - -t1 = timer() -res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) -t2 = timer() - - -t3 = timer() -res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) -t4 = timer() - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(res.as_array()) -plt.title('no memopt') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(res1.as_array()) -plt.title('memopt') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow((res1 - res).abs().as_array()) -plt.title('diff') -plt.colorbar() -plt.show() -# -plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'memopt') -plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'no memopt') -plt.legend() -plt.show() -# -print ("Time: No memopt in {}s, \n Time: Memopt in {}s ".format(t2-t1, t4 -t3)) -diff = (res1 - res).abs().as_array().max() -# -print(" Max of abs difference is {}".format(diff)) - -#pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -#pdhg.max_iteration = 2000 -#pdhg.update_objective_interval = 10 -# -#pdhg.run(2000) - - - -#sol = pdhg.get_output().as_array() -##sol = result.as_array() -## -#fig = plt.figure() -#plt.subplot(1,2,1) -#plt.imshow(noisy_data.as_array()) -##plt.colorbar() -#plt.subplot(1,2,2) -#plt.imshow(sol) -##plt.colorbar() -#plt.show() -## - -## -#plt.plot(np.linspace(0,N,N), data[int(N/2),:], label = 'GTruth') -#plt.plot(np.linspace(0,N,N), sol[int(N/2),:], label = 'Recon') -#plt.legend() -#plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - - fidelity = pnorm( u - noisy_data.as_array(),1) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( res.as_array() - u.value ) - -# Show result - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(res.as_array()) - plt.title('PDHG solution') - plt.colorbar() - - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - - - - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(primal[-1])) - - - -#try_cvx = input("Do you want CVX comparison (0/1)") -# -#if try_cvx=='0': -# -# from cvxpy import * -# import sys -# sys.path.insert(0,'/Users/evangelos/Desktop/Projects/CCPi/CCPi-Framework/Wrappers/Python/ccpi/optimisation/cvx_scripts') -# from cvx_functions import TV_cvx -# -# u = Variable((N, N)) -# fidelity = pnorm( u - noisy_data.as_array(),1) -# regulariser = alpha * TV_cvx(u) -# solver = MOSEK -# obj = Minimize( regulariser + fidelity) -# constraints = [] -# prob = Problem(obj, constraints) -# -# # Choose solver (SCS is fast but less accurate than MOSEK) -# result = prob.solve(verbose = True, solver = solver) -# -# print('Objective value is {} '.format(obj.value)) -# -# diff_pdhg_cvx = np.abs(u.value - res.as_array()) -# plt.imshow(diff_pdhg_cvx) -# plt.colorbar() -# plt.title('|CVX-PDHG|') -# plt.show() -# -# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') -# plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'PDHG') -# plt.legend() -# plt.show() -# -#else: -# print('No CVX solution available') - - - - diff --git a/Wrappers/Python/wip/pdhg_TV_tomography2D.py b/Wrappers/Python/wip/pdhg_TV_tomography2D.py deleted file mode 100644 index e123739..0000000 --- a/Wrappers/Python/wip/pdhg_TV_tomography2D.py +++ /dev/null @@ -1,231 +0,0 @@ -# -*- coding: utf-8 -*- - -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 22 14:53:03 2019 - -@author: evangelos -""" - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, PDHG_old - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from ccpi.astra.operators import AstraProjectorSimple -from timeit import default_timer as timer - - -#%%############################################################################### -# Create phantom for TV tomography - -#import os -#import tomophantom -#from tomophantom import TomoP2D -#from tomophantom.supp.qualitymetrics import QualityTools - -#model = 1 # select a model number from the library -#N = 150 # set dimension of the phantom -## one can specify an exact path to the parameters file -## path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' -#path = os.path.dirname(tomophantom.__file__) -#path_library2D = os.path.join(path, "Phantom2DLibrary.dat") -##This will generate a N_size x N_size phantom (2D) -#phantom_2D = TomoP2D.Model(model, N, path_library2D) -#ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -#data = ImageData(phantom_2D, geometry=ig) - -N = 75 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - - -detectors = N -angles = np.linspace(0,np.pi,N) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -plt.imshow(sin.as_array()) -plt.title('Sinogram') -plt.colorbar() -plt.show() - -# Add Gaussian noise to the sinogram data -np.random.seed(10) -n1 = np.random.random(sin.shape) - -noisy_data = sin + ImageData(5*n1) - -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Sinogram') -plt.colorbar() -plt.show() - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Form Composite Operator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -alpha = 10 -f = BlockFunction( alpha * MixedL21Norm(), \ - 0.5 * L2NormSquared(b = noisy_data) ) -g = ZeroFunction() - -# Compute operator Norm -normK = operator.norm() - -## Primal & dual stepsizes -diag_precon = True - -if diag_precon: - - def tau_sigma_precond(operator): - - tau = 1/operator.sum_abs_row() - sigma = 1/ operator.sum_abs_col() - - return tau, sigma - - tau, sigma = tau_sigma_precond(operator) - -else: - normK = operator.norm() - sigma = 1 - tau = 1/(sigma*normK**2) - -# Compute operator Norm - - -# Primal & dual stepsizes - -opt = {'niter':200} -opt1 = {'niter':200, 'memopt': True} - -t1 = timer() -res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) -t2 = timer() - - -t3 = timer() -res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) -t4 = timer() -# -# -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(res.as_array()) -plt.title('no memopt') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(res1.as_array()) -plt.title('memopt') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow((res1 - res).abs().as_array()) -plt.title('diff') -plt.colorbar() -plt.show() -# -plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'memopt') -plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'no memopt') -plt.legend() -plt.show() -# -print ("Time: No memopt in {}s, \n Time: Memopt in {}s ".format(t2-t1, t4 -t3)) -diff = (res1 - res).abs().as_array().max() -# -print(" Max of abs difference is {}".format(diff)) - - -#%% Check with CVX solution - -#from ccpi.optimisation.operators import SparseFiniteDiff -#import astra -#import numpy -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -# -#if cvx_not_installable: -# -# -# u = Variable( N*N) -# -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) -# -# # create matrix representation for Astra operator -# -# vol_geom = astra.create_vol_geom(N, N) -# proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) -# -# proj_id = astra.create_projector('strip', proj_geom, vol_geom) -# -# matrix_id = astra.projector.matrix(proj_id) -# -# ProjMat = astra.matrix.get(matrix_id) -# -# fidelity = 0.5 * sum_squares(ProjMat * u - noisy_data.as_array().ravel()) -# -# # choose solver -# if 'MOSEK' in installed_solvers(): -# solver = MOSEK -# else: -# solver = SCS -# -# obj = Minimize( regulariser + fidelity) -# prob = Problem(obj) -# result = prob.solve(verbose = True, solver = solver) -# -##%% -# -# u_rs = np.reshape(u.value, (N,N)) -# -# diff_cvx = numpy.abs( res.as_array() - u_rs ) -# -# # Show result -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(res.as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# -# plt.subplot(3,1,2) -# plt.imshow(u_rs) -# plt.title('CVX solution') -# plt.colorbar() -# -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), u_rs[int(N/2),:], label = 'CVX') -# plt.legend() -# -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(primal[-1])) \ No newline at end of file diff --git a/Wrappers/Python/wip/pdhg_TV_tomography2D_time.py b/Wrappers/Python/wip/pdhg_TV_tomography2D_time.py deleted file mode 100644 index 5423b22..0000000 --- a/Wrappers/Python/wip/pdhg_TV_tomography2D_time.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- - -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 22 14:53:03 2019 - -@author: evangelos -""" - -from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer, AcquisitionGeometry, AcquisitionData - -import numpy as np -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG, PDHG_old - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction, ScaledFunction - -from ccpi.astra.ops import AstraProjectorSimple, AstraProjectorMC -from skimage.util import random_noise - - -#%%############################################################################### -# Create phantom for TV tomography - -import numpy as np -import matplotlib.pyplot as plt -import os -import tomophantom -from tomophantom import TomoP2D - -model = 102 # note that the selected model is temporal (2D + time) -N = 150 # set dimension of the phantom -# one can specify an exact path to the parameters file -# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' -path = os.path.dirname(tomophantom.__file__) -path_library2D = os.path.join(path, "Phantom2DLibrary.dat") -#This will generate a N_size x N_size x Time frames phantom (2D + time) -phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) - -plt.close('all') -plt.figure(1) -plt.rcParams.update({'font.size': 21}) -plt.title('{}''{}'.format('2D+t phantom using model no.',model)) -for sl in range(0,np.shape(phantom_2Dt)[0]): - im = phantom_2Dt[sl,:,:] - plt.imshow(im, vmin=0, vmax=1) - plt.pause(.1) - plt.draw - -#N = 150 -#x = np.zeros((N,N)) -#x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -#x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -#%% -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) -data = ImageData(phantom_2Dt, geometry=ig) - - - -detectors = 150 -angles = np.linspace(0,np.pi,100) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors, channels = np.shape(phantom_2Dt)[0]) -Aop = AstraProjectorMC(ig, ag, 'gpu') -sin = Aop.direct(data) - -plt.imshow(sin.as_array()[10]) -plt.title('Sinogram') -plt.colorbar() -plt.show() - -# Add Gaussian noise to the sinogram data -np.random.seed(10) -n1 = np.random.random(sin.shape) - -noisy_data = sin + ImageData(5*n1) - -plt.imshow(noisy_data.as_array()[10]) -plt.title('Noisy Sinogram') -plt.colorbar() -plt.show() - - -#%% Works only with Composite Operator Structure of PDHG - -#ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Form Composite Operator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -alpha = 50 -f = BlockFunction( alpha * MixedL21Norm(), \ - 0.5 * L2NormSquared(b = noisy_data) ) -g = ZeroFunction() - -# Compute operator Norm -normK = operator.norm() - -## Primal & dual stepsizes - -sigma = 1 -tau = 1/(sigma*normK**2) - -#sigma = 1/normK -#tau = 1/normK - -opt = {'niter':2000} - -res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) - -plt.figure(figsize=(5,5)) -plt.imshow(res.as_array()) -plt.colorbar() -plt.show() - -#sigma = 10 -#tau = 1/(sigma*normK**2) -# -#pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -#pdhg.max_iteration = 5000 -#pdhg.update_objective_interval = 20 -# -#pdhg.run(5000) -# -##%% -#sol = pdhg.get_output().as_array() -#fig = plt.figure() -#plt.subplot(1,2,1) -#plt.imshow(noisy_data.as_array()) -##plt.colorbar() -#plt.subplot(1,2,2) -#plt.imshow(sol) -##plt.colorbar() -#plt.show() - - -#%% -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), sol[int(N/2),:], label = 'Recon') -plt.legend() -plt.show() - - diff --git a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py deleted file mode 100644 index cb37598..0000000 --- a/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py +++ /dev/null @@ -1,187 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Feb 22 14:53:03 2019 - -@author: evangelos -""" - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.framework import ImageData, ImageGeometry - -from ccpi.optimisation.algorithms import PDHG, PDHG_old - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ - MixedL21Norm, BlockFunction - - -from skimage.util import random_noise -from timeit import default_timer as timer - - - -# ############################################################################ -# Create phantom for TV Poisson denoising - -N = 200 -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Add Gaussian noise -n1 = random_noise(data, mode = 'poisson', seed = 10) -noisy_data = ImageData(n1) - -plt.imshow(noisy_data.as_array()) -plt.colorbar() -plt.show() - -# Regularisation Parameter -alpha = 2 - -#method = input("Enter structure of PDHG (0=Composite or 1=NotComposite): ") - -method = '0' -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Form Composite Operator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - f1 = alpha * MixedL21Norm() - f2 = KullbackLeibler(noisy_data) - - f = BlockFunction(f1, f2 ) - g = ZeroFunction() - -else: - - ########################################################################### - # No Composite # - ########################################################################### - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = KullbackLeibler(noisy_data) - ########################################################################### - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -opt = {'niter':2000} -opt1 = {'niter':2000, 'memopt': True} - -t1 = timer() -res, time, primal, dual, pdgap = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt) -t2 = timer() - -t3 = timer() -res1, time1, primal1, dual1, pdgap1 = PDHG_old(f, g, operator, tau = tau, sigma = sigma, opt = opt1) -t4 = timer() - -print(pdgap[-1]) - - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(res.as_array()) -plt.title('no memopt') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(res1.as_array()) -plt.title('memopt') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow((res1 - res).abs().as_array()) -plt.title('diff') -plt.colorbar() -plt.show() -# -plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'memopt') -plt.plot(np.linspace(0,N,N), res.as_array()[int(N/2),:], label = 'no memopt') -plt.legend() -plt.show() - -print ("Time: No memopt in {}s, \n Time: Memopt in {}s ".format(t2-t1, t4 -t3)) -diff = (res1 - res).abs().as_array().max() - -print(" Max of abs difference is {}".format(diff)) - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u1 = Variable(ig.shape) - q = Variable() - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) - - fidelity = sum( u1 - multiply(noisy_data.as_array(), log(u1)) ) - constraints = [q>= fidelity, u1>=0] - - solver = ECOS - obj = Minimize( regulariser + q) - prob = Problem(obj, constraints) - result = prob.solve(verbose = True, solver = solver) - - - diff_cvx = numpy.abs( res.as_array() - u1.value ) - - # Show result - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(res.as_array()) - plt.title('PDHG solution') - plt.colorbar() - - plt.subplot(3,1,2) - plt.imshow(u1.value) - plt.title('CVX solution') - plt.colorbar() - - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), res1.as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') - plt.legend() - - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(primal[-1])) - - - - - -- cgit v1.2.3 From ffb6b9f2bda8eda074b1d1730d51b3035adc9475 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 9 May 2019 17:03:36 +0100 Subject: Added loader with number of test images --- Wrappers/Python/ccpi/framework/TestData.py | 65 ++++++++++++++++++++++-------- Wrappers/Python/setup.py | 3 +- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/TestData.py b/Wrappers/Python/ccpi/framework/TestData.py index 61ed4df..45ee1d4 100755 --- a/Wrappers/Python/ccpi/framework/TestData.py +++ b/Wrappers/Python/ccpi/framework/TestData.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from ccpi.framework import ImageData +from ccpi.framework import ImageData, ImageGeometry import numpy from PIL import Image import os @@ -14,29 +14,62 @@ class TestData(object): BOAT = 'boat.tiff' CAMERA = 'camera.png' PEPPERS = 'peppers.tiff' + RESOLUTION_CHART = 'resolution_chart.tiff' + SIMPLE_PHANTOM_2D = 'simple_jakobs_phantom' def __init__(self, **kwargs): self.data_dir = kwargs.get('data_dir', data_dir) def load(self, which, size=(512,512), scale=(0,1), **kwargs): - if which not in [TestData.BOAT, TestData.CAMERA, TestData.PEPPERS]: + if which not in [TestData.BOAT, TestData.CAMERA, + TestData.PEPPERS, TestData.RESOLUTION_CHART, + TestData.SIMPLE_PHANTOM_2D]: raise ValueError('Unknown TestData {}.'.format(which)) - tmp = Image.open(os.path.join(data_dir, which)) - - data = numpy.array(tmp.resize(size)) - - if scale is not None: - dmax = data.max() - dmin = data.min() - - data = data -dmin / (dmax - dmin) + if which == TestData.SIMPLE_PHANTOM_2D: + N = size[0] + M = size[1] + sdata = numpy.zeros((N,M)) + sdata[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 + sdata[round(M/8):round(7*M/8),round(3*M/8):round(5*M/8)] = 1 + ig = ImageGeometry(voxel_num_x = N, voxel_num_y = M, dimension_labels=[ImageGeometry.HORIZONTAL_X, ImageGeometry.HORIZONTAL_Y]) + data = ig.allocate() + data.geometry = ig + data.fill(sdata) + else: + tmp = Image.open(os.path.join(self.data_dir, which)) + print (tmp) + bands = tmp.getbands() + if len(bands) > 1: + # convert to greyscale + #tmp = tmp.convert('L') + ig = ImageGeometry(voxel_num_x=size[0], voxel_num_y=size[1], channels=len(bands), + dimension_labels=[ImageGeometry.HORIZONTAL_X, ImageGeometry.HORIZONTAL_Y, ImageGeometry.CHANNEL]) + + data = ig.allocate() + data.geometry = ig + #newsize = (size[0], size[1], len(bands)) + else: + ig = ImageGeometry(voxel_num_x = size[0], voxel_num_y = size[1], dimension_labels=[ImageGeometry.HORIZONTAL_X, ImageGeometry.HORIZONTAL_Y]) + data = ig.allocate() + data.geometry = ig + #newsize = size - if scale != (0,1): - #data = (data-dmin)/(dmax-dmin) * (scale[1]-scale[0]) +scale[0]) - data *= (scale[1]-scale[0]) - data += scale[0] + data.fill(numpy.array(tmp.resize((size[1],size[0])))) + + if scale is not None: + dmax = data.as_array().max() + dmin = data.as_array().min() + + # scale 0,1 + data = (data -dmin) / (dmax - dmin) + + if scale != (0,1): + #data = (data-dmin)/(dmax-dmin) * (scale[1]-scale[0]) +scale[0]) + data *= (scale[1]-scale[0]) + data += scale[0] - return ImageData(data) + print ("data.geometry", data.geometry) + return data def camera(**kwargs): diff --git a/Wrappers/Python/setup.py b/Wrappers/Python/setup.py index 44da471..8bd33a6 100644 --- a/Wrappers/Python/setup.py +++ b/Wrappers/Python/setup.py @@ -40,7 +40,8 @@ setup( 'ccpi.contrib','ccpi.contrib.optimisation', 'ccpi.contrib.optimisation.algorithms'], data_files = [('share/ccpi', ['data/boat.tiff', 'data/peppers.tiff', - 'data/camera.png'])], + 'data/camera.png', + 'data/resolution_chart.tiff'])], # Project uses reStructuredText, so ensure that the docutils get # installed or upgraded on the target machine -- cgit v1.2.3 From e2626466b7fc8fd73b416af60287d7724e6b0cd5 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 9 May 2019 17:04:48 +0100 Subject: specify data order in Geometry and use it in allocate --- Wrappers/Python/ccpi/framework/framework.py | 75 ++++++++++++++++++++++------- Wrappers/Python/test/test_DataContainer.py | 30 ++++++++++++ 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/framework.py b/Wrappers/Python/ccpi/framework/framework.py index dbe7d0a..4a2a9e8 100755 --- a/Wrappers/Python/ccpi/framework/framework.py +++ b/Wrappers/Python/ccpi/framework/framework.py @@ -63,7 +63,8 @@ class ImageGeometry(object): center_x=0, center_y=0, center_z=0, - channels=1): + channels=1, + **kwargs): self.voxel_num_x = voxel_num_x self.voxel_num_y = voxel_num_y @@ -80,25 +81,41 @@ class ImageGeometry(object): if self.channels > 1: if self.voxel_num_z>1: self.length = 4 - self.shape = (self.channels, self.voxel_num_z, self.voxel_num_y, self.voxel_num_x) + shape = (self.channels, self.voxel_num_z, self.voxel_num_y, self.voxel_num_x) dim_labels = [ImageGeometry.CHANNEL, ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] else: self.length = 3 - self.shape = (self.channels, self.voxel_num_y, self.voxel_num_x) + shape = (self.channels, self.voxel_num_y, self.voxel_num_x) dim_labels = [ImageGeometry.CHANNEL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] else: if self.voxel_num_z>1: self.length = 3 - self.shape = (self.voxel_num_z, self.voxel_num_y, self.voxel_num_x) + shape = (self.voxel_num_z, self.voxel_num_y, self.voxel_num_x) dim_labels = [ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] else: self.length = 2 - self.shape = (self.voxel_num_y, self.voxel_num_x) + shape = (self.voxel_num_y, self.voxel_num_x) dim_labels = [ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] - self.dimension_labels = dim_labels + labels = kwargs.get('dimension_labels', None) + if labels is None: + self.shape = shape + self.dimension_labels = dim_labels + else: + order = [] + for i, el in enumerate(labels): + for j, ek in enumerate(dim_labels): + if el == ek: + order.append(j) + break + if order != [0,1,2]: + # resort + self.shape = tuple([shape[i] for i in order]) + self.dimension_labels = labels + + def get_min_x(self): return self.center_x - 0.5*self.voxel_num_x*self.voxel_size_x @@ -146,7 +163,10 @@ class ImageGeometry(object): return repres def allocate(self, value=0, dimension_labels=None, **kwargs): '''allocates an ImageData according to the size expressed in the instance''' - out = ImageData(geometry=self) + if dimension_labels is None: + out = ImageData(geometry=self, dimension_labels=self.dimension_labels) + else: + out = ImageData(geometry=self, dimension_labels=dimension_labels) if isinstance(value, Number): if value != 0: out += value @@ -164,9 +184,7 @@ class ImageGeometry(object): out.fill(numpy.random.randint(max_value,size=self.shape)) else: raise ValueError('Value {} unknown'.format(value)) - if dimension_labels is not None: - if dimension_labels != self.dimension_labels: - return out.subset(dimensions=dimension_labels) + return out # The following methods return 2 members of the class, therefore I # don't think we need to implement them. @@ -244,6 +262,8 @@ class AcquisitionGeometry(object): self.channels = channels self.angle_unit=kwargs.get(AcquisitionGeometry.ANGLE_UNIT, AcquisitionGeometry.DEGREE) + + # default labels if channels > 1: if pixel_num_v > 1: shape = (channels, num_of_angles , pixel_num_v, pixel_num_h) @@ -262,9 +282,27 @@ class AcquisitionGeometry(object): else: shape = (num_of_angles, pixel_num_h) dim_labels = [AcquisitionGeometry.ANGLE, AcquisitionGeometry.HORIZONTAL] - self.shape = shape + + labels = kwargs.get('dimension_labels', None) + if labels is None: + self.shape = shape + self.dimension_labels = dim_labels + else: + if len(labels) != len(dim_labels): + raise ValueError('Wrong number of labels. Expected {} got {}'.format(len(dim_labels), len(labels))) + order = [] + for i, el in enumerate(labels): + for j, ek in enumerate(dim_labels): + if el == ek: + order.append(j) + break + if order != [0,1,2]: + # resort + self.shape = tuple([shape[i] for i in order]) + self.dimension_labels = labels + + - self.dimension_labels = dim_labels def clone(self): '''returns a copy of the AcquisitionGeometry''' @@ -292,7 +330,10 @@ class AcquisitionGeometry(object): return repres def allocate(self, value=0, dimension_labels=None): '''allocates an AcquisitionData according to the size expressed in the instance''' - out = AcquisitionData(geometry=self) + if dimension_labels is None: + out = AcquisitionData(geometry=self, dimension_labels=self.dimension_labels) + else: + out = AcquisitionData(geometry=self, dimension_labels=dimension_labels) if isinstance(value, Number): if value != 0: out += value @@ -310,9 +351,7 @@ class AcquisitionGeometry(object): out.fill(numpy.random.randint(max_value,size=self.shape)) else: raise ValueError('Value {} unknown'.format(value)) - if dimension_labels is not None: - if dimension_labels != self.dimension_labels: - return out.subset(dimensions=dimension_labels) + return out class DataContainer(object): @@ -658,7 +697,7 @@ class DataContainer(object): # geometry=self.geometry) return out else: - raise ValueError(message(type(self),"Wrong size for data memory: ", out.shape,self.shape)) + raise ValueError(message(type(self),"Wrong size for data memory: out {} x2 {} expected {}".format( out.shape,x2.shape ,self.shape))) elif issubclass(type(out), DataContainer) and isinstance(x2, (int,float,complex)): if self.check_dimensions(out): kwargs['out']=out.as_array() @@ -806,7 +845,7 @@ class ImageData(DataContainer): self.geometry = kwargs.get('geometry', None) if array is None: if self.geometry is not None: - shape, dimension_labels = self.get_shape_labels(self.geometry) + shape, dimension_labels = self.get_shape_labels(self.geometry, dimension_labels) array = numpy.zeros( shape , dtype=numpy.float32) super(ImageData, self).__init__(array, deep_copy, diff --git a/Wrappers/Python/test/test_DataContainer.py b/Wrappers/Python/test/test_DataContainer.py index e92d4c6..4c53df8 100755 --- a/Wrappers/Python/test/test_DataContainer.py +++ b/Wrappers/Python/test/test_DataContainer.py @@ -487,6 +487,13 @@ class TestDataContainer(unittest.TestCase): self.assertNumpyArrayEqual(vol1.as_array(), numpy.ones(vol.shape) * 4) self.assertEqual(vol.number_of_dimensions, 3) + + ig2 = ImageGeometry (voxel_num_x=2,voxel_num_y=3,voxel_num_z=4, + dimension_labels=[ImageGeometry.HORIZONTAL_X, ImageGeometry.HORIZONTAL_Y, + ImageGeometry.VERTICAL]) + data = ig2.allocate() + self.assertNumpyArrayEqual(numpy.asarray(data.shape), numpy.asarray(ig2.shape)) + self.assertNumpyArrayEqual(numpy.asarray(data.shape), data.as_array().shape) def test_AcquisitionData(self): sgeometry = AcquisitionGeometry(dimension=2, angles=numpy.linspace(0, 180, num=10), @@ -494,6 +501,29 @@ class TestDataContainer(unittest.TestCase): pixel_num_h=5, channels=2) sino = AcquisitionData(geometry=sgeometry) self.assertEqual(sino.shape, (2, 10, 3, 5)) + + ag = AcquisitionGeometry (pixel_num_h=2,pixel_num_v=3,channels=4, dimension=2, angles=numpy.linspace(0, 180, num=10), + geom_type='parallel', ) + print (ag.shape) + print (ag.dimension_labels) + + data = ag.allocate() + self.assertNumpyArrayEqual(numpy.asarray(data.shape), numpy.asarray(ag.shape)) + self.assertNumpyArrayEqual(numpy.asarray(data.shape), data.as_array().shape) + + print (data.shape, ag.shape, data.as_array().shape) + + ag2 = AcquisitionGeometry (pixel_num_h=2,pixel_num_v=3,channels=4, dimension=2, angles=numpy.linspace(0, 180, num=10), + geom_type='parallel', + dimension_labels=[AcquisitionGeometry.VERTICAL , + AcquisitionGeometry.ANGLE, AcquisitionGeometry.HORIZONTAL, AcquisitionGeometry.CHANNEL]) + + data = ag2.allocate() + print (data.shape, ag2.shape, data.as_array().shape) + self.assertNumpyArrayEqual(numpy.asarray(data.shape), numpy.asarray(ag2.shape)) + self.assertNumpyArrayEqual(numpy.asarray(data.shape), data.as_array().shape) + + def test_ImageGeometry_allocate(self): vgeometry = ImageGeometry(voxel_num_x=4, voxel_num_y=3, channels=2) image = vgeometry.allocate() -- cgit v1.2.3 From b3e2c5e7a9d1a4f291c217a2f0d221b55638b4e9 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 9 May 2019 17:05:20 +0100 Subject: use TestData --- .../PDHG_examples/PDHG_TV_Denoising_Gaussian.py | 55 +++++++++++++++------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py index afdb6a2..7966ded 100644 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py @@ -50,42 +50,57 @@ from ccpi.optimisation.algorithms import PDHG from ccpi.optimisation.operators import BlockOperator, Identity, Gradient from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ MixedL21Norm, BlockFunction - + +from ccpi.framework import TestData +import os, sys +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) + # Load Data -N = 100 +N = 256 +M = 300 data = np.zeros((N,N)) data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +data = loader.load(TestData.PEPPERS, size=(N,M), scale=(0,1)) +#ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +print (data) +ig = data.geometry ag = ig # Create Noisy data. Add Gaussian noise np.random.seed(10) -noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.1, size=ig.shape) ) +noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.1, size=data.shape) ) + +print ("min {} max {}".format(data.as_array().min(), data.as_array().max())) # Show Ground Truth and Noisy Data -plt.figure(figsize=(15,15)) -plt.subplot(2,1,1) +plt.figure() +plt.subplot(1,3,1) plt.imshow(data.as_array()) plt.title('Ground Truth') plt.colorbar() -plt.subplot(2,1,2) +plt.subplot(1,3,2) plt.imshow(noisy_data.as_array()) plt.title('Noisy Data') plt.colorbar() +plt.subplot(1,3,3) +plt.imshow((data - noisy_data).as_array()) +plt.title('diff') +plt.colorbar() + plt.show() # Regularisation Parameter -alpha = 0.2 +alpha = 2. method = '0' if method == '0': # Create operators - op1 = Gradient(ig) + op1 = Gradient(ig, correlation='SpaceChannels') op2 = Identity(ig, ag) # Create BlockOperator @@ -114,28 +129,32 @@ tau = 1/(sigma*normK**2) # Setup and Run the PDHG algorithm pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 3000 -pdhg.update_objective_interval = 200 -pdhg.run(3000, verbose=False) +pdhg.max_iteration = 10000 +pdhg.update_objective_interval = 100 +pdhg.run(1000, verbose=True) # Show Results -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) +plt.figure() +plt.subplot(1,3,1) plt.imshow(data.as_array()) plt.title('Ground Truth') plt.colorbar() -plt.subplot(3,1,2) +plt.clim(0,1) +plt.subplot(1,3,2) plt.imshow(noisy_data.as_array()) plt.title('Noisy Data') plt.colorbar() -plt.subplot(3,1,3) +plt.clim(0,1) +plt.subplot(1,3,3) plt.imshow(pdhg.get_output().as_array()) plt.title('TV Reconstruction') +plt.clim(0,1) plt.colorbar() plt.show() -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.plot(np.linspace(0,N,M), noisy_data.as_array()[int(N/2),:], label = 'Noisy data') plt.legend() plt.title('Middle Line Profiles') plt.show() -- cgit v1.2.3 From 6c0f6b62116800106e90f7fa704e14998c2cd032 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 11:45:17 +0100 Subject: add get_order_by_label --- Wrappers/Python/ccpi/framework/framework.py | 33 +++++++++++++++++------------ 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/framework.py b/Wrappers/Python/ccpi/framework/framework.py index 4a2a9e8..a5c9160 100755 --- a/Wrappers/Python/ccpi/framework/framework.py +++ b/Wrappers/Python/ccpi/framework/framework.py @@ -104,18 +104,21 @@ class ImageGeometry(object): self.shape = shape self.dimension_labels = dim_labels else: - order = [] - for i, el in enumerate(labels): - for j, ek in enumerate(dim_labels): - if el == ek: - order.append(j) - break + order = self.get_order_by_label(labels, dim_labels) if order != [0,1,2]: # resort self.shape = tuple([shape[i] for i in order]) self.dimension_labels = labels - + def get_order_by_label(self, dimension_labels, default_dimension_labels): + order = [] + for i, el in enumerate(dimension_labels): + for j, ek in enumerate(default_dimension_labels): + if el == ek: + order.append(j) + break + return order + def get_min_x(self): return self.center_x - 0.5*self.voxel_num_x*self.voxel_size_x @@ -290,16 +293,20 @@ class AcquisitionGeometry(object): else: if len(labels) != len(dim_labels): raise ValueError('Wrong number of labels. Expected {} got {}'.format(len(dim_labels), len(labels))) - order = [] - for i, el in enumerate(labels): - for j, ek in enumerate(dim_labels): - if el == ek: - order.append(j) - break + order = self.get_order_by_label(labels, dim_labels) if order != [0,1,2]: # resort self.shape = tuple([shape[i] for i in order]) self.dimension_labels = labels + + def get_order_by_label(self, dimension_labels, default_dimension_labels): + order = [] + for i, el in enumerate(dimension_labels): + for j, ek in enumerate(default_dimension_labels): + if el == ek: + order.append(j) + break + return order -- cgit v1.2.3 From 4b68c798e24543639994c3105c720527cdd23df8 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 11:45:47 +0100 Subject: Fix Gradient operator axis indexing any axis order ImageData can be passed and the gradient will now operate on the appropriate axis. Uses get_order_by_label from ImageGeometry --- .../optimisation/operators/GradientOperator.py | 40 +++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py b/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py index 6ffaf70..60978be 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py @@ -14,23 +14,47 @@ from ccpi.optimisation.operators import FiniteDiff, SparseFiniteDiff #%% class Gradient(LinearOperator): - + CORRELATION_SPACE = "Space" + CORRELATION_SPACECHANNEL = "SpaceChannels" + # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] + # Grad_order = ['channels', 'direction_y', 'direction_x'] + # Grad_order = ['direction_z', 'direction_y', 'direction_x'] + # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] def __init__(self, gm_domain, bnd_cond = 'Neumann', **kwargs): super(Gradient, self).__init__() self.gm_domain = gm_domain # Domain of Grad Operator - self.correlation = kwargs.get('correlation','Space') + self.correlation = kwargs.get('correlation',Gredient.CORRELATION_SPACE) - if self.correlation=='Space': + if self.correlation==Gredient.CORRELATION_SPACE: if self.gm_domain.channels>1: - self.gm_range = BlockGeometry(*[self.gm_domain for _ in range(self.gm_domain.length-1)] ) - self.ind = numpy.arange(1,self.gm_domain.length) - else: + self.gm_range = BlockGeometry(*[self.gm_domain for _ in range(self.gm_domain.length-1)] ) + if self.gm_domain.length == 4: + # 3D + Channel + # expected Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] + expected_order = [ImageGeometry.CHANNEL, ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] + else: + # 2D + Channel + # expected Grad_order = ['channels', 'direction_y', 'direction_x'] + expected_order = [ImageGeometry.CHANNEL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] + order = self.gm_domain.get_order_by_label(self.gm_domain.dimension_labels, expected_order) + self.ind = order[1:] + #self.ind = numpy.arange(1,self.gm_domain.length) + else: + # no channel info self.gm_range = BlockGeometry(*[self.gm_domain for _ in range(self.gm_domain.length) ] ) - self.ind = numpy.arange(self.gm_domain.length) - elif self.correlation=='SpaceChannels': + if self.gm_domain.length == 3: + # 3D + # expected Grad_order = ['direction_z', 'direction_y', 'direction_x'] + expected_order = [ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] + else: + # 2D + expected_order = [ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] + self.ind = self.gm_domain.get_order_by_label(self.gm_domain.dimension_labels, expected_order) + # self.ind = numpy.arange(self.gm_domain.length) + elif self.correlation==Gredient.CORRELATION_SPACECHANNEL: if self.gm_domain.channels>1: self.gm_range = BlockGeometry(*[self.gm_domain for _ in range(self.gm_domain.length)]) self.ind = range(self.gm_domain.length) -- cgit v1.2.3 From 52b7eff6f554c74d9e7e3f0d38a126bef90ee9f8 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 11:51:05 +0100 Subject: uses Gradient.CORRELATION_SPACE variable, reduce iterations --- Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py index 7966ded..ab4e89c 100644 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py @@ -93,14 +93,14 @@ plt.colorbar() plt.show() # Regularisation Parameter -alpha = 2. +alpha = .1 method = '0' if method == '0': # Create operators - op1 = Gradient(ig, correlation='SpaceChannels') + op1 = Gradient(ig, correlation=Gradient.CORRELATION_SPACE) op2 = Identity(ig, ag) # Create BlockOperator @@ -131,7 +131,7 @@ tau = 1/(sigma*normK**2) pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) pdhg.max_iteration = 10000 pdhg.update_objective_interval = 100 -pdhg.run(1000, verbose=True) +pdhg.run(200, verbose=True) # Show Results plt.figure() -- cgit v1.2.3 From 1acd0548494500968fa5ebb86f9ff8c3f4c1db6d Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 11:52:18 +0100 Subject: add header --- .../PDHG_examples/PDHG_TV_Denoising_Gaussian.py | 34 ++++++++++------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py index ab4e89c..610cb2c 100644 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py @@ -1,22 +1,20 @@ #======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# +# CCP in Tomographic Imaging (CCPi) Core Imaging Library (CIL). + +# Copyright 2017 UKRI-STFC +# Copyright 2017 University of Manchester + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. #========================================================================= """ -- cgit v1.2.3 From 8869843482318cf25739318b91e154f3f46f8795 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 12:00:55 +0100 Subject: fix wrong indentation --- Wrappers/Python/ccpi/framework/framework.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/framework.py b/Wrappers/Python/ccpi/framework/framework.py index a5c9160..4eb37bf 100755 --- a/Wrappers/Python/ccpi/framework/framework.py +++ b/Wrappers/Python/ccpi/framework/framework.py @@ -300,13 +300,13 @@ class AcquisitionGeometry(object): self.dimension_labels = labels def get_order_by_label(self, dimension_labels, default_dimension_labels): - order = [] - for i, el in enumerate(dimension_labels): - for j, ek in enumerate(default_dimension_labels): - if el == ek: - order.append(j) - break - return order + order = [] + for i, el in enumerate(dimension_labels): + for j, ek in enumerate(default_dimension_labels): + if el == ek: + order.append(j) + break + return order -- cgit v1.2.3 From 30e78a10ddec519db3b5bba3c6e54958d8838eee Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 12:07:16 +0100 Subject: fix indentation --- Wrappers/Python/ccpi/framework/framework.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/framework.py b/Wrappers/Python/ccpi/framework/framework.py index 4eb37bf..3840f2c 100755 --- a/Wrappers/Python/ccpi/framework/framework.py +++ b/Wrappers/Python/ccpi/framework/framework.py @@ -299,14 +299,14 @@ class AcquisitionGeometry(object): self.shape = tuple([shape[i] for i in order]) self.dimension_labels = labels - def get_order_by_label(self, dimension_labels, default_dimension_labels): - order = [] - for i, el in enumerate(dimension_labels): - for j, ek in enumerate(default_dimension_labels): - if el == ek: - order.append(j) - break - return order + def get_order_by_label(self, dimension_labels, default_dimension_labels): + order = [] + for i, el in enumerate(dimension_labels): + for j, ek in enumerate(default_dimension_labels): + if el == ek: + order.append(j) + break + return order -- cgit v1.2.3 From 4183664214a30a45ffdc4a2f10b70c72c7fb6cce Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 12:07:46 +0100 Subject: typo fix --- Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py b/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py index 60978be..33dede2 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py @@ -26,9 +26,9 @@ class Gradient(LinearOperator): self.gm_domain = gm_domain # Domain of Grad Operator - self.correlation = kwargs.get('correlation',Gredient.CORRELATION_SPACE) + self.correlation = kwargs.get('correlation',Gradient.CORRELATION_SPACE) - if self.correlation==Gredient.CORRELATION_SPACE: + if self.correlation==Gradient.CORRELATION_SPACE: if self.gm_domain.channels>1: self.gm_range = BlockGeometry(*[self.gm_domain for _ in range(self.gm_domain.length-1)] ) if self.gm_domain.length == 4: @@ -54,7 +54,7 @@ class Gradient(LinearOperator): expected_order = [ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] self.ind = self.gm_domain.get_order_by_label(self.gm_domain.dimension_labels, expected_order) # self.ind = numpy.arange(self.gm_domain.length) - elif self.correlation==Gredient.CORRELATION_SPACECHANNEL: + elif self.correlation==Gradient.CORRELATION_SPACECHANNEL: if self.gm_domain.channels>1: self.gm_range = BlockGeometry(*[self.gm_domain for _ in range(self.gm_domain.length)]) self.ind = range(self.gm_domain.length) -- cgit v1.2.3 From f5befc6b94366ebb966968ca648bfbdce17e37c3 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 12:17:13 +0100 Subject: removed vertical from 2D gradient --- Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py b/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py index 33dede2..6f32845 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py @@ -51,7 +51,7 @@ class Gradient(LinearOperator): expected_order = [ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] else: # 2D - expected_order = [ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] + expected_order = [ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X] self.ind = self.gm_domain.get_order_by_label(self.gm_domain.dimension_labels, expected_order) # self.ind = numpy.arange(self.gm_domain.length) elif self.correlation==Gradient.CORRELATION_SPACECHANNEL: -- cgit v1.2.3 From db6697057dbbc3661fdd627a2c9fc43cbf728cbd Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Fri, 10 May 2019 12:24:29 +0100 Subject: fix methods --- .../ccpi/optimisation/functions/IndicatorBox.py | 119 +++++++++++++++------ 1 file changed, 88 insertions(+), 31 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py b/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py index df8dc89..dd2162c 100755 --- a/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py +++ b/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py @@ -1,21 +1,25 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= -# Copyright 2018-2019 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. from ccpi.optimisation.functions import Function import numpy @@ -43,23 +47,76 @@ class IndicatorBox(Function): val = numpy.inf return val - def prox(self,x,tau=None): - return (x.maximum(self.lower)).minimum(self.upper) + def gradient(self,x): + return ValueError('Not Differentiable') + + def convex_conjugate(self,x): + # support function sup + return 0 def proximal(self, x, tau, out=None): + if out is None: - return self.prox(x, tau) + return (x.maximum(self.lower)).minimum(self.upper) + else: + x.maximum(self.lower, out=out) + out.minimum(self.upper, out=out) + + def proximal_conjugate(self, x, tau, out=None): + + if out is None: + + return x - tau * self.proximal(x/tau, tau) + else: - if not x.shape == out.shape: - raise ValueError('Norm1 Incompatible size:', - x.shape, out.shape) - #(x.abs() - tau*self.gamma).maximum(0) * x.sign() - x.abs(out = out) - out.__isub__(tau*self.gamma) - out.maximum(0, out=out) - if self.sign_x is None or not x.shape == self.sign_x.shape: - self.sign_x = x.sign() - else: - x.sign(out=self.sign_x) + + self.proximal(x/tau, tau, out=out) + out *= -1*tau + out += x + + + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + + N, M = 2,3 + ig = ImageGeometry(voxel_num_x = N, voxel_num_y = M) + + u = ig.allocate('random_int') + tau = 10 + + f = IndicatorBox(2, 3) + + lower = 10 + upper = 30 + + x = u + + z1 = (x.maximum(lower)).minimum(upper) + + z2 = x - tau * ((x/tau).maximum(lower)).minimum(upper) + + z = z1 + z2/tau + + print(z.array, x.array) + + +# prox = f.proximal(u, tau) +# prox_conj = f.proximal_conjugate(u/tau, tau) +# +# +# z = prox + tau * prox_conj +# print(z.as_array(), u.array) + + +# x - tau * ((x/tau).maximum(self.lower)).minimum(self.upper) + + + + + + - out.__imul__( self.sign_x ) + + + -- cgit v1.2.3 From fd3cc5601e9aad0b516ea73a34eef839861ab8f8 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Fri, 10 May 2019 12:25:03 +0100 Subject: still not correct --- .../Python/ccpi/optimisation/functions/KullbackLeibler.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index e298d92..d808e63 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -25,7 +25,7 @@ import functools class KullbackLeibler(Function): - ''' Assume that data > 0 + ''' Assume that data >= 0 ''' @@ -125,7 +125,18 @@ class KullbackLeibler(Function): ''' - return ScaledFunction(self, scalar) + return ScaledFunction(self, scalar) + + +if __name__ == '__main__': + + from ccpi.framework import ImageGeometry + import numpy + + M, N = 2,3 + ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N) + u = ig.allocate('random_int') + b = np.random.normal(0, 0.1, size=ig.shape) -- cgit v1.2.3 From d67eaeedfc48051c38529c18ef2a6cf2e191b711 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Fri, 10 May 2019 12:25:38 +0100 Subject: add contr info --- .../Python/ccpi/optimisation/functions/L1Norm.py | 38 ++++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/L1Norm.py b/Wrappers/Python/ccpi/optimisation/functions/L1Norm.py index 4e53f2c..79040a0 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/L1Norm.py +++ b/Wrappers/Python/ccpi/optimisation/functions/L1Norm.py @@ -1,21 +1,23 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= from ccpi.optimisation.functions import Function -- cgit v1.2.3 From 70a628e4a632d436920577569490df3266f35c2a Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Fri, 10 May 2019 12:34:07 +0100 Subject: demos and check 2D_time denosigin --- Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py | 205 +++++++++++---------- .../demos/PDHG_examples/PDHG_2D_time_denoising.py | 169 +++++++++++++++++ Wrappers/Python/wip/demo_box_constraints_FISTA.py | 2 +- 3 files changed, 276 insertions(+), 100 deletions(-) create mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_2D_time_denoising.py diff --git a/Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py b/Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py index b6d7725..72d0670 100644 --- a/Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py +++ b/Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py @@ -29,7 +29,7 @@ from ccpi.optimisation.algorithms import PDHG from ccpi.optimisation.operators import BlockOperator, Gradient from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ - MixedL21Norm, BlockFunction + MixedL21Norm, BlockFunction, IndicatorBox from ccpi.astra.ops import AstraProjectorSimple @@ -67,10 +67,13 @@ Aop = AstraProjectorSimple(ig, ag, 'cpu') sin = Aop.direct(data) # Create noisy data. Apply Poisson noise -scale = 0.5 -n1 = scale * np.random.poisson(sin.as_array()/scale) +scale = 0.25 +eta = 0 #np.random.randint(0, sin.as_array().max()/2, ag.shape) +n1 = scale * np.random.poisson(eta + sin.as_array()/scale) + noisy_data = AcquisitionData(n1, ag) + # Show Ground Truth and Noisy Data plt.figure(figsize=(10,10)) plt.subplot(2,1,1) @@ -83,9 +86,10 @@ plt.title('Noisy Data') plt.colorbar() plt.show() +#%% # Regularisation Parameter -alpha = 0.5 +alpha = 2 # Create operators op1 = Gradient(ig) @@ -100,20 +104,23 @@ f1 = alpha * MixedL21Norm() f2 = KullbackLeibler(noisy_data) f = BlockFunction(f1, f2) -g = ZeroFunction() +#g = ZeroFunction() +g = IndicatorBox(lower=0) # Compute operator Norm normK = operator.norm() # Primal & dual stepsizes -sigma = 10 +sigma = 2 tau = 1/(sigma*normK**2) +#sigma = 1/normK +#tau = 1/normK # Setup and run the PDHG algorithm pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000) +pdhg.update_objective_interval = 500 +pdhg.run(2000, verbose = True) plt.figure(figsize=(15,15)) plt.subplot(3,1,1) @@ -137,64 +144,11 @@ plt.title('Middle Line Profiles') plt.show() -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff -import astra -import numpy - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - - ##Construct problem - u = Variable(N*N) - #q = Variable() - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - - # create matrix representation for Astra operator - - vol_geom = astra.create_vol_geom(N, N) - proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) - - proj_id = astra.create_projector('strip', proj_geom, vol_geom) - - matrix_id = astra.projector.matrix(proj_id) - - ProjMat = astra.matrix.get(matrix_id) - - tmp = noisy_data.as_array().ravel('F') - -# fidelity = sum( ProjMat * u - tmp * log(ProjMat * u + 1e-6)) - #constraints = [q>= fidelity, u>=0] - constraints = [] - - fidelity = sum(kl_div(tmp, ProjMat * u + 1e-6)) -# fidelity = kl_div(cp.multiply(alpha, W), -# cp.multiply(alpha, W + cp.multiply(beta, P))) - \ -# cp.multiply(alpha, cp.multiply(beta, P)) - - - - solver = SCS - obj = Minimize( regulariser + fidelity) - prob = Problem(obj, constraints) - result = prob.solve(verbose = True, solver = solver) - - -###%% Check with CVX solution +##%% Check with CVX solution # #from ccpi.optimisation.operators import SparseFiniteDiff +#import astra +#import numpy # #try: # from cvxpy import * @@ -204,48 +158,101 @@ if cvx_not_installable: # # #if cvx_not_installable: +# # # ##Construct problem -# u = Variable(ig.shape) +# u = Variable(N*N) +# #q = Variable() # # DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') # DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# # Define Total Variation as a regulariser +# # regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) -# fidelity = pnorm( u - noisy_data.as_array(),1) # -# # choose solver -# if 'MOSEK' in installed_solvers(): -# solver = MOSEK -# else: -# solver = SCS +# # create matrix representation for Astra operator +# +# vol_geom = astra.create_vol_geom(N, N) +# proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) +# +# proj_id = astra.create_projector('strip', proj_geom, vol_geom) +# +# matrix_id = astra.projector.matrix(proj_id) +# +# ProjMat = astra.matrix.get(matrix_id) +# +# tmp = noisy_data.as_array().ravel('F') +# +## fidelity = sum( ProjMat * u - tmp * log(ProjMat * u + 1e-6)) +# #constraints = [q>= fidelity, u>=0] +# constraints = [] +# +# fidelity = sum(kl_div(tmp, ProjMat * u + 1e-6)) +## fidelity = kl_div(cp.multiply(alpha, W), +## cp.multiply(alpha, W + cp.multiply(beta, P))) - \ +## cp.multiply(alpha, cp.multiply(beta, P)) +# +# # +# solver = SCS # obj = Minimize( regulariser + fidelity) -# prob = Problem(obj) -# result = prob.solve(verbose = True, solver = solver) +# prob = Problem(obj, constraints) +# result = prob.solve(verbose = True, solver = solver) +# +# +####%% Check with CVX solution +## +##from ccpi.optimisation.operators import SparseFiniteDiff +## +##try: +## from cvxpy import * +## cvx_not_installable = True +##except ImportError: +## cvx_not_installable = False +## +## +##if cvx_not_installable: +## +## ##Construct problem +## u = Variable(ig.shape) +## +## DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +## DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +## +## # Define Total Variation as a regulariser +## regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) +## fidelity = pnorm( u - noisy_data.as_array(),1) +## +## # choose solver +## if 'MOSEK' in installed_solvers(): +## solver = MOSEK +## else: +## solver = SCS +## +## obj = Minimize( regulariser + fidelity) +## prob = Problem(obj) +## result = prob.solve(verbose = True, solver = solver) +## +## +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(pdhg.get_output().as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# plt.subplot(3,1,2) +# plt.imshow(np.reshape(u.value, (N, N))) +# plt.title('CVX solution') +# plt.colorbar() +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() # -# - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(np.reshape(u.value, (N, N))) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file +# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') +# plt.legend() +# plt.title('Middle Line Profiles') +# plt.show() +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_2D_time_denoising.py b/Wrappers/Python/demos/PDHG_examples/PDHG_2D_time_denoising.py new file mode 100644 index 0000000..045458a --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_2D_time_denoising.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorMC + +import os +import tomophantom +from tomophantom import TomoP2D + +# Create phantom for TV 2D dynamic tomography + +model = 102 # note that the selected model is temporal (2D + time) +N = 50 # set dimension of the phantom +# one can specify an exact path to the parameters file +# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' +path = os.path.dirname(tomophantom.__file__) +path_library2D = os.path.join(path, "Phantom2DLibrary.dat") +#This will generate a N_size x N_size x Time frames phantom (2D + time) +phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) + +plt.close('all') +plt.figure(1) +plt.rcParams.update({'font.size': 21}) +plt.title('{}''{}'.format('2D+t phantom using model no.',model)) +for sl in range(0,np.shape(phantom_2Dt)[0]): + im = phantom_2Dt[sl,:,:] + plt.imshow(im, vmin=0, vmax=1) + plt.pause(.1) + plt.draw + + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) +data = ImageData(phantom_2Dt, geometry=ig) + +detectors = N +angles = np.linspace(0,np.pi,N) + +ag = AcquisitionGeometry('parallel','2D', angles, detectors, channels = np.shape(phantom_2Dt)[0]) +Aop = AstraProjectorMC(ig, ag, 'gpu') +sin = Aop.direct(data) + +scale = 2 +n1 = scale * np.random.poisson(sin.as_array()/scale) +noisy_data = AcquisitionData(n1, ag) + +tindex = [3, 6, 10] + +fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) +plt.subplot(1,3,1) +plt.imshow(noisy_data.as_array()[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) +plt.subplot(1,3,2) +plt.imshow(noisy_data.as_array()[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) +plt.subplot(1,3,3) +plt.imshow(noisy_data.as_array()[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +plt.show() + +#%% +# Regularisation Parameter +alpha = 5 + +# Create operators +#op1 = Gradient(ig) +op1 = Gradient(ig, correlation='SpaceChannels') +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + + +#%% +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) + +plt.subplot(2,3,1) +plt.imshow(phantom_2Dt[tindex[0],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) + +plt.subplot(2,3,2) +plt.imshow(phantom_2Dt[tindex[1],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) + +plt.subplot(2,3,3) +plt.imshow(phantom_2Dt[tindex[2],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + + +plt.subplot(2,3,4) +plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(2,3,5) +plt.imshow(pdhg.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(2,3,6) +plt.imshow(pdhg.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') +im = plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + diff --git a/Wrappers/Python/wip/demo_box_constraints_FISTA.py b/Wrappers/Python/wip/demo_box_constraints_FISTA.py index 2f9e0c6..b15dd45 100644 --- a/Wrappers/Python/wip/demo_box_constraints_FISTA.py +++ b/Wrappers/Python/wip/demo_box_constraints_FISTA.py @@ -72,7 +72,7 @@ else: # Set up Operator object combining the ImageGeometry and AcquisitionGeometry # wrapping calls to ASTRA as well as specifying whether to use CPU or GPU. -Aop = AstraProjectorSimple(ig, ag, 'gpu') +Aop = AstraProjectorSimple(ig, ag, 'cpu') Aop = Identity(ig,ig) -- cgit v1.2.3 From 8d5d2dc372c479347989a16622542378ce6b1eca Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Fri, 10 May 2019 13:13:08 +0100 Subject: check 3D/ 2D time denoising --- .../PDHG_examples/PDHG_TV_Denoising_2D_time.py | 192 +++++++++++++++++++++ .../PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py | 3 + 2 files changed, 195 insertions(+) create mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_2D_time.py diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_2D_time.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_2D_time.py new file mode 100644 index 0000000..14608db --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_2D_time.py @@ -0,0 +1,192 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient, Identity +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorMC + +import os +import tomophantom +from tomophantom import TomoP2D + +# Create phantom for TV 2D dynamic tomography + +model = 102 # note that the selected model is temporal (2D + time) +N = 128 # set dimension of the phantom +# one can specify an exact path to the parameters file +# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' +path = os.path.dirname(tomophantom.__file__) +path_library2D = os.path.join(path, "Phantom2DLibrary.dat") +#This will generate a N_size x N_size x Time frames phantom (2D + time) +phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) + +plt.close('all') +plt.figure(1) +plt.rcParams.update({'font.size': 21}) +plt.title('{}''{}'.format('2D+t phantom using model no.',model)) +for sl in range(0,np.shape(phantom_2Dt)[0]): + im = phantom_2Dt[sl,:,:] + plt.imshow(im, vmin=0, vmax=1) +# plt.pause(.1) +# plt.draw + + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) +data = ImageData(phantom_2Dt, geometry=ig) +ag = ig + +# Create Noisy data. Add Gaussian noise +np.random.seed(10) +noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.25, size=ig.shape) ) + +tindex = [3, 6, 10] + +fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) +plt.subplot(1,3,1) +plt.imshow(noisy_data.as_array()[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) +plt.subplot(1,3,2) +plt.imshow(noisy_data.as_array()[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) +plt.subplot(1,3,3) +plt.imshow(noisy_data.as_array()[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +plt.show() + +#%% +# Regularisation Parameter +alpha = 0.3 + +# Create operators +#op1 = Gradient(ig) +op1 = Gradient(ig, correlation='Space') +op2 = Gradient(ig, correlation='SpaceChannels') + +op3 = Identity(ig, ag) + +# Create BlockOperator +operator1 = BlockOperator(op1, op3, shape=(2,1) ) +operator2 = BlockOperator(op2, op3, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = 0.5 * L2NormSquared(b = noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK1 = operator1.norm() +normK2 = operator2.norm() + +#%% +# Primal & dual stepsizes +sigma1 = 1 +tau1 = 1/(sigma1*normK1**2) + +sigma2 = 1 +tau2 = 1/(sigma2*normK2**2) + +# Setup and run the PDHG algorithm +pdhg1 = PDHG(f=f,g=g,operator=operator1, tau=tau1, sigma=sigma1) +pdhg1.max_iteration = 2000 +pdhg1.update_objective_interval = 200 +pdhg1.run(2000) + +# Setup and run the PDHG algorithm +pdhg2 = PDHG(f=f,g=g,operator=operator2, tau=tau2, sigma=sigma2) +pdhg2.max_iteration = 2000 +pdhg2.update_objective_interval = 200 +pdhg2.run(2000) + + +#%% + +tindex = [3, 6, 10] +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) + +plt.subplot(3,3,1) +plt.imshow(phantom_2Dt[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) + +plt.subplot(3,3,2) +plt.imshow(phantom_2Dt[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) + +plt.subplot(3,3,3) +plt.imshow(phantom_2Dt[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +plt.subplot(3,3,4) +plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(3,3,5) +plt.imshow(pdhg1.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(3,3,6) +plt.imshow(pdhg1.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') + + +plt.subplot(3,3,7) +plt.imshow(pdhg2.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(3,3,8) +plt.imshow(pdhg2.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(3,3,9) +plt.imshow(pdhg2.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') + +#%% +im = plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py index dbf81e2..03dc2ef 100644 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py @@ -67,6 +67,8 @@ path_library3D = os.path.join(path, "Phantom3DLibrary.dat") #This will generate a N x N x N phantom (3D) phantom_tm = TomoP3D.Model(model, N, path_library3D) +#%% + # Create noisy data. Add Gaussian noise ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N, voxel_num_z=N) ag = ig @@ -89,6 +91,7 @@ plt.title('Sagittal View') plt.colorbar() plt.show() +#%% # Regularisation Parameter alpha = 0.05 -- cgit v1.2.3 From dd4a215c7ed2d9013ec3488401ec3219972de5dd Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Fri, 10 May 2019 13:47:39 +0100 Subject: fix methods IndBox --- .../ccpi/optimisation/functions/IndicatorBox.py | 35 +++++++++------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py b/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py index dd2162c..6c18ebf 100755 --- a/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py +++ b/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py @@ -84,39 +84,32 @@ if __name__ == '__main__': ig = ImageGeometry(voxel_num_x = N, voxel_num_y = M) u = ig.allocate('random_int') - tau = 10 + tau = 2 f = IndicatorBox(2, 3) lower = 10 upper = 30 + + z1 = f.proximal(u, tau) - x = u + z2 = f.proximal_conjugate(u/tau, 1/tau) - z1 = (x.maximum(lower)).minimum(upper) + z = z1 + tau * z2 - z2 = x - tau * ((x/tau).maximum(lower)).minimum(upper) + numpy.testing.assert_array_equal(z.as_array(), u.as_array()) + + out1 = ig.allocate() + out2 = ig.allocate() - z = z1 + z2/tau + f.proximal(u, tau, out=out1) + f.proximal_conjugate(u/tau, 1/tau, out = out2) - print(z.array, x.array) + p = out1 + tau * out2 + numpy.testing.assert_array_equal(p.as_array(), u.as_array()) -# prox = f.proximal(u, tau) -# prox_conj = f.proximal_conjugate(u/tau, tau) -# -# -# z = prox + tau * prox_conj -# print(z.as_array(), u.array) -# x - tau * ((x/tau).maximum(self.lower)).minimum(self.upper) + - - - - - - - - + \ No newline at end of file -- cgit v1.2.3 From 604cb98751375bf3b2b7861ed55f52452c1e53ac Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 14:28:03 +0100 Subject: add iterations to norm, added test --- .../ccpi/optimisation/operators/GradientOperator.py | 14 +++++++++++--- Wrappers/Python/test/test_Gradient.py | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py b/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py index 6f32845..d98961b 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py @@ -110,10 +110,11 @@ class Gradient(LinearOperator): def range_geometry(self): return self.gm_range - def norm(self): + def norm(self, **kwargs): x0 = self.gm_domain.allocate('random') - self.s1, sall, svec = LinearOperator.PowerMethod(self, 10, x0) + iterations = kwargs.get('iterations', 10) + self.s1, sall, svec = LinearOperator.PowerMethod(self, iterations, x0) return self.s1 def __rmul__(self, scalar): @@ -160,14 +161,21 @@ if __name__ == '__main__': from ccpi.optimisation.operators import Identity, BlockOperator - M, N = 2, 3 + M, N = 20, 30 ig = ImageGeometry(M, N) arr = ig.allocate('random_int' ) # check direct of Gradient and sparse matrix G = Gradient(ig) + norm1 = G.norm(iterations=300) + print ("should be sqrt(8) {} {}".format(numpy.sqrt(8), norm1)) G_sp = G.matrix() + ig4 = ImageGeometry(M,N, channels=3) + G4 = Gradient(ig4, correlation=Gradient.CORRELATION_SPACECHANNEL) + norm4 = G4.norm(iterations=300) + print ("should be sqrt(12) {} {}".format(numpy.sqrt(12), norm4)) + res1 = G.direct(arr) res1y = numpy.reshape(G_sp[0].toarray().dot(arr.as_array().flatten('F')), ig.shape, 'F') diff --git a/Wrappers/Python/test/test_Gradient.py b/Wrappers/Python/test/test_Gradient.py index c6b2d2e..89f26eb 100755 --- a/Wrappers/Python/test/test_Gradient.py +++ b/Wrappers/Python/test/test_Gradient.py @@ -84,3 +84,18 @@ class TestGradient(unittest.TestCase): res = G2D.direct(u4) print(res[0].as_array()) print(res[1].as_array()) + + M, N = 20, 30 + ig = ImageGeometry(M, N) + arr = ig.allocate('random_int' ) + + # check direct of Gradient and sparse matrix + G = Gradient(ig) + norm1 = G.norm(iterations=300) + print ("should be sqrt(8) {} {}".format(numpy.sqrt(8), norm1)) + numpy.testing.assert_almost_equal(norm1, numpy.sqrt(8), decimal=2) + ig4 = ImageGeometry(M,N, channels=3) + G4 = Gradient(ig4, correlation=Gradient.CORRELATION_SPACECHANNEL) + norm4 = G4.norm(iterations=300) + print ("should be sqrt(12) {} {}".format(numpy.sqrt(12), norm4)) + numpy.testing.assert_almost_equal(norm4, numpy.sqrt(12), decimal=2) -- cgit v1.2.3 From 2612b73ca1d4581364720b4646805cdcc430f3cb Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 15:21:08 +0100 Subject: commented out line --- Wrappers/Python/ccpi/framework/TestData.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/TestData.py b/Wrappers/Python/ccpi/framework/TestData.py index 45ee1d4..83b2ee1 100755 --- a/Wrappers/Python/ccpi/framework/TestData.py +++ b/Wrappers/Python/ccpi/framework/TestData.py @@ -33,7 +33,7 @@ class TestData(object): sdata[round(M/8):round(7*M/8),round(3*M/8):round(5*M/8)] = 1 ig = ImageGeometry(voxel_num_x = N, voxel_num_y = M, dimension_labels=[ImageGeometry.HORIZONTAL_X, ImageGeometry.HORIZONTAL_Y]) data = ig.allocate() - data.geometry = ig + #data.geometry = ig data.fill(sdata) else: tmp = Image.open(os.path.join(self.data_dir, which)) @@ -46,12 +46,12 @@ class TestData(object): dimension_labels=[ImageGeometry.HORIZONTAL_X, ImageGeometry.HORIZONTAL_Y, ImageGeometry.CHANNEL]) data = ig.allocate() - data.geometry = ig + #data.geometry = ig #newsize = (size[0], size[1], len(bands)) else: ig = ImageGeometry(voxel_num_x = size[0], voxel_num_y = size[1], dimension_labels=[ImageGeometry.HORIZONTAL_X, ImageGeometry.HORIZONTAL_Y]) data = ig.allocate() - data.geometry = ig + #data.geometry = ig #newsize = size data.fill(numpy.array(tmp.resize((size[1],size[0])))) -- cgit v1.2.3 From 170d1c659b35ba36eb734511470ead444502fe20 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 15:23:04 +0100 Subject: cleanup --- Wrappers/Python/ccpi/framework/TestData.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/TestData.py b/Wrappers/Python/ccpi/framework/TestData.py index 83b2ee1..752bc13 100755 --- a/Wrappers/Python/ccpi/framework/TestData.py +++ b/Wrappers/Python/ccpi/framework/TestData.py @@ -33,44 +33,30 @@ class TestData(object): sdata[round(M/8):round(7*M/8),round(3*M/8):round(5*M/8)] = 1 ig = ImageGeometry(voxel_num_x = N, voxel_num_y = M, dimension_labels=[ImageGeometry.HORIZONTAL_X, ImageGeometry.HORIZONTAL_Y]) data = ig.allocate() - #data.geometry = ig data.fill(sdata) else: tmp = Image.open(os.path.join(self.data_dir, which)) print (tmp) bands = tmp.getbands() if len(bands) > 1: - # convert to greyscale - #tmp = tmp.convert('L') ig = ImageGeometry(voxel_num_x=size[0], voxel_num_y=size[1], channels=len(bands), dimension_labels=[ImageGeometry.HORIZONTAL_X, ImageGeometry.HORIZONTAL_Y, ImageGeometry.CHANNEL]) - data = ig.allocate() - #data.geometry = ig - #newsize = (size[0], size[1], len(bands)) else: ig = ImageGeometry(voxel_num_x = size[0], voxel_num_y = size[1], dimension_labels=[ImageGeometry.HORIZONTAL_X, ImageGeometry.HORIZONTAL_Y]) data = ig.allocate() - #data.geometry = ig - #newsize = size - data.fill(numpy.array(tmp.resize((size[1],size[0])))) - if scale is not None: dmax = data.as_array().max() dmin = data.as_array().min() - # scale 0,1 data = (data -dmin) / (dmax - dmin) - if scale != (0,1): #data = (data-dmin)/(dmax-dmin) * (scale[1]-scale[0]) +scale[0]) data *= (scale[1]-scale[0]) data += scale[0] - print ("data.geometry", data.geometry) return data - def camera(**kwargs): -- cgit v1.2.3 From c43ec42bb8a140ca4de7b43e9967263fb23099bd Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 15:23:28 +0100 Subject: adjusted decimal --- Wrappers/Python/test/test_Gradient.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Wrappers/Python/test/test_Gradient.py b/Wrappers/Python/test/test_Gradient.py index 89f26eb..6d9d3be 100755 --- a/Wrappers/Python/test/test_Gradient.py +++ b/Wrappers/Python/test/test_Gradient.py @@ -93,9 +93,9 @@ class TestGradient(unittest.TestCase): G = Gradient(ig) norm1 = G.norm(iterations=300) print ("should be sqrt(8) {} {}".format(numpy.sqrt(8), norm1)) - numpy.testing.assert_almost_equal(norm1, numpy.sqrt(8), decimal=2) + numpy.testing.assert_almost_equal(norm1, numpy.sqrt(8), decimal=1) ig4 = ImageGeometry(M,N, channels=3) G4 = Gradient(ig4, correlation=Gradient.CORRELATION_SPACECHANNEL) norm4 = G4.norm(iterations=300) print ("should be sqrt(12) {} {}".format(numpy.sqrt(12), norm4)) - numpy.testing.assert_almost_equal(norm4, numpy.sqrt(12), decimal=2) + numpy.testing.assert_almost_equal(norm4, numpy.sqrt(12), decimal=1) -- cgit v1.2.3 From 24665dc2b0d981aad97aed618070c4ffca0add6c Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 15:23:52 +0100 Subject: added info on how to change image --- .../PDHG_examples/PDHG_TV_Denoising_Gaussian.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py index 610cb2c..cba5bcb 100644 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py @@ -57,13 +57,16 @@ loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) N = 256 M = 300 -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) + +# user can change the size of the input data +# you can choose between +# TestData.PEPPERS 2D + Channel +# TestData.BOAT 2D +# TestData.CAMERA 2D +# TestData.RESOLUTION_CHART 2D +# TestData.SIMPLE_PHANTOM_2D 2D data = loader.load(TestData.PEPPERS, size=(N,M), scale=(0,1)) -#ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -print (data) + ig = data.geometry ag = ig @@ -129,7 +132,7 @@ tau = 1/(sigma*normK**2) pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) pdhg.max_iteration = 10000 pdhg.update_objective_interval = 100 -pdhg.run(200, verbose=True) +pdhg.run(1000, verbose=True) # Show Results plt.figure() @@ -150,9 +153,10 @@ plt.clim(0,1) plt.colorbar() plt.show() +plt.plot(np.linspace(0,N,M), noisy_data.as_array()[int(N/2),:], label = 'Noisy data') plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'GTruth') plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.plot(np.linspace(0,N,M), noisy_data.as_array()[int(N/2),:], label = 'Noisy data') + plt.legend() plt.title('Middle Line Profiles') plt.show() -- cgit v1.2.3 From d68a357f32cd1f42699c63eb99c2bfb89849f54a Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 15:34:59 +0100 Subject: removed comments and empty lines --- .../Python/ccpi/optimisation/functions/KullbackLeibler.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index ceea5ce..2de11ce 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -120,21 +120,7 @@ class KullbackLeibler(Function): tmp += 2 out += tmp out *= 0.5 - -# z_m = x + tau * self.bnoise - 1 -# self.b.multiply(4*tau, out=out) -# z_m.multiply(z_m, out=z_m) -# out += z_m -# out.sqrt(out=out) -# # z = z_m + 2 -# z_m.sqrt(out=z_m) -# z_m += 2 -# out *= -1 -# out += z_m - - - def __rmul__(self, scalar): ''' Multiplication of L2NormSquared with a scalar -- cgit v1.2.3 From cc3efc1f09d167deea1d82d461abd02a49aec357 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 10 May 2019 17:09:09 +0100 Subject: relaxed test accuracy --- Wrappers/Python/test/test_Gradient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/test/test_Gradient.py b/Wrappers/Python/test/test_Gradient.py index 6d9d3be..4b7a034 100755 --- a/Wrappers/Python/test/test_Gradient.py +++ b/Wrappers/Python/test/test_Gradient.py @@ -98,4 +98,4 @@ class TestGradient(unittest.TestCase): G4 = Gradient(ig4, correlation=Gradient.CORRELATION_SPACECHANNEL) norm4 = G4.norm(iterations=300) print ("should be sqrt(12) {} {}".format(numpy.sqrt(12), norm4)) - numpy.testing.assert_almost_equal(norm4, numpy.sqrt(12), decimal=1) + self.assertTrue((norm4 - numpy.sqrt(12))/norm4 < 0.2) -- cgit v1.2.3 From 6c9b8e5bbf571de3c748a8abc1d9c768d5896b67 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 13 May 2019 16:15:32 +0100 Subject: fix method Indi --- .../ccpi/optimisation/functions/IndicatorBox.py | 27 ++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py b/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py index 6c18ebf..ace0ba7 100755 --- a/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py +++ b/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py @@ -22,6 +22,7 @@ from ccpi.optimisation.functions import Function import numpy +from ccpi.framework import ImageData class IndicatorBox(Function): '''Box constraints indicator function. @@ -39,7 +40,7 @@ class IndicatorBox(Function): def __call__(self,x): - + if (numpy.all(x.array>=self.lower) and numpy.all(x.array <= self.upper) ): val = 0 @@ -51,8 +52,9 @@ class IndicatorBox(Function): return ValueError('Not Differentiable') def convex_conjugate(self,x): - # support function sup - return 0 + # support function sup , z \in [lower, upper] + # ???? + return x.maximum(0).sum() def proximal(self, x, tau, out=None): @@ -78,7 +80,7 @@ class IndicatorBox(Function): if __name__ == '__main__': - from ccpi.framework import ImageGeometry + from ccpi.framework import ImageGeometry, BlockDataContainer N, M = 2,3 ig = ImageGeometry(voxel_num_x = N, voxel_num_y = M) @@ -109,6 +111,23 @@ if __name__ == '__main__': numpy.testing.assert_array_equal(p.as_array(), u.as_array()) + d = f.convex_conjugate(u) + print(d) + + + + # what about n-dimensional Block + #uB = BlockDataContainer(u,u,u) + #lowerB = BlockDataContainer(1,2,3) + #upperB = BlockDataContainer(10,21,30) + + #fB = IndicatorBox(lowerB, upperB) + + #z1B = fB.proximal(uB, tau) + + + + -- cgit v1.2.3 From 186d8f44b7abc5d5a4bcadea4ee3f9d08a7bcd33 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 13 May 2019 16:15:55 +0100 Subject: fix call conv_conj --- .../ccpi/optimisation/functions/KullbackLeibler.py | 126 +++++++++++++++++---- 1 file changed, 105 insertions(+), 21 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index 4dd77cf..0d3c8f5 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -20,33 +20,42 @@ import numpy from ccpi.optimisation.functions import Function from ccpi.optimisation.functions.ScaledFunction import ScaledFunction -from ccpi.framework import ImageData, ImageGeometry import functools +import scipy.special class KullbackLeibler(Function): - ''' Assume that data >= 0 + ''' + + KL_div(x, y + back) = int x * log(x/(y+back)) - x + (y+back) + + Assumption: y>=0 + back>=0 ''' - def __init__(self,data, **kwargs): + def __init__(self, data, **kwargs): super(KullbackLeibler, self).__init__() - self.b = data - self.bnoise = kwargs.get('bnoise', 0) - - + self.b = data + self.bnoise = 0 + + def __call__(self, x): - - res_tmp = numpy.zeros(x.shape) - tmp = x + self.bnoise - ind = x.as_array()>0 - res_tmp[ind] = x.as_array()[ind] - self.b.as_array()[ind] * numpy.log(tmp.as_array()[ind]) + ''' - return res_tmp.sum() + x - y * log( x + bnoise) + y * log(y) - y + bnoise + + + ''' + + # TODO avoid scipy import ???? + tmp = scipy.special.kl_div(self.b.as_array(), x.as_array()) + return numpy.sum(tmp) + def log(self, datacontainer): '''calculates the in-place log of the datacontainer''' @@ -58,7 +67,6 @@ class KullbackLeibler(Function): def gradient(self, x, out=None): - #TODO Division check if out is None: return 1 - self.b/(x + self.bnoise) else: @@ -70,12 +78,10 @@ class KullbackLeibler(Function): def convex_conjugate(self, x): - tmp = self.b/(1-x) - ind = tmp.as_array()>0 - - return (self.b.as_array()[ind] * (numpy.log(tmp.as_array()[ind])-1)).sum() - - + # TODO avoid scipy import ???? + xlogy = scipy.special.xlogy(self.b.as_array(), 1 - x.as_array()) + return numpy.sum(-xlogy) + def proximal(self, x, tau, out=None): if out is None: @@ -140,8 +146,86 @@ if __name__ == '__main__': M, N = 2,3 ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N) u = ig.allocate('random_int') - b = np.random.normal(0, 0.1, size=ig.shape) + b = ig.allocate('random_int') + u.as_array()[1,1]=0 + u.as_array()[2,0]=0 + b.as_array()[1,1]=0 + b.as_array()[2,0]=0 + + f = KullbackLeibler(b) + + +# longest = reduce(lambda x, y: len(x) if len(x) > len(y) else len(y), strings) + + +# tmp = functools.reduce(lambda x, y: \ +# 0 if x==0 and not numpy.isnan(y) else x * numpy.log(y), \ +# zip(b.as_array().ravel(), u.as_array().ravel()),0) + + +# np.multiply.reduce(X, 0) + + +# sf = reduce(lambda x,y: x + y[0]*y[1], +# zip(self.as_array().ravel(), +# other.as_array().ravel()), +# 0) +#cdef inline number_t xlogy(number_t x, number_t y) nogil: +# if x == 0 and not zisnan(y): +# return 0 +# else: +# return x * zlog(y) + +# if npy_isnan(x): +# return x +# elif x > 0: +# return -x * log(x) +# elif x == 0: +# return 0 +# else: +# return -inf + +# cdef inline double kl_div(double x, double y) nogil: +# if npy_isnan(x) or npy_isnan(y): +# return nan +# elif x > 0 and y > 0: +# return x * log(x / y) - x + y +# elif x == 0 and y >= 0: +# return y +# else: +# return inf + + + + +# def xlogy(self, dc1, dc2): + +# return numpy.sum(numpy.where(dc1.as_array() != 0, dc2.as_array() * numpy.log(dc2.as_array() / dc1.as_array()), 0)) + + +# f.xlog(u, b) + + + + +# tmp1 = b.as_array() +# tmp2 = u.as_array() +# +# zz = scipy.special.xlogy(tmp1, tmp2) +# +# print(np.sum(zz)) + + +# ww = f.xlogy(b, u) + +# print(ww) + + +#cdef inline double kl_div(double x, double y) nogil: + + + -- cgit v1.2.3 From 159743ca3c40a08fd8c2a858cbd8fd4a30b83728 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 13 May 2019 16:16:48 +0100 Subject: FISTA and PDHG ex --- .../FISTA_Tikhonov_Poisson_Denoising.py | 184 +++++++++++++++++ .../demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py | 218 +++++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py create mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py diff --git a/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py b/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py new file mode 100644 index 0000000..5b1bb16 --- /dev/null +++ b/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py @@ -0,0 +1,184 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Tikhonov for Poisson denoising using FISTA algorithm: + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2} + \int x - g * log(x) + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Poisson Noise + + +""" + +from ccpi.framework import ImageData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import FISTA + +from ccpi.optimisation.operators import Gradient, BlockOperator, Identity +from ccpi.optimisation.functions import KullbackLeibler, IndicatorBox, BlockFunction, \ + L2NormSquared, IndicatorBox, FunctionOperatorComposition + +from ccpi.framework import TestData +import os, sys +from skimage.util import random_noise + +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) + +# Load Data +N = 50 +M = 50 +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) + +ig = data.geometry +ag = ig + +# Create Noisy data. Add Gaussian noise +n1 = random_noise(data.as_array(), mode = 'poisson', seed = 10) +noisy_data = ImageData(n1) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +#%% + +# Regularisation Parameter +alpha = 20 + +# Setup and run the FISTA algorithm +op1 = Gradient(ig) +op2 = BlockOperator(Identity(ig), Identity(ig), shape=(2,1)) + +tmp_function = BlockFunction( KullbackLeibler(noisy_data), IndicatorBox(lower=0) ) + +fid = tmp +reg = FunctionOperatorComposition(alpha * L2NormSquared(), operator) + +x_init = ig.allocate() +opt = {'memopt':True} +fista = FISTA(x_init=x_init , f=reg, g=fid, opt=opt) +fista.max_iteration = 2000 +fista.update_objective_interval = 500 +fista.run(2000, verbose=True) + +#%% +# Show results +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(fista.get_output().as_array()) +plt.title('Reconstruction') +plt.colorbar() +plt.show() + +plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,M), fista.get_output().as_array()[int(N/2),:], label = 'Reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u1 = Variable(ig.shape) + q = Variable() + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum_squares(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) + fidelity = sum(kl_div(noisy_data.as_array(), u1)) + + constraints = [q>=fidelity, u1>=0] + + solver = SCS + obj = Minimize( regulariser + q) + prob = Problem(obj, constraints) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( fista.get_output().as_array() - u1.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(fista.get_output().as_array()) + plt.title('FISTA solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u1.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,M), fista.get_output().as_array()[int(N/2),:], label = 'FISTA') + plt.plot(np.linspace(0,N,M), u1.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (FISTA) {} '.format(fista.objective[-1][0])) + + + + + + + diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py new file mode 100644 index 0000000..830ea00 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py @@ -0,0 +1,218 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +from ccpi.framework import AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient, Identity +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction, IndicatorBox + +from ccpi.astra.ops import AstraProjectorSimple +from ccpi.framework import TestData +import os, sys + +""" + +Total Variation Denoising using PDHG algorithm: + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + int A x -g log(Ax + \eta) + + \nabla: Gradient operator + + A: Projection Matrix + g: Noisy sinogram corrupted with Poisson Noise + + \eta: Background Noise + \alpha: Regularization parameter + +""" + +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) + +# Load Data +N = 50 +M = 50 +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) + +ig = data.geometry +ag = ig + +#Create Acquisition Data and apply poisson noise + +detectors = N +angles = np.linspace(0, np.pi, N) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +scale = 0.5 +eta = 0 +n1 = scale * np.random.poisson(eta + sin.as_array()/scale) + +noisy_data = AcquisitionData(n1, ag) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = 2 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1,op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2) + +g = IndicatorBox(lower=0) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 3000 +pdhg.update_objective_interval = 500 +pdhg.run(3000, verbose = True) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +##%% Check with CVX solution +# +#from ccpi.optimisation.operators import SparseFiniteDiff +#import astra +#import numpy +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +# +#if cvx_not_installable: +# +# +# ##Construct problem +# u = Variable(N*N) +# q = Variable() +# +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) +# +# # create matrix representation for Astra operator +# +# vol_geom = astra.create_vol_geom(N, N) +# proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) +# +# proj_id = astra.create_projector('strip', proj_geom, vol_geom) +# +# matrix_id = astra.projector.matrix(proj_id) +# +# ProjMat = astra.matrix.get(matrix_id) +# +# tmp = noisy_data.as_array().ravel() +# +# fidelity = sum(kl_div(tmp, ProjMat * u)) +# +# constraints = [q>=fidelity, u>=0] +# solver = SCS +# obj = Minimize( regulariser + q) +# prob = Problem(obj, constraints) +# result = prob.solve(verbose = True, solver = solver) +# +# diff_cvx = np.abs(pdhg.get_output().as_array() - np.reshape(u.value, (N, N))) +# +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(pdhg.get_output().as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# plt.subplot(3,1,2) +# plt.imshow(np.reshape(u.value, (N, N))) +# plt.title('CVX solution') +# plt.colorbar() +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() +# +# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N, N))[int(N/2),:], label = 'CVX') +# plt.legend() +# plt.title('Middle Line Profiles') +# plt.show() +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file -- cgit v1.2.3 From 0d409c14b7b4a21835abf812c8e7ef931c8dbace Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 13 May 2019 16:17:19 +0100 Subject: fix order super --- Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py b/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py index 0836324..4178e6c 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py +++ b/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py @@ -20,11 +20,14 @@ class BlockFunction(Function): ''' def __init__(self, *functions): - + + super(BlockFunction, self).__init__() self.functions = functions self.length = len(self.functions) - super(BlockFunction, self).__init__() + self.L = self.functions.L + + def __call__(self, x): -- cgit v1.2.3 From 38dda450572c20ab7fcab23b35f1082ba4f933fc Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 13 May 2019 16:27:23 +0100 Subject: pdhg poisson ex --- .../PDHG_examples/PDHG_TV_Denoising_Gaussian.py | 10 +++++----- .../PDHG_examples/PDHG_TV_Denoising_Poisson.py | 21 ++++++++++----------- .../demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py | 12 ++++++++++-- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py index cba5bcb..9d00ee1 100644 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py @@ -54,7 +54,7 @@ import os, sys loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) # Load Data -N = 256 +N = 200 M = 300 @@ -65,7 +65,7 @@ M = 300 # TestData.CAMERA 2D # TestData.RESOLUTION_CHART 2D # TestData.SIMPLE_PHANTOM_2D 2D -data = loader.load(TestData.PEPPERS, size=(N,M), scale=(0,1)) +data = loader.load(TestData.BOAT, size=(N,M), scale=(0,1)) ig = data.geometry ag = ig @@ -212,9 +212,9 @@ if cvx_not_installable: plt.colorbar() plt.show() - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'Truth') + plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,M), u.value[int(N/2),:], label = 'CVX') + plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'Truth') plt.legend() plt.title('Middle Line Profiles') diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Poisson.py index 4d53635..1d887c1 100644 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Poisson.py +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Poisson.py @@ -49,7 +49,7 @@ import matplotlib.pyplot as plt from ccpi.optimisation.algorithms import PDHG from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, IndicatorBox, \ MixedL21Norm, BlockFunction from skimage.util import random_noise @@ -70,7 +70,7 @@ noisy_data = ImageData(n1) # Show Ground Truth and Noisy Data -plt.figure(figsize=(15,15)) +plt.figure(figsize=(10,10)) plt.subplot(2,1,1) plt.imshow(data.as_array()) plt.title('Ground Truth') @@ -81,7 +81,6 @@ plt.title('Noisy Data') plt.colorbar() plt.show() -#%% # Regularisation Parameter alpha = 2 @@ -101,9 +100,8 @@ if method == '0': f1 = alpha * MixedL21Norm() f2 = KullbackLeibler(noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() + f = BlockFunction(f1, f2) + g = IndicatorBox(lower=0) else: @@ -124,7 +122,7 @@ tau = 1/(sigma*normK**2) pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) pdhg.max_iteration = 3000 pdhg.update_objective_interval = 200 -pdhg.run(3000, verbose=False) +pdhg.run(3000, verbose=True) plt.figure(figsize=(15,15)) @@ -165,16 +163,17 @@ if cvx_not_installable: ##Construct problem u1 = Variable(ig.shape) q = Variable() + w = Variable() DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) + fidelity = sum(kl_div(noisy_data.as_array(), u1)) + + constraints = [q>=fidelity, u1>=0] - fidelity = sum( u1 - multiply(noisy_data.as_array(), log(u1)) ) - constraints = [q>= fidelity, u1>=0] - solver = ECOS obj = Minimize( regulariser + q) prob = Problem(obj, constraints) diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py index 830ea00..8f39f86 100644 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py @@ -27,8 +27,8 @@ import matplotlib.pyplot as plt from ccpi.optimisation.algorithms import PDHG -from ccpi.optimisation.operators import BlockOperator, Gradient, Identity -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import KullbackLeibler, \ MixedL21Norm, BlockFunction, IndicatorBox from ccpi.astra.ops import AstraProjectorSimple @@ -68,6 +68,14 @@ detectors = N angles = np.linspace(0, np.pi, N) ag = AcquisitionGeometry('parallel','2D',angles, detectors) + +device = input('Available device: GPU==1 / CPU==0 ') + +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + Aop = AstraProjectorSimple(ig, ag, 'cpu') sin = Aop.direct(data) -- cgit v1.2.3 From 68d8228799f5c823200340afb67a7bcf4ac34180 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 13 May 2019 16:27:42 +0100 Subject: pdhg poisson ex --- Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py | 120 ++++++++++++++++++-------- 1 file changed, 83 insertions(+), 37 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py b/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py index f17c4fe..63f8955 100644 --- a/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py +++ b/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py @@ -1,21 +1,42 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation Denoising using PDHG algorithm: + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2}^{2} + int A x -g log(Ax + \eta) + + \nabla: Gradient operator + + A: Projection Matrix + g: Noisy sinogram corrupted with Poisson Noise + + \eta: Background Noise + \alpha: Regularization parameter + + + +""" -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData @@ -26,33 +47,59 @@ import matplotlib.pyplot as plt from ccpi.optimisation.algorithms import PDHG from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction +from ccpi.optimisation.functions import IndicatorBox, L2NormSquared, BlockFunction from skimage.util import random_noise from ccpi.astra.ops import AstraProjectorSimple -# Create phantom for TV 2D tomography -N = 75 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) + +# Load Data +N = 100 +M = 100 +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) + +ig = data.geometry +ag = ig -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +#Create Acquisition Data and apply poisson noise detectors = N -angles = np.linspace(0, np.pi, N, dtype=np.float32) +angles = np.linspace(0, np.pi, N) ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'gpu') + +device = input('Available device: GPU==1 / CPU==0 ') + +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + +Aop = AstraProjectorSimple(ig, ag, 'cpu') sin = Aop.direct(data) -# Create noisy data. Apply Gaussian noise +# Create noisy data. Apply Poisson noise +scale = 0.5 +eta = 0 +n1 = scale * np.random.poisson(eta + sin.as_array()/scale) + +noisy_data = AcquisitionData(n1, ag) -np.random.seed(10) -noisy_data = sin + AcquisitionData(np.random.normal(0, 3, sin.shape)) +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() +#%% # Regularisation Parameter -alpha = 500 +alpha = 100 # Create operators op1 = Gradient(ig) @@ -67,7 +114,7 @@ f1 = alpha * L2NormSquared() f2 = 0.5 * L2NormSquared(b=noisy_data) f = BlockFunction(f1, f2) -g = ZeroFunction() +g = IndicatorBox(lower=0) # Compute operator Norm normK = operator.norm() @@ -79,11 +126,10 @@ tau = 1/(sigma*normK**2) # Setup and run the PDHG algorithm pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 5000 -pdhg.update_objective_interval = 50 +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 500 pdhg.run(2000) -#%% plt.figure(figsize=(15,15)) plt.subplot(3,1,1) plt.imshow(data.as_array()) @@ -99,8 +145,8 @@ plt.title('Tikhonov Reconstruction') plt.colorbar() plt.show() ## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') +plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') plt.legend() plt.title('Middle Line Profiles') plt.show() -- cgit v1.2.3 From f1960b9d0f04b834fa069a016ad148140c8fefae Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 13 May 2019 18:31:10 +0100 Subject: add minimum --- Wrappers/Python/ccpi/framework/BlockDataContainer.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Wrappers/Python/ccpi/framework/BlockDataContainer.py b/Wrappers/Python/ccpi/framework/BlockDataContainer.py index 166014b..670e214 100755 --- a/Wrappers/Python/ccpi/framework/BlockDataContainer.py +++ b/Wrappers/Python/ccpi/framework/BlockDataContainer.py @@ -251,6 +251,18 @@ class BlockDataContainer(object): elif isinstance(other, list) or isinstance(other, numpy.ndarray): return type(self)(*[ el.maximum(ot, *args, **kwargs) for el,ot in zip(self.containers,other)], shape=self.shape) return type(self)(*[ el.maximum(ot, *args, **kwargs) for el,ot in zip(self.containers,other.containers)], shape=self.shape) + + + def minimum(self,other, *args, **kwargs): + if not self.is_compatible(other): + raise ValueError('Incompatible for maximum') + out = kwargs.get('out', None) + if isinstance(other, Number): + return type(self)(*[ el.minimum(other, *args, **kwargs) for el in self.containers], shape=self.shape) + elif isinstance(other, list) or isinstance(other, numpy.ndarray): + return type(self)(*[ el.minimum(ot, *args, **kwargs) for el,ot in zip(self.containers,other)], shape=self.shape) + return type(self)(*[ el.minimum(ot, *args, **kwargs) for el,ot in zip(self.containers,other.containers)], shape=self.shape) + ## unary operations def abs(self, *args, **kwargs): -- cgit v1.2.3 From e953157f75cdc7087019688408929052d542eef8 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 13 May 2019 18:31:48 +0100 Subject: delete L --- Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py b/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py index 4178e6c..3765685 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py +++ b/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py @@ -25,7 +25,6 @@ class BlockFunction(Function): self.functions = functions self.length = len(self.functions) - self.L = self.functions.L -- cgit v1.2.3 From 9edcc1d0b5cbefd29952827c2322ebebeb98a0ab Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 13 May 2019 18:32:40 +0100 Subject: try TGV tomo --- .../ccpi/optimisation/functions/IndicatorBox.py | 2 +- Wrappers/Python/demos/PDHG_TGV_Tomo2D.py | 9 +- Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py | 6 +- .../PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py | 2 +- .../demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py | 140 ++++++++++----------- 5 files changed, 82 insertions(+), 77 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py b/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py index ace0ba7..f585c9b 100755 --- a/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py +++ b/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py @@ -60,7 +60,7 @@ class IndicatorBox(Function): if out is None: return (x.maximum(self.lower)).minimum(self.upper) - else: + else: x.maximum(self.lower, out=out) out.minimum(self.upper, out=out) diff --git a/Wrappers/Python/demos/PDHG_TGV_Tomo2D.py b/Wrappers/Python/demos/PDHG_TGV_Tomo2D.py index 49d4db6..19cf684 100644 --- a/Wrappers/Python/demos/PDHG_TGV_Tomo2D.py +++ b/Wrappers/Python/demos/PDHG_TGV_Tomo2D.py @@ -27,7 +27,7 @@ from ccpi.optimisation.algorithms import PDHG from ccpi.optimisation.operators import BlockOperator, Gradient, Identity, \ SymmetrizedGradient, ZeroOperator -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ +from ccpi.optimisation.functions import ZeroFunction, IndicatorBox, KullbackLeibler, \ MixedL21Norm, BlockFunction from ccpi.astra.ops import AstraProjectorSimple @@ -84,13 +84,16 @@ f1 = alpha * MixedL21Norm() f2 = beta * MixedL21Norm() f3 = KullbackLeibler(noisy_data) f = BlockFunction(f1, f2, f3) -g = ZeroFunction() + +g = BlockFunction(-1 * IndicatorBox(lower=0), ZeroFunction()) +#g = IndicatorBox(lower=0) +#g = ZeroFunction() # Compute operator Norm normK = operator.norm() # Primal & dual stepsizes -sigma = 1 +sigma = 10 tau = 1/(sigma*normK**2) diff --git a/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py b/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py index 63f8955..22972da 100644 --- a/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py +++ b/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py @@ -50,6 +50,8 @@ from ccpi.optimisation.operators import BlockOperator, Gradient from ccpi.optimisation.functions import IndicatorBox, L2NormSquared, BlockFunction from skimage.util import random_noise from ccpi.astra.ops import AstraProjectorSimple +from ccpi.framework import TestData +import os, sys loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) @@ -97,9 +99,9 @@ plt.title('Noisy Data') plt.colorbar() plt.show() -#%% + # Regularisation Parameter -alpha = 100 +alpha = 500 # Create operators op1 = Gradient(ig) diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py index 57f6fcd..15c0a05 100644 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py @@ -28,7 +28,7 @@ Problem: min_{x} \alpha * ||\nabla x - w||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} \alpha: Regularization parameter - \alpha: Regularization parameter + \beta: Regularization parameter \nabla: Gradient operator E: Symmetrized Gradient operator diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py index 8f39f86..c44f393 100644 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py @@ -153,74 +153,74 @@ plt.title('Middle Line Profiles') plt.show() -##%% Check with CVX solution +#%% Check with CVX solution # -#from ccpi.optimisation.operators import SparseFiniteDiff -#import astra -#import numpy -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -# -#if cvx_not_installable: -# -# -# ##Construct problem -# u = Variable(N*N) -# q = Variable() -# -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) -# -# # create matrix representation for Astra operator -# -# vol_geom = astra.create_vol_geom(N, N) -# proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) -# -# proj_id = astra.create_projector('strip', proj_geom, vol_geom) -# -# matrix_id = astra.projector.matrix(proj_id) -# -# ProjMat = astra.matrix.get(matrix_id) -# -# tmp = noisy_data.as_array().ravel() -# -# fidelity = sum(kl_div(tmp, ProjMat * u)) -# -# constraints = [q>=fidelity, u>=0] -# solver = SCS -# obj = Minimize( regulariser + q) -# prob = Problem(obj, constraints) -# result = prob.solve(verbose = True, solver = solver) -# -# diff_cvx = np.abs(pdhg.get_output().as_array() - np.reshape(u.value, (N, N))) -# -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(pdhg.get_output().as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# plt.subplot(3,1,2) -# plt.imshow(np.reshape(u.value, (N, N))) -# plt.title('CVX solution') -# plt.colorbar() -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N, N))[int(N/2),:], label = 'CVX') -# plt.legend() -# plt.title('Middle Line Profiles') -# plt.show() -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file +from ccpi.optimisation.operators import SparseFiniteDiff +import astra +import numpy + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + + ##Construct problem + u = Variable(N*N) + q = Variable() + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + + # create matrix representation for Astra operator + + vol_geom = astra.create_vol_geom(N, N) + proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + + proj_id = astra.create_projector('strip', proj_geom, vol_geom) + + matrix_id = astra.projector.matrix(proj_id) + + ProjMat = astra.matrix.get(matrix_id) + + tmp = noisy_data.as_array().ravel() + + fidelity = sum(kl_div(tmp, ProjMat * u)) + + constraints = [q>=fidelity, u>=0] + solver = SCS + obj = Minimize( regulariser + q) + prob = Problem(obj, constraints) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = np.abs(pdhg.get_output().as_array() - np.reshape(u.value, (N, N))) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(np.reshape(u.value, (N, N))) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N, N))[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file -- cgit v1.2.3 From 25778e72b53caf22082ba1a58198185a3c89cdb2 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 14 May 2019 10:17:19 +0100 Subject: delete demos move to PDHG ex dir --- Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py | 12 +- .../demos/PDHG_examples/PDHG_Tikhonov_Tomo2D.py | 156 +++++++++++++++++++++ 2 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Tomo2D.py diff --git a/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py b/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py index 22972da..02cd053 100644 --- a/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py +++ b/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py @@ -47,8 +47,8 @@ import matplotlib.pyplot as plt from ccpi.optimisation.algorithms import PDHG from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import IndicatorBox, L2NormSquared, BlockFunction -from skimage.util import random_noise +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction + from ccpi.astra.ops import AstraProjectorSimple from ccpi.framework import TestData import os, sys @@ -101,7 +101,7 @@ plt.show() # Regularisation Parameter -alpha = 500 +alpha = 1000 # Create operators op1 = Gradient(ig) @@ -115,8 +115,8 @@ operator = BlockOperator(op1, op2, shape=(2,1) ) f1 = alpha * L2NormSquared() f2 = 0.5 * L2NormSquared(b=noisy_data) f = BlockFunction(f1, f2) - -g = IndicatorBox(lower=0) + +g = ZeroFunction() # Compute operator Norm normK = operator.norm() @@ -127,7 +127,7 @@ tau = 1/(sigma*normK**2) # Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) pdhg.max_iteration = 2000 pdhg.update_objective_interval = 500 pdhg.run(2000) diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Tomo2D.py b/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Tomo2D.py new file mode 100644 index 0000000..02cd053 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Tomo2D.py @@ -0,0 +1,156 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation Denoising using PDHG algorithm: + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2}^{2} + int A x -g log(Ax + \eta) + + \nabla: Gradient operator + + A: Projection Matrix + g: Noisy sinogram corrupted with Poisson Noise + + \eta: Background Noise + \alpha: Regularization parameter + + + +""" + + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple +from ccpi.framework import TestData +import os, sys + +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) + +# Load Data +N = 100 +M = 100 +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) + +ig = data.geometry +ag = ig + +#Create Acquisition Data and apply poisson noise + +detectors = N +angles = np.linspace(0, np.pi, N) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) + +device = input('Available device: GPU==1 / CPU==0 ') + +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +scale = 0.5 +eta = 0 +n1 = scale * np.random.poisson(eta + sin.as_array()/scale) + +noisy_data = AcquisitionData(n1, ag) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + + +# Regularisation Parameter +alpha = 1000 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * L2NormSquared() +f2 = 0.5 * L2NormSquared(b=noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 500 +pdhg.run(2000) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('Tikhonov Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + -- cgit v1.2.3 From 1b8c5193938a68548a34b42433ddb97fcaec587a Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 14 May 2019 22:38:34 +0100 Subject: FISTA_ with box cons --- Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py | 219 +++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py diff --git a/Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py b/Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py new file mode 100644 index 0000000..0c12cbb --- /dev/null +++ b/Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py @@ -0,0 +1,219 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +from ccpi.framework import AcquisitionGeometry +from ccpi.optimisation.algorithms import FISTA +from ccpi.optimisation.functions import IndicatorBox, ZeroFunction, L2NormSquared, FunctionOperatorComposition +from ccpi.astra.ops import AstraProjectorSimple + +import numpy as np +import matplotlib.pyplot as plt +from ccpi.framework import TestData +import os, sys + + +# Load Data +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) + +N = 50 +M = 50 +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) + +ig = data.geometry + +# Show Ground Truth +plt.figure(figsize=(5,5)) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.show() + +#%% +# Set up AcquisitionGeometry object to hold the parameters of the measurement +# setup geometry: # Number of angles, the actual angles from 0 to +# pi for parallel beam and 0 to 2pi for fanbeam, set the width of a detector +# pixel relative to an object pixel, the number of detector pixels, and the +# source-origin and origin-detector distance (here the origin-detector distance +# set to 0 to simulate a "virtual detector" with same detector pixel size as +# object pixel size). + +#device = input('Available device: GPU==1 / CPU==0 ') + +#if device=='1': +# dev = 'gpu' +#else: +# dev = 'cpu' + +test_case = 1 +dev = 'cpu' + +if test_case==1: + + detectors = N + angles_num = N + det_w = 1.0 + + angles = np.linspace(0, np.pi, angles_num, endpoint=False) + ag = AcquisitionGeometry('parallel', + '2D', + angles, + detectors,det_w) + +elif test_case==2: + + SourceOrig = 200 + OrigDetec = 0 + angles = np.linspace(0,2*np.pi,angles_num) + ag = AcquisitionGeometry('cone', + '2D', + angles, + detectors, + det_w, + dist_source_center=SourceOrig, + dist_center_detector=OrigDetec) +else: + NotImplemented + +# Set up Operator object combining the ImageGeometry and AcquisitionGeometry +# wrapping calls to ASTRA as well as specifying whether to use CPU or GPU. +Aop = AstraProjectorSimple(ig, ag, dev) + +# Forward and backprojection are available as methods direct and adjoint. Here +# generate test data b and do simple backprojection to obtain z. +sin = Aop.direct(data) +back_proj = Aop.adjoint(sin) + +plt.figure() +plt.imshow(sin.array) +plt.title('Simulated data') +plt.show() + +plt.figure() +plt.imshow(back_proj.array) +plt.title('Backprojected data') +plt.show() + +#%% + +# Using the test data b, different reconstruction methods can now be set up as +# demonstrated in the rest of this file. In general all methods need an initial +# guess and some algorithm options to be set: + +f = FunctionOperatorComposition(L2NormSquared(b=sin), Aop) +g = ZeroFunction() + +x_init = ig.allocate() +opt = {'memopt':True} + +fista = FISTA(x_init=x_init, f=f, g=g, opt=opt) +fista.max_iteration = 100 +fista.update_objective_interval = 1 +fista.run(100, verbose=False) + +plt.figure() +plt.imshow(fista.get_output().as_array()) +plt.title('FISTA unconstrained') +plt.colorbar() +plt.show() + +plt.figure() +plt.semilogy(fista.objective) +plt.title('FISTA objective') +plt.show() + +#%% + +# Run FISTA for least squares with lower bound 0.1 +fista0 = FISTA(x_init=x_init, f=f, g=IndicatorBox(lower=0, upper=1), opt=opt) +fista0.max_iteration = 100 +fista0.update_objective_interval = 1 +fista0.run(100, verbose=False) + +plt.figure() +plt.imshow(fista0.get_output().as_array()) +plt.title('FISTA constrained in [0,1]') +plt.colorbar() +plt.show() + +plt.figure() +plt.semilogy(fista0.objective) +plt.title('FISTA constrained in [0,1]') +plt.show() + +#%% Check with CVX solution + +import astra +import numpy + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + +if cvx_not_installable: + + ##Construct problem + u = Variable(N*N) + + # create matrix representation for Astra operator + vol_geom = astra.create_vol_geom(N, N) + proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + + proj_id = astra.create_projector('strip', proj_geom, vol_geom) + + matrix_id = astra.projector.matrix(proj_id) + + ProjMat = astra.matrix.get(matrix_id) + + tmp = sin.as_array().ravel() + + fidelity = 0.5 * sum_squares(ProjMat * u - tmp) + + solver = MOSEK + obj = Minimize(fidelity) + constraints = [u>=0, u<=1] + prob = Problem(obj, constraints=constraints) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( fista0.get_output().as_array() - np.reshape(u.value, (N,M) )) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(fista0.get_output().as_array()) + plt.title('FISTA solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(np.reshape(u.value, (N, M))) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,M), fista0.get_output().as_array()[int(N/2),:], label = 'FISTA') + plt.plot(np.linspace(0,N,M), np.reshape(u.value, (N,M) )[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + \ No newline at end of file -- cgit v1.2.3 From 75239edf1e0d616caa5f2af0b6779739819ed265 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 14 May 2019 23:49:12 +0100 Subject: minor fix --- .../demos/CompareAlgorithms/CGLS_PDHG_Tikhonov.py | 52 +++++++--------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/Wrappers/Python/demos/CompareAlgorithms/CGLS_PDHG_Tikhonov.py b/Wrappers/Python/demos/CompareAlgorithms/CGLS_PDHG_Tikhonov.py index 942d328..9b6d10f 100644 --- a/Wrappers/Python/demos/CompareAlgorithms/CGLS_PDHG_Tikhonov.py +++ b/Wrappers/Python/demos/CompareAlgorithms/CGLS_PDHG_Tikhonov.py @@ -32,8 +32,7 @@ Problem: min_x alpha * ||\grad x ||^{2}_{2} + || A x - g ||_{2}^{2} """ -from ccpi.framework import ImageData, ImageGeometry, \ - AcquisitionGeometry, BlockDataContainer, AcquisitionData +from ccpi.framework import AcquisitionGeometry, BlockDataContainer, AcquisitionData import numpy as np import numpy @@ -42,28 +41,30 @@ import matplotlib.pyplot as plt from ccpi.optimisation.algorithms import PDHG, CGLS from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, BlockFunction, L2NormSquared +from ccpi.optimisation.functions import ZeroFunction, BlockFunction, L2NormSquared +from ccpi.astra.ops import AstraProjectorSimple +from ccpi.framework import TestData +import os, sys -# Create Ground truth phantom and Sinogram -N = 128 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +# Create Ground truth phantom and Sinogram +N = 150 +M = 150 +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) +ig = data.geometry detectors = N angles = np.linspace(0, np.pi, N, dtype=np.float32) - ag = AcquisitionGeometry('parallel','2D', angles, detectors) + device = input('Available device: GPU==1 / CPU==0 ') -ag = AcquisitionGeometry('parallel','2D', angles, detectors) if device=='1': dev = 'gpu' else: dev = 'cpu' +Aop = AstraProjectorSimple(ig, ag, dev) sin = Aop.direct(data) noisy_data = AcquisitionData( sin.as_array() + np.random.normal(0,3,ig.shape)) @@ -77,7 +78,6 @@ op_CGLS = BlockOperator( Aop, alpha * Grad, shape=(2,1)) block_data = BlockDataContainer(noisy_data, Grad.range_geometry().allocate()) x_init = ig.allocate() - cgls = CGLS(x_init=x_init, operator=op_CGLS, data=block_data) cgls.max_iteration = 1000 cgls.update_objective_interval = 200 @@ -88,7 +88,6 @@ cgls.run(1000,verbose=False) # Create BlockOperator op_PDHG = BlockOperator(Grad, Aop, shape=(2,1) ) - # Create functions f1 = 0.5 * alpha**2 * L2NormSquared() f2 = 0.5 * L2NormSquared(b = noisy_data) @@ -102,13 +101,11 @@ normK = op_PDHG.norm() sigma = 10 tau = 1/(sigma*normK**2) -pdhg = PDHG(f=f,g=g,operator=op_PDHG, tau=tau, sigma=sigma, memopt=True) +pdhg = PDHG(f=f,g=g,operator=op_PDHG, tau=tau, sigma=sigma) pdhg.max_iteration = 1000 pdhg.update_objective_interval = 200 pdhg.run(1000, verbose=False) - -#%% # Show results plt.figure(figsize=(10,10)) @@ -129,25 +126,8 @@ plt.title('Diff PDHG vs CGLS') plt.colorbar() plt.show() -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') -plt.plot(np.linspace(0,N,N), cgls.get_output().as_array()[int(N/2),:], label = 'CGLS') +plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') +plt.plot(np.linspace(0,N,M), cgls.get_output().as_array()[int(N/2),:], label = 'CGLS') plt.legend() plt.title('Middle Line Profiles') plt.show() - - - - - - - - - -# -# -# -# -# -# -# -# -- cgit v1.2.3 From 9a37cf60477f938b1e98baaa085b833dec8838f6 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 14 May 2019 23:56:30 +0100 Subject: demo CGLS --- .../Python/demos/CGLS_examples/CGLS_Tikhonov.py | 106 +++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 Wrappers/Python/demos/CGLS_examples/CGLS_Tikhonov.py diff --git a/Wrappers/Python/demos/CGLS_examples/CGLS_Tikhonov.py b/Wrappers/Python/demos/CGLS_examples/CGLS_Tikhonov.py new file mode 100644 index 0000000..d1cbe20 --- /dev/null +++ b/Wrappers/Python/demos/CGLS_examples/CGLS_Tikhonov.py @@ -0,0 +1,106 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" +Compare solutions of PDHG & "Block CGLS" algorithms for + + +Problem: min_x alpha * ||\grad x ||^{2}_{2} + || A x - g ||_{2}^{2} + + + A: Projection operator + g: Sinogram + +""" + + +from ccpi.framework import AcquisitionGeometry, BlockDataContainer, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import CGLS +from ccpi.optimisation.operators import BlockOperator, Gradient + +from ccpi.framework import TestData +import os, sys +from ccpi.astra.ops import AstraProjectorSimple + +# Load Data +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) +N = 150 +M = 150 +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) + +ig = data.geometry + +detectors = N +angles = np.linspace(0, np.pi, N, dtype=np.float32) + +ag = AcquisitionGeometry('parallel','2D', angles, detectors) + +device = input('Available device: GPU==1 / CPU==0 ') +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + +Aop = AstraProjectorSimple(ig, ag, dev) +sin = Aop.direct(data) + +noisy_data = AcquisitionData( sin.as_array() + np.random.normal(0,3,ig.shape)) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Setup and run the CGLS algorithm +alpha = 50 +Grad = Gradient(ig) + +# Form Tikhonov as a Block CGLS structure +op_CGLS = BlockOperator( Aop, alpha * Grad, shape=(2,1)) +block_data = BlockDataContainer(noisy_data, Grad.range_geometry().allocate()) + +x_init = ig.allocate() +cgls = CGLS(x_init=x_init, operator=op_CGLS, data=block_data) +cgls.max_iteration = 1000 +cgls.update_objective_interval = 200 +cgls.run(1000,verbose=False) + +#%% +# Show results +plt.figure(figsize=(5,5)) +plt.imshow(cgls.get_output().as_array()) +plt.title('CGLS reconstruction') +plt.colorbar() +plt.show() + + -- cgit v1.2.3 From abb4ce7d7aea5e88c442891da756a32e80ccb9b0 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 14 May 2019 23:57:11 +0100 Subject: fix space --- Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py b/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py index f585c9b..7fec65e 100755 --- a/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py +++ b/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py @@ -55,7 +55,7 @@ class IndicatorBox(Function): # support function sup , z \in [lower, upper] # ???? return x.maximum(0).sum() - + def proximal(self, x, tau, out=None): if out is None: -- cgit v1.2.3 From ee580a5a8c852958c7ce28d935806c37bca12d44 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 15 May 2019 00:43:41 +0100 Subject: check FISTA, constr, regul --- .../FISTA_Tikhonov_Poisson_Denoising.py | 53 +++++++++++++++------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py b/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py index 5b1bb16..be397ff 100644 --- a/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py +++ b/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py @@ -23,7 +23,7 @@ Tikhonov for Poisson denoising using FISTA algorithm: -Problem: min_x, x>0 \alpha * ||\nabla x||_{2} + \int x - g * log(x) +Problem: min_x, x>0 \alpha * ||\nabla x||_{2}^{2} + \int x - g * log(x) \alpha: Regularization parameter @@ -42,9 +42,8 @@ import matplotlib.pyplot as plt from ccpi.optimisation.algorithms import FISTA -from ccpi.optimisation.operators import Gradient, BlockOperator, Identity -from ccpi.optimisation.functions import KullbackLeibler, IndicatorBox, BlockFunction, \ - L2NormSquared, IndicatorBox, FunctionOperatorComposition +from ccpi.optimisation.operators import Gradient +from ccpi.optimisation.functions import KullbackLeibler, L2NormSquared, FunctionOperatorComposition from ccpi.framework import TestData import os, sys @@ -75,19 +74,42 @@ plt.imshow(noisy_data.as_array()) plt.title('Noisy Data') plt.colorbar() plt.show() - #%% - # Regularisation Parameter -alpha = 20 +alpha = 10 # Setup and run the FISTA algorithm -op1 = Gradient(ig) -op2 = BlockOperator(Identity(ig), Identity(ig), shape=(2,1)) +operator = Gradient(ig) +fid = KullbackLeibler(noisy_data) -tmp_function = BlockFunction( KullbackLeibler(noisy_data), IndicatorBox(lower=0) ) +def KL_Prox_PosCone(x, tau, out=None): + + if out is None: + tmp = 0.5 *( (x - fid.bnoise - tau) + ( (x + fid.bnoise - tau)**2 + 4*tau*fid.b ) .sqrt() ) + return tmp.maximum(0) + else: + tmp = 0.5 *( (x - fid.bnoise - tau) + + ( (x + fid.bnoise - tau)**2 + 4*tau*fid.b ) .sqrt() + ) + x.add(fid.bnoise, out=out) + out -= tau + out *= out + tmp = fid.b * (4 * tau) + out.add(tmp, out=out) + out.sqrt(out=out) + + x.subtract(fid.bnoise, out=tmp) + tmp -= tau + + out += tmp + + out *= 0.5 + + # ADD the constraint here + out.maximum(0, out=out) + +fid.proximal = KL_Prox_PosCone -fid = tmp reg = FunctionOperatorComposition(alpha * L2NormSquared(), operator) x_init = ig.allocate() @@ -97,7 +119,6 @@ fista.max_iteration = 2000 fista.update_objective_interval = 500 fista.run(2000, verbose=True) -#%% # Show results plt.figure(figsize=(15,15)) plt.subplot(3,1,1) @@ -120,6 +141,7 @@ plt.legend() plt.title('Middle Line Profiles') plt.show() + #%% Check with CVX solution from ccpi.optimisation.operators import SparseFiniteDiff @@ -173,12 +195,9 @@ if cvx_not_installable: plt.title('Middle Line Profiles') plt.show() + #TODO what is the output of fista.objective, fista.loss print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (FISTA) {} '.format(fista.objective[-1][0])) - - - - + print('Primal Objective (FISTA) {} '.format(fista.loss[1])) -- cgit v1.2.3 From 4432e6f12fc46deaec8b128ce0abbf8f49dbc553 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 15 May 2019 00:44:19 +0100 Subject: check FISTA, constr, regul --- .../Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py b/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py index be397ff..41219f4 100644 --- a/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py +++ b/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py @@ -52,8 +52,8 @@ from skimage.util import random_noise loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) # Load Data -N = 50 -M = 50 +N = 150 +M = 150 data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) ig = data.geometry @@ -74,6 +74,7 @@ plt.imshow(noisy_data.as_array()) plt.title('Noisy Data') plt.colorbar() plt.show() + #%% # Regularisation Parameter alpha = 10 -- cgit v1.2.3 From 42b296291263c081603b828faa45da6f860f3b7b Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Wed, 22 May 2019 11:58:10 +0100 Subject: Added denoising demos which contain all noises and data selection --- .../demos/PDHG_examples/PDHG_TGV_Denoising.py | 291 +++++++++++++++++++++ .../demos/PDHG_examples/PDHG_TV_Denoising.py | 266 +++++++++++++++++++ .../demos/PDHG_examples/PDHG_Tikhonov_Denoising.py | 96 +++++-- 3 files changed, 628 insertions(+), 25 deletions(-) create mode 100755 Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising.py create mode 100755 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising.py diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising.py new file mode 100755 index 0000000..761c025 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising.py @@ -0,0 +1,291 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= +""" + +Total Generalised Variation (TGV) Denoising using PDHG algorithm: + + +Problem: min_{x} \alpha * ||\nabla x - w||_{2,1} + + \beta * || E w ||_{2,1} + + fidelity + + where fidelity can be as follows depending on the noise characteristics + of the data: + * Norm2Squared \frac{1}{2} * || x - g ||_{2}^{2} + * KullbackLeibler + * L1Norm + + \alpha: Regularization parameter + \beta: Regularization parameter + + \nabla: Gradient operator + E: Symmetrized Gradient operator + + g: Noisy Data with Salt & Pepper Noise + + Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity + ZeroOperator, E + Identity, ZeroOperator] + + + Method = 1 (PDHG - explicit ): K = [ \nabla, - Identity + ZeroOperator, E ] + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, \ + Gradient, SymmetrizedGradient, ZeroOperator +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, BlockFunction, KullbackLeibler, L2NormSquared +import sys +if int(numpy.version.version.split('.')[1]) > 12: + from skimage.util import random_noise +else: + from demoutil import random_noise + + + +# user supplied input +if len(sys.argv) > 1: + which_noise = int(sys.argv[1]) +else: + which_noise = 0 +print ("Applying {} noise") + +if len(sys.argv) > 2: + method = sys.argv[2] +else: + method = '1' +print ("method ", method) +# Create phantom for TGV SaltPepper denoising + +N = 100 + +x1 = np.linspace(0, int(N/2), N) +x2 = np.linspace(int(N/2), 0., N) +xv, yv = np.meshgrid(x1, x2) + +xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T + +#data = xv + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +data = ig.allocate() +data.fill(xv/xv.max()) +ag = ig + +# Create noisy data. +# Apply Salt & Pepper noise +# gaussian +# poisson +noises = ['gaussian', 'poisson', 's&p'] +noise = noises[which_noise] +if noise == 's&p': + n1 = random_noise(data.as_array(), mode = noise, salt_vs_pepper = 0.9, amount=0.2, seed=10) +elif noise == 'poisson': + n1 = random_noise(data.as_array(), mode = noise, seed = 10) +elif noise == 'gaussian': + n1 = random_noise(data.as_array(), mode = noise, seed = 10) +else: + raise ValueError('Unsupported Noise ', noise) +noisy_data = ImageData(n1) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,5)) +plt.subplot(1,2,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(1,2,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameters +if noise == 's&p': + alpha = 0.8 +elif noise == 'poisson': + alpha = .1 +elif noise == 'gaussian': + alpha = .3 + +beta = numpy.sqrt(2)* alpha + +# fidelity +if noise == 's&p': + f3 = L1Norm(b=noisy_data) +elif noise == 'poisson': + f3 = KullbackLeibler(noisy_data) +elif noise == 'gaussian': + f3 = L2NormSquared(b=noisy_data) + +if method == '0': + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + op31 = Identity(ig, ag) + op32 = ZeroOperator(op22.domain_geometry(), ag) + + operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + # f3 depends on the noise characteristics + + f = BlockFunction(f1, f2, f3) + g = ZeroFunction() + +else: + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + operator = BlockOperator(op11, -1*op12, op21, op22, shape=(2,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + + f = BlockFunction(f1, f2) + g = BlockFunction(f3, ZeroFunction()) + +## Compute operator Norm +normK = operator.norm() +# +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000, verbose = True) + +#%% +plt.figure(figsize=(20,5)) +plt.subplot(1,4,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(1,4,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(1,4,3) +plt.imshow(pdhg.get_output()[0].as_array()) +plt.title('TGV Reconstruction') +plt.colorbar() +plt.subplot(1,4,4) +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TGV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + +if cvx_not_installable: + + u = Variable(ig.shape) + w1 = Variable((N, N)) + w2 = Variable((N, N)) + + # create TGV regulariser + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ + DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ + beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) + + constraints = [] + fidelity = pnorm(u - noisy_data.as_array(),1) + solver = MOSEK + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output()[0].as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output()[0].as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising.py new file mode 100755 index 0000000..0f1effa --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising.py @@ -0,0 +1,266 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation Denoising using PDHG algorithm: + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + ||x-g||_{1} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Salt & Pepper Noise + + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, BlockFunction, L2NormSquared,\ + KullbackLeibler +from ccpi.framework import TestData +import os, sys +if int(numpy.version.version.split('.')[1]) > 12: + from skimage.util import random_noise +else: + from demoutil import random_noise + +# user supplied input +if len(sys.argv) > 1: + which_noise = int(sys.argv[1]) +else: + which_noise = 0 +print ("Applying {} noise") + +if len(sys.argv) > 2: + method = sys.argv[2] +else: + method = '0' +print ("method ", method) +# Create phantom for TV Salt & Pepper denoising +N = 100 + +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,N)) +data = loader.load(TestData.PEPPERS, size=(N,N)) +ig = data.geometry +ag = ig + +# Create noisy data. +# Apply Salt & Pepper noise +# gaussian +# poisson +noises = ['gaussian', 'poisson', 's&p'] +noise = noises[which_noise] +if noise == 's&p': + n1 = random_noise(data.as_array(), mode = noise, salt_vs_pepper = 0.9, amount=0.2) +elif noise == 'poisson': + n1 = random_noise(data.as_array(), mode = noise, seed = 10) +elif noise == 'gaussian': + n1 = random_noise(data.as_array(), mode = noise, seed = 10) +else: + raise ValueError('Unsupported Noise ', noise) +noisy_data = ig.allocate() +noisy_data.fill(n1) +#noisy_data = ImageData(n1) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,5)) +plt.subplot(1,2,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(1,2,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = .2 + +# fidelity +if noise == 's&p': + f2 = L1Norm(b=noisy_data) +elif noise == 'poisson': + f2 = KullbackLeibler(noisy_data) +elif noise == 'gaussian': + f2 = L2NormSquared(b=noisy_data) + +if method == '0': + + # Create operators + op1 = Gradient(ig, correlation=Gradient.CORRELATION_SPACECHANNEL) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + f1 = alpha * MixedL21Norm() + #f2 = L1Norm(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + #g = L1Norm(b = noisy_data) + g = f2 + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +opt = {'niter':2000, 'memopt': True} + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + +if data.geometry.channels > 1: + plt.figure(figsize=(20,15)) + for row in range(data.geometry.channels): + + plt.subplot(3,4,1+row*4) + plt.imshow(data.subset(channel=row).as_array()) + plt.title('Ground Truth') + plt.colorbar() + plt.subplot(3,4,2+row*4) + plt.imshow(noisy_data.subset(channel=row).as_array()) + plt.title('Noisy Data') + plt.colorbar() + plt.subplot(3,4,3+row*4) + plt.imshow(pdhg.get_output().subset(channel=row).as_array()) + plt.title('TV Reconstruction') + plt.colorbar() + plt.subplot(3,4,4+row*4) + plt.plot(np.linspace(0,N,N), data.subset(channel=row).as_array()[int(N/2),:], label = 'GTruth') + plt.plot(np.linspace(0,N,N), pdhg.get_output().subset(channel=row).as_array()[int(N/2),:], label = 'TV reconstruction') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() +else: + plt.figure(figsize=(20,5)) + plt.subplot(1,4,1) + plt.imshow(data.subset(channel=0).as_array()) + plt.title('Ground Truth') + plt.colorbar() + plt.subplot(1,4,2) + plt.imshow(noisy_data.subset(channel=0).as_array()) + plt.title('Noisy Data') + plt.colorbar() + plt.subplot(1,4,3) + plt.imshow(pdhg.get_output().subset(channel=0).as_array()) + plt.title('TV Reconstruction') + plt.colorbar() + plt.subplot(1,4,4) + plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = pnorm( u - noisy_data.as_array(),1) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Denoising.py b/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Denoising.py index 7b73c1a..f00f1cc 100644 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Denoising.py +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Denoising.py @@ -39,7 +39,7 @@ Problem: min_{x} \alpha * ||\nabla x||_{2}^{2} + \frac{1}{2} * || x - g ||_{ """ -from ccpi.framework import ImageData, ImageGeometry +from ccpi.framework import ImageData, ImageGeometry, TestData import numpy as np import numpy @@ -48,40 +48,83 @@ import matplotlib.pyplot as plt from ccpi.optimisation.algorithms import PDHG from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared,\ + BlockFunction, KullbackLeibler, L1Norm -from skimage.util import random_noise +import sys, os +if int(numpy.version.version.split('.')[1]) > 12: + from skimage.util import random_noise +else: + from demoutil import random_noise + + +# user supplied input +if len(sys.argv) > 1: + which_noise = int(sys.argv[1]) +else: + which_noise = 0 +print ("Applying {} noise") + +if len(sys.argv) > 2: + method = sys.argv[2] +else: + method = '0' +print ("method ", method) # Create phantom for TV Salt & Pepper denoising N = 100 -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,N)) +ig = data.geometry ag = ig # Create noisy data. Apply Salt & Pepper noise -n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +# Create noisy data. +# Apply Salt & Pepper noise +# gaussian +# poisson +noises = ['gaussian', 'poisson', 's&p'] +noise = noises[which_noise] +if noise == 's&p': + n1 = random_noise(data.as_array(), mode = noise, salt_vs_pepper = 0.9, amount=0.2) +elif noise == 'poisson': + n1 = random_noise(data.as_array(), mode = noise, seed = 10) +elif noise == 'gaussian': + n1 = random_noise(data.as_array(), mode = noise, seed = 10) +else: + raise ValueError('Unsupported Noise ', noise) noisy_data = ImageData(n1) +# fidelity +if noise == 's&p': + f2 = L1Norm(b=noisy_data) +elif noise == 'poisson': + f2 = KullbackLeibler(noisy_data) +elif noise == 'gaussian': + f2 = 0.5 * L2NormSquared(b=noisy_data) + # Show Ground Truth and Noisy Data -plt.figure(figsize=(15,15)) -plt.subplot(2,1,1) +plt.figure(figsize=(10,5)) +plt.subplot(1,2,1) plt.imshow(data.as_array()) plt.title('Ground Truth') plt.colorbar() -plt.subplot(2,1,2) +plt.subplot(1,2,2) plt.imshow(noisy_data.as_array()) plt.title('Noisy Data') plt.colorbar() plt.show() -# Regularisation Parameter -alpha = 4 -method = '1' +# Regularisation Parameter +# no edge preservation alpha is big +if noise == 's&p': + alpha = 8. +elif noise == 'poisson': + alpha = 8. +elif noise == 'gaussian': + alpha = 8. if method == '0': @@ -95,7 +138,8 @@ if method == '0': # Create functions f1 = alpha * L2NormSquared() - f2 = 0.5 * L2NormSquared(b = noisy_data) + # f2 must change according to noise + #f2 = 0.5 * L2NormSquared(b = noisy_data) f = BlockFunction(f1, f2) g = ZeroFunction() @@ -104,7 +148,9 @@ else: # Without the "Block Framework" operator = Gradient(ig) f = alpha * L2NormSquared() - g = 0.5 * L2NormSquared(b = noisy_data) + # g must change according to noise + #g = 0.5 * L2NormSquared(b = noisy_data) + g = f2 # Compute operator Norm @@ -119,31 +165,31 @@ opt = {'niter':2000, 'memopt': True} pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) pdhg.max_iteration = 2000 pdhg.update_objective_interval = 50 -pdhg.run(2000) +pdhg.run(1500) -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) +plt.figure(figsize=(20,5)) +plt.subplot(1,4,1) plt.imshow(data.as_array()) plt.title('Ground Truth') plt.colorbar() -plt.subplot(3,1,2) +plt.subplot(1,4,2) plt.imshow(noisy_data.as_array()) plt.title('Noisy Data') plt.colorbar() -plt.subplot(3,1,3) +plt.subplot(1,4,3) plt.imshow(pdhg.get_output().as_array()) plt.title('Tikhonov Reconstruction') plt.colorbar() -plt.show() -## +plt.subplot(1,4,4) plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') plt.legend() plt.title('Middle Line Profiles') plt.show() + ##%% Check with CVX solution from ccpi.optimisation.operators import SparseFiniteDiff -- cgit v1.2.3 From fab42230c06ea7b213042e0dea358a2f6a0dcd48 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 23 May 2019 17:18:42 +0100 Subject: check accuracy --- .../functions/FunctionOperatorComposition.py | 64 ++++++++++++++-------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py b/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py index 8895f3d..e788d5a 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py +++ b/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py @@ -50,41 +50,59 @@ class FunctionOperatorComposition(Function): self.operator.adjoint(tmp, out=out) - - - #TODO do not know if we need it - #def call_adjoint(self, x): - # - # return self.function(self.operator.adjoint(x)) - - - #def convex_conjugate(self, x): - # - # ''' convex_conjugate does not take into account the Operator''' - # return self.function.convex_conjugate(x) - - - if __name__ == '__main__': - from ccpi.framework import ImageGeometry + from ccpi.framework import ImageGeometry, AcquisitionGeometry from ccpi.optimisation.operators import Gradient from ccpi.optimisation.functions import L2NormSquared + from ccpi.astra.ops import AstraProjectorSimple + import numpy as np - M, N, K = 2,3 + M, N= 50, 50 ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N) - G = Gradient(ig) + detectors = N + angles_num = N + det_w = 1.0 + + angles = np.linspace(0, np.pi, angles_num, endpoint=False) + ag = AcquisitionGeometry('parallel', + '2D', + angles, + detectors,det_w) + + + Aop = AstraProjectorSimple(ig, ag, 'cpu') + + u = ig.allocate('random_int') + u1 = ig.allocate('random_int') + b = Aop.direct(u1) + + +# G = Gradient(ig) alpha = 0.5 - f = L2NormSquared() - f_comp = FunctionOperatorComposition(G, alpha * f) - x = ig.allocate('random_int') - print(f_comp.gradient(x).shape + f1 = alpha * L2NormSquared(b=b) + + f_comp = FunctionOperatorComposition(f1, Aop) + + print(f_comp(u)) + + + z1 = Aop.direct(u) + tmp = 0.5 * ((z1 - b)**2).sum() + + + print(tmp) + + + + + + - ) -- cgit v1.2.3 From ee29c1319d61d6810a14bf53f940c236933480da Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 23 May 2019 17:26:56 +0100 Subject: check accuracy --- .../Python/ccpi/optimisation/functions/FunctionOperatorComposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py b/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py index e788d5a..741aef9 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py +++ b/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py @@ -59,7 +59,7 @@ if __name__ == '__main__': from ccpi.optimisation.functions import L2NormSquared from ccpi.astra.ops import AstraProjectorSimple import numpy as np - + M, N= 50, 50 ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N) -- cgit v1.2.3 From 20f3490926d4f7a98c747d7f2093cec04e8d6005 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 23 May 2019 17:30:18 +0100 Subject: check accuracy, add seed --- .../Python/ccpi/optimisation/functions/FunctionOperatorComposition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py b/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py index 741aef9..a2445cd 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py +++ b/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py @@ -76,8 +76,8 @@ if __name__ == '__main__': Aop = AstraProjectorSimple(ig, ag, 'cpu') - u = ig.allocate('random_int') - u1 = ig.allocate('random_int') + u = ig.allocate('random_int', seed=15) + u1 = ig.allocate('random_int', seed=10) b = Aop.direct(u1) -- cgit v1.2.3 From 2c0744b55d69e3755d1d99ea164b5eaedb382b6d Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 23 May 2019 17:39:28 +0100 Subject: delete cvx sol --- .../demos/PDHG_examples/PDHG_TGV_Denoising.py | 74 +--------------------- 1 file changed, 2 insertions(+), 72 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising.py index 761c025..4d6da00 100755 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising.py +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising.py @@ -30,8 +30,8 @@ Problem: min_{x} \alpha * ||\nabla x - w||_{2,1} + where fidelity can be as follows depending on the noise characteristics of the data: * Norm2Squared \frac{1}{2} * || x - g ||_{2}^{2} - * KullbackLeibler - * L1Norm + * KullbackLeibler \int u - g * log(u) + Id_{u>0} + * L1Norm ||u - g||_{1} \alpha: Regularization parameter \beta: Regularization parameter @@ -219,73 +219,3 @@ plt.title('Middle Line Profiles') plt.show() -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - -if cvx_not_installable: - - u = Variable(ig.shape) - w1 = Variable((N, N)) - w2 = Variable((N, N)) - - # create TGV regulariser - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ - DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ - beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ - 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ - 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) - - constraints = [] - fidelity = pnorm(u - noisy_data.as_array(),1) - solver = MOSEK - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output()[0].as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output()[0].as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - -- cgit v1.2.3 From bd48e492158c29e8d4158655fdd0fe1e50a0b87f Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 23 May 2019 17:53:58 +0100 Subject: reorder demos --- .../PDHG_examples/GatherAll/PDHG_TGV_Denoising.py | 221 +++++++++++++++++ .../PDHG_examples/GatherAll/PDHG_TV_Denoising.py | 266 +++++++++++++++++++++ .../GatherAll/PDHG_Tikhonov_Denoising.py | 256 ++++++++++++++++++++ .../MultiChannel/PDHG_2D_time_denoising.py | 169 +++++++++++++ .../MultiChannel/PDHG_TV_Tomo2D_time.py | 169 +++++++++++++ .../TGV_Denoising/PDHG_TGV_Denoising_SaltPepper.py | 245 +++++++++++++++++++ .../demos/PDHG_examples/TV_Denoising/.DS_Store | Bin 0 -> 6148 bytes .../TV_Denoising/PDHG_TV_Denoising_2D_time.py | 192 +++++++++++++++ .../TV_Denoising/PDHG_TV_Denoising_Gaussian.py | 225 +++++++++++++++++ .../TV_Denoising/PDHG_TV_Denoising_Gaussian_3D.py | 181 ++++++++++++++ .../TV_Denoising/PDHG_TV_Denoising_Poisson.py | 212 ++++++++++++++++ .../TV_Denoising/PDHG_TV_Denoising_SaltPepper.py | 213 +++++++++++++++++ .../demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py | 189 +++++++++++++++ .../PDHG_examples/Tomo/PDHG_TV_Tomo2D_gaussian.py | 212 ++++++++++++++++ .../PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py | 228 ++++++++++++++++++ .../PDHG_examples/Tomo/PDHG_Tikhonov_Tomo2D.py | 156 ++++++++++++ 16 files changed, 3134 insertions(+) create mode 100755 Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py create mode 100755 Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py create mode 100644 Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py create mode 100644 Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_2D_time_denoising.py create mode 100644 Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Tomo2D_time.py create mode 100644 Wrappers/Python/demos/PDHG_examples/TGV_Denoising/PDHG_TGV_Denoising_SaltPepper.py create mode 100644 Wrappers/Python/demos/PDHG_examples/TV_Denoising/.DS_Store create mode 100644 Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_2D_time.py create mode 100644 Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Gaussian.py create mode 100644 Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Gaussian_3D.py create mode 100644 Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Poisson.py create mode 100644 Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_SaltPepper.py create mode 100644 Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py create mode 100644 Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_gaussian.py create mode 100644 Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py create mode 100644 Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_Tikhonov_Tomo2D.py diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py new file mode 100755 index 0000000..4d6da00 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py @@ -0,0 +1,221 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= +""" + +Total Generalised Variation (TGV) Denoising using PDHG algorithm: + + +Problem: min_{x} \alpha * ||\nabla x - w||_{2,1} + + \beta * || E w ||_{2,1} + + fidelity + + where fidelity can be as follows depending on the noise characteristics + of the data: + * Norm2Squared \frac{1}{2} * || x - g ||_{2}^{2} + * KullbackLeibler \int u - g * log(u) + Id_{u>0} + * L1Norm ||u - g||_{1} + + \alpha: Regularization parameter + \beta: Regularization parameter + + \nabla: Gradient operator + E: Symmetrized Gradient operator + + g: Noisy Data with Salt & Pepper Noise + + Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity + ZeroOperator, E + Identity, ZeroOperator] + + + Method = 1 (PDHG - explicit ): K = [ \nabla, - Identity + ZeroOperator, E ] + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, \ + Gradient, SymmetrizedGradient, ZeroOperator +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, BlockFunction, KullbackLeibler, L2NormSquared +import sys +if int(numpy.version.version.split('.')[1]) > 12: + from skimage.util import random_noise +else: + from demoutil import random_noise + + + +# user supplied input +if len(sys.argv) > 1: + which_noise = int(sys.argv[1]) +else: + which_noise = 0 +print ("Applying {} noise") + +if len(sys.argv) > 2: + method = sys.argv[2] +else: + method = '1' +print ("method ", method) +# Create phantom for TGV SaltPepper denoising + +N = 100 + +x1 = np.linspace(0, int(N/2), N) +x2 = np.linspace(int(N/2), 0., N) +xv, yv = np.meshgrid(x1, x2) + +xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T + +#data = xv + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +data = ig.allocate() +data.fill(xv/xv.max()) +ag = ig + +# Create noisy data. +# Apply Salt & Pepper noise +# gaussian +# poisson +noises = ['gaussian', 'poisson', 's&p'] +noise = noises[which_noise] +if noise == 's&p': + n1 = random_noise(data.as_array(), mode = noise, salt_vs_pepper = 0.9, amount=0.2, seed=10) +elif noise == 'poisson': + n1 = random_noise(data.as_array(), mode = noise, seed = 10) +elif noise == 'gaussian': + n1 = random_noise(data.as_array(), mode = noise, seed = 10) +else: + raise ValueError('Unsupported Noise ', noise) +noisy_data = ImageData(n1) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,5)) +plt.subplot(1,2,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(1,2,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameters +if noise == 's&p': + alpha = 0.8 +elif noise == 'poisson': + alpha = .1 +elif noise == 'gaussian': + alpha = .3 + +beta = numpy.sqrt(2)* alpha + +# fidelity +if noise == 's&p': + f3 = L1Norm(b=noisy_data) +elif noise == 'poisson': + f3 = KullbackLeibler(noisy_data) +elif noise == 'gaussian': + f3 = L2NormSquared(b=noisy_data) + +if method == '0': + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + op31 = Identity(ig, ag) + op32 = ZeroOperator(op22.domain_geometry(), ag) + + operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + # f3 depends on the noise characteristics + + f = BlockFunction(f1, f2, f3) + g = ZeroFunction() + +else: + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + operator = BlockOperator(op11, -1*op12, op21, op22, shape=(2,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + + f = BlockFunction(f1, f2) + g = BlockFunction(f3, ZeroFunction()) + +## Compute operator Norm +normK = operator.norm() +# +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000, verbose = True) + +#%% +plt.figure(figsize=(20,5)) +plt.subplot(1,4,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(1,4,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(1,4,3) +plt.imshow(pdhg.get_output()[0].as_array()) +plt.title('TGV Reconstruction') +plt.colorbar() +plt.subplot(1,4,4) +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TGV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py new file mode 100755 index 0000000..0f1effa --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py @@ -0,0 +1,266 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation Denoising using PDHG algorithm: + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + ||x-g||_{1} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Salt & Pepper Noise + + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, BlockFunction, L2NormSquared,\ + KullbackLeibler +from ccpi.framework import TestData +import os, sys +if int(numpy.version.version.split('.')[1]) > 12: + from skimage.util import random_noise +else: + from demoutil import random_noise + +# user supplied input +if len(sys.argv) > 1: + which_noise = int(sys.argv[1]) +else: + which_noise = 0 +print ("Applying {} noise") + +if len(sys.argv) > 2: + method = sys.argv[2] +else: + method = '0' +print ("method ", method) +# Create phantom for TV Salt & Pepper denoising +N = 100 + +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,N)) +data = loader.load(TestData.PEPPERS, size=(N,N)) +ig = data.geometry +ag = ig + +# Create noisy data. +# Apply Salt & Pepper noise +# gaussian +# poisson +noises = ['gaussian', 'poisson', 's&p'] +noise = noises[which_noise] +if noise == 's&p': + n1 = random_noise(data.as_array(), mode = noise, salt_vs_pepper = 0.9, amount=0.2) +elif noise == 'poisson': + n1 = random_noise(data.as_array(), mode = noise, seed = 10) +elif noise == 'gaussian': + n1 = random_noise(data.as_array(), mode = noise, seed = 10) +else: + raise ValueError('Unsupported Noise ', noise) +noisy_data = ig.allocate() +noisy_data.fill(n1) +#noisy_data = ImageData(n1) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,5)) +plt.subplot(1,2,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(1,2,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = .2 + +# fidelity +if noise == 's&p': + f2 = L1Norm(b=noisy_data) +elif noise == 'poisson': + f2 = KullbackLeibler(noisy_data) +elif noise == 'gaussian': + f2 = L2NormSquared(b=noisy_data) + +if method == '0': + + # Create operators + op1 = Gradient(ig, correlation=Gradient.CORRELATION_SPACECHANNEL) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + f1 = alpha * MixedL21Norm() + #f2 = L1Norm(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + #g = L1Norm(b = noisy_data) + g = f2 + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +opt = {'niter':2000, 'memopt': True} + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + +if data.geometry.channels > 1: + plt.figure(figsize=(20,15)) + for row in range(data.geometry.channels): + + plt.subplot(3,4,1+row*4) + plt.imshow(data.subset(channel=row).as_array()) + plt.title('Ground Truth') + plt.colorbar() + plt.subplot(3,4,2+row*4) + plt.imshow(noisy_data.subset(channel=row).as_array()) + plt.title('Noisy Data') + plt.colorbar() + plt.subplot(3,4,3+row*4) + plt.imshow(pdhg.get_output().subset(channel=row).as_array()) + plt.title('TV Reconstruction') + plt.colorbar() + plt.subplot(3,4,4+row*4) + plt.plot(np.linspace(0,N,N), data.subset(channel=row).as_array()[int(N/2),:], label = 'GTruth') + plt.plot(np.linspace(0,N,N), pdhg.get_output().subset(channel=row).as_array()[int(N/2),:], label = 'TV reconstruction') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() +else: + plt.figure(figsize=(20,5)) + plt.subplot(1,4,1) + plt.imshow(data.subset(channel=0).as_array()) + plt.title('Ground Truth') + plt.colorbar() + plt.subplot(1,4,2) + plt.imshow(noisy_data.subset(channel=0).as_array()) + plt.title('Noisy Data') + plt.colorbar() + plt.subplot(1,4,3) + plt.imshow(pdhg.get_output().subset(channel=0).as_array()) + plt.title('TV Reconstruction') + plt.colorbar() + plt.subplot(1,4,4) + plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = pnorm( u - noisy_data.as_array(),1) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py new file mode 100644 index 0000000..5aa334d --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py @@ -0,0 +1,256 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= +""" + +Tikhonov Denoising using PDHG algorithm: + + +Problem: min_{x} \alpha * ||\nabla x||_{2}^{2} + \frac{1}{2} * || x - g ||_{2}^{2} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Gaussian Noise + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + +""" + +from ccpi.framework import ImageData, TestData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared,\ + BlockFunction, KullbackLeibler, L1Norm + +import sys, os +if int(numpy.version.version.split('.')[1]) > 12: + from skimage.util import random_noise +else: + from demoutil import random_noise + + +# user supplied input +if len(sys.argv) > 1: + which_noise = int(sys.argv[1]) +else: + which_noise = 0 +print ("Applying {} noise") + +if len(sys.argv) > 2: + method = sys.argv[2] +else: + method = '0' +print ("method ", method) + +# Create phantom for TV Salt & Pepper denoising +N = 100 + +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,N)) +ig = data.geometry +ag = ig + +# Create noisy data. Apply Salt & Pepper noise +# Create noisy data. +# Apply Salt & Pepper noise +# gaussian +# poisson +noises = ['gaussian', 'poisson', 's&p'] +noise = noises[which_noise] +if noise == 's&p': + n1 = random_noise(data.as_array(), mode = noise, salt_vs_pepper = 0.9, amount=0.2) +elif noise == 'poisson': + n1 = random_noise(data.as_array(), mode = noise, seed = 10) +elif noise == 'gaussian': + n1 = random_noise(data.as_array(), mode = noise, seed = 10) +else: + raise ValueError('Unsupported Noise ', noise) +noisy_data = ImageData(n1) + +# fidelity +if noise == 's&p': + f2 = L1Norm(b=noisy_data) +elif noise == 'poisson': + f2 = KullbackLeibler(noisy_data) +elif noise == 'gaussian': + f2 = 0.5 * L2NormSquared(b=noisy_data) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,5)) +plt.subplot(1,2,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(1,2,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + + +# Regularisation Parameter +# no edge preservation alpha is big +if noise == 's&p': + alpha = 8. +elif noise == 'poisson': + alpha = 8. +elif noise == 'gaussian': + alpha = 8. + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * L2NormSquared() + # f2 must change according to noise + #f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * L2NormSquared() + # g must change according to noise + #g = 0.5 * L2NormSquared(b = noisy_data) + g = f2 + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +opt = {'niter':2000, 'memopt': True} + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(1500) + + +plt.figure(figsize=(20,5)) +plt.subplot(1,4,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(1,4,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(1,4,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('Tikhonov Reconstruction') +plt.colorbar() +plt.subplot(1,4,4) +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + + regulariser = alpha * sum_squares(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_2D_time_denoising.py b/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_2D_time_denoising.py new file mode 100644 index 0000000..045458a --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_2D_time_denoising.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorMC + +import os +import tomophantom +from tomophantom import TomoP2D + +# Create phantom for TV 2D dynamic tomography + +model = 102 # note that the selected model is temporal (2D + time) +N = 50 # set dimension of the phantom +# one can specify an exact path to the parameters file +# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' +path = os.path.dirname(tomophantom.__file__) +path_library2D = os.path.join(path, "Phantom2DLibrary.dat") +#This will generate a N_size x N_size x Time frames phantom (2D + time) +phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) + +plt.close('all') +plt.figure(1) +plt.rcParams.update({'font.size': 21}) +plt.title('{}''{}'.format('2D+t phantom using model no.',model)) +for sl in range(0,np.shape(phantom_2Dt)[0]): + im = phantom_2Dt[sl,:,:] + plt.imshow(im, vmin=0, vmax=1) + plt.pause(.1) + plt.draw + + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) +data = ImageData(phantom_2Dt, geometry=ig) + +detectors = N +angles = np.linspace(0,np.pi,N) + +ag = AcquisitionGeometry('parallel','2D', angles, detectors, channels = np.shape(phantom_2Dt)[0]) +Aop = AstraProjectorMC(ig, ag, 'gpu') +sin = Aop.direct(data) + +scale = 2 +n1 = scale * np.random.poisson(sin.as_array()/scale) +noisy_data = AcquisitionData(n1, ag) + +tindex = [3, 6, 10] + +fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) +plt.subplot(1,3,1) +plt.imshow(noisy_data.as_array()[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) +plt.subplot(1,3,2) +plt.imshow(noisy_data.as_array()[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) +plt.subplot(1,3,3) +plt.imshow(noisy_data.as_array()[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +plt.show() + +#%% +# Regularisation Parameter +alpha = 5 + +# Create operators +#op1 = Gradient(ig) +op1 = Gradient(ig, correlation='SpaceChannels') +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + + +#%% +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) + +plt.subplot(2,3,1) +plt.imshow(phantom_2Dt[tindex[0],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) + +plt.subplot(2,3,2) +plt.imshow(phantom_2Dt[tindex[1],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) + +plt.subplot(2,3,3) +plt.imshow(phantom_2Dt[tindex[2],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + + +plt.subplot(2,3,4) +plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(2,3,5) +plt.imshow(pdhg.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(2,3,6) +plt.imshow(pdhg.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') +im = plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + diff --git a/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Tomo2D_time.py b/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Tomo2D_time.py new file mode 100644 index 0000000..045458a --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Tomo2D_time.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorMC + +import os +import tomophantom +from tomophantom import TomoP2D + +# Create phantom for TV 2D dynamic tomography + +model = 102 # note that the selected model is temporal (2D + time) +N = 50 # set dimension of the phantom +# one can specify an exact path to the parameters file +# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' +path = os.path.dirname(tomophantom.__file__) +path_library2D = os.path.join(path, "Phantom2DLibrary.dat") +#This will generate a N_size x N_size x Time frames phantom (2D + time) +phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) + +plt.close('all') +plt.figure(1) +plt.rcParams.update({'font.size': 21}) +plt.title('{}''{}'.format('2D+t phantom using model no.',model)) +for sl in range(0,np.shape(phantom_2Dt)[0]): + im = phantom_2Dt[sl,:,:] + plt.imshow(im, vmin=0, vmax=1) + plt.pause(.1) + plt.draw + + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) +data = ImageData(phantom_2Dt, geometry=ig) + +detectors = N +angles = np.linspace(0,np.pi,N) + +ag = AcquisitionGeometry('parallel','2D', angles, detectors, channels = np.shape(phantom_2Dt)[0]) +Aop = AstraProjectorMC(ig, ag, 'gpu') +sin = Aop.direct(data) + +scale = 2 +n1 = scale * np.random.poisson(sin.as_array()/scale) +noisy_data = AcquisitionData(n1, ag) + +tindex = [3, 6, 10] + +fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) +plt.subplot(1,3,1) +plt.imshow(noisy_data.as_array()[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) +plt.subplot(1,3,2) +plt.imshow(noisy_data.as_array()[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) +plt.subplot(1,3,3) +plt.imshow(noisy_data.as_array()[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +plt.show() + +#%% +# Regularisation Parameter +alpha = 5 + +# Create operators +#op1 = Gradient(ig) +op1 = Gradient(ig, correlation='SpaceChannels') +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + + +#%% +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) + +plt.subplot(2,3,1) +plt.imshow(phantom_2Dt[tindex[0],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) + +plt.subplot(2,3,2) +plt.imshow(phantom_2Dt[tindex[1],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) + +plt.subplot(2,3,3) +plt.imshow(phantom_2Dt[tindex[2],:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + + +plt.subplot(2,3,4) +plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(2,3,5) +plt.imshow(pdhg.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(2,3,6) +plt.imshow(pdhg.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') +im = plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + diff --git a/Wrappers/Python/demos/PDHG_examples/TGV_Denoising/PDHG_TGV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_examples/TGV_Denoising/PDHG_TGV_Denoising_SaltPepper.py new file mode 100644 index 0000000..15c0a05 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/TGV_Denoising/PDHG_TGV_Denoising_SaltPepper.py @@ -0,0 +1,245 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= +""" + +Total Generalised Variation (TGV) Denoising using PDHG algorithm: + + +Problem: min_{x} \alpha * ||\nabla x - w||_{2,1} + + \beta * || E w ||_{2,1} + + \frac{1}{2} * || x - g ||_{2}^{2} + + \alpha: Regularization parameter + \beta: Regularization parameter + + \nabla: Gradient operator + E: Symmetrized Gradient operator + + g: Noisy Data with Salt & Pepper Noise + + Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity + ZeroOperator, E + Identity, ZeroOperator] + + + Method = 1 (PDHG - explicit ): K = [ \nabla, - Identity + ZeroOperator, E ] + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, \ + Gradient, SymmetrizedGradient, ZeroOperator +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TGV SaltPepper denoising + +N = 100 + +data = np.zeros((N,N)) + +x1 = np.linspace(0, int(N/2), N) +x2 = np.linspace(int(N/2), 0., N) +xv, yv = np.meshgrid(x1, x2) + +xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T + +data = xv +data = ImageData(data/data.max()) + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Add Gaussian noise +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(15,15)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameters +alpha = 0.8 +beta = numpy.sqrt(2)* alpha + +method = '1' + +if method == '0': + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + op31 = Identity(ig, ag) + op32 = ZeroOperator(op22.domain_geometry(), ag) + + operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + f3 = L1Norm(b=noisy_data) + f = BlockFunction(f1, f2, f3) + g = ZeroFunction() + +else: + + # Create operators + op11 = Gradient(ig) + op12 = Identity(op11.range_geometry()) + op22 = SymmetrizedGradient(op11.domain_geometry()) + op21 = ZeroOperator(ig, op22.range_geometry()) + + operator = BlockOperator(op11, -1*op12, op21, op22, shape=(2,2) ) + + f1 = alpha * MixedL21Norm() + f2 = beta * MixedL21Norm() + + f = BlockFunction(f1, f2) + g = BlockFunction(L1Norm(b=noisy_data), ZeroFunction()) + +## Compute operator Norm +normK = operator.norm() +# +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000, verbose = False) + +#%% +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output()[0].as_array()) +plt.title('TGV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + +if cvx_not_installable: + + u = Variable(ig.shape) + w1 = Variable((N, N)) + w2 = Variable((N, N)) + + # create TGV regulariser + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ + DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ + beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) + + constraints = [] + fidelity = pnorm(u - noisy_data.as_array(),1) + solver = MOSEK + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output()[0].as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output()[0].as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/.DS_Store b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/.DS_Store differ diff --git a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_2D_time.py b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_2D_time.py new file mode 100644 index 0000000..14608db --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_2D_time.py @@ -0,0 +1,192 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient, Identity +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorMC + +import os +import tomophantom +from tomophantom import TomoP2D + +# Create phantom for TV 2D dynamic tomography + +model = 102 # note that the selected model is temporal (2D + time) +N = 128 # set dimension of the phantom +# one can specify an exact path to the parameters file +# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' +path = os.path.dirname(tomophantom.__file__) +path_library2D = os.path.join(path, "Phantom2DLibrary.dat") +#This will generate a N_size x N_size x Time frames phantom (2D + time) +phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) + +plt.close('all') +plt.figure(1) +plt.rcParams.update({'font.size': 21}) +plt.title('{}''{}'.format('2D+t phantom using model no.',model)) +for sl in range(0,np.shape(phantom_2Dt)[0]): + im = phantom_2Dt[sl,:,:] + plt.imshow(im, vmin=0, vmax=1) +# plt.pause(.1) +# plt.draw + + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) +data = ImageData(phantom_2Dt, geometry=ig) +ag = ig + +# Create Noisy data. Add Gaussian noise +np.random.seed(10) +noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.25, size=ig.shape) ) + +tindex = [3, 6, 10] + +fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) +plt.subplot(1,3,1) +plt.imshow(noisy_data.as_array()[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) +plt.subplot(1,3,2) +plt.imshow(noisy_data.as_array()[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) +plt.subplot(1,3,3) +plt.imshow(noisy_data.as_array()[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +plt.show() + +#%% +# Regularisation Parameter +alpha = 0.3 + +# Create operators +#op1 = Gradient(ig) +op1 = Gradient(ig, correlation='Space') +op2 = Gradient(ig, correlation='SpaceChannels') + +op3 = Identity(ig, ag) + +# Create BlockOperator +operator1 = BlockOperator(op1, op3, shape=(2,1) ) +operator2 = BlockOperator(op2, op3, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = 0.5 * L2NormSquared(b = noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK1 = operator1.norm() +normK2 = operator2.norm() + +#%% +# Primal & dual stepsizes +sigma1 = 1 +tau1 = 1/(sigma1*normK1**2) + +sigma2 = 1 +tau2 = 1/(sigma2*normK2**2) + +# Setup and run the PDHG algorithm +pdhg1 = PDHG(f=f,g=g,operator=operator1, tau=tau1, sigma=sigma1) +pdhg1.max_iteration = 2000 +pdhg1.update_objective_interval = 200 +pdhg1.run(2000) + +# Setup and run the PDHG algorithm +pdhg2 = PDHG(f=f,g=g,operator=operator2, tau=tau2, sigma=sigma2) +pdhg2.max_iteration = 2000 +pdhg2.update_objective_interval = 200 +pdhg2.run(2000) + + +#%% + +tindex = [3, 6, 10] +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) + +plt.subplot(3,3,1) +plt.imshow(phantom_2Dt[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) + +plt.subplot(3,3,2) +plt.imshow(phantom_2Dt[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) + +plt.subplot(3,3,3) +plt.imshow(phantom_2Dt[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +plt.subplot(3,3,4) +plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(3,3,5) +plt.imshow(pdhg1.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(3,3,6) +plt.imshow(pdhg1.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') + + +plt.subplot(3,3,7) +plt.imshow(pdhg2.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(3,3,8) +plt.imshow(pdhg2.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(3,3,9) +plt.imshow(pdhg2.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') + +#%% +im = plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + diff --git a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Gaussian.py new file mode 100644 index 0000000..9d00ee1 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Gaussian.py @@ -0,0 +1,225 @@ +#======================================================================== +# CCP in Tomographic Imaging (CCPi) Core Imaging Library (CIL). + +# Copyright 2017 UKRI-STFC +# Copyright 2017 University of Manchester + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#========================================================================= +""" + +Total Variation Denoising using PDHG algorithm: + + +Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Gaussian Noise + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from ccpi.framework import TestData +import os, sys +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) + +# Load Data +N = 200 +M = 300 + + +# user can change the size of the input data +# you can choose between +# TestData.PEPPERS 2D + Channel +# TestData.BOAT 2D +# TestData.CAMERA 2D +# TestData.RESOLUTION_CHART 2D +# TestData.SIMPLE_PHANTOM_2D 2D +data = loader.load(TestData.BOAT, size=(N,M), scale=(0,1)) + +ig = data.geometry +ag = ig + +# Create Noisy data. Add Gaussian noise +np.random.seed(10) +noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.1, size=data.shape) ) + +print ("min {} max {}".format(data.as_array().min(), data.as_array().max())) + +# Show Ground Truth and Noisy Data +plt.figure() +plt.subplot(1,3,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(1,3,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(1,3,3) +plt.imshow((data - noisy_data).as_array()) +plt.title('diff') +plt.colorbar() + +plt.show() + +# Regularisation Parameter +alpha = .1 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig, correlation=Gradient.CORRELATION_SPACE) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + f1 = alpha * MixedL21Norm() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = 0.5 * L2NormSquared(b = noisy_data) + +# Compute Operator Norm +normK = operator.norm() + +# Primal & Dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + +# Setup and Run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 10000 +pdhg.update_objective_interval = 100 +pdhg.run(1000, verbose=True) + +# Show Results +plt.figure() +plt.subplot(1,3,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.clim(0,1) +plt.subplot(1,3,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.clim(0,1) +plt.subplot(1,3,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.clim(0,1) +plt.colorbar() +plt.show() + +plt.plot(np.linspace(0,N,M), noisy_data.as_array()[int(N/2),:], label = 'Noisy data') +plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') + +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = MOSEK) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,M), u.value[int(N/2),:], label = 'CVX') + plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'Truth') + + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + diff --git a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Gaussian_3D.py b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Gaussian_3D.py new file mode 100644 index 0000000..03dc2ef --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Gaussian_3D.py @@ -0,0 +1,181 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= +""" + +Total Variation (3D) Denoising using PDHG algorithm: + + +Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Gaussian Noise + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Gaussian denoising +import timeit +import os +from tomophantom import TomoP3D +import tomophantom + +print ("Building 3D phantom using TomoPhantom software") +tic=timeit.default_timer() +model = 13 # select a model number from the library +N = 64 # Define phantom dimensions using a scalar value (cubic phantom) +path = os.path.dirname(tomophantom.__file__) +path_library3D = os.path.join(path, "Phantom3DLibrary.dat") + +#This will generate a N x N x N phantom (3D) +phantom_tm = TomoP3D.Model(model, N, path_library3D) + +#%% + +# Create noisy data. Add Gaussian noise +ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N, voxel_num_z=N) +ag = ig +n1 = random_noise(phantom_tm, mode = 'gaussian', mean=0, var = 0.001, seed=10) +noisy_data = ImageData(n1) + +sliceSel = int(0.5*N) +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.title('Axial View') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.title('Coronal View') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.title('Sagittal View') +plt.colorbar() +plt.show() + +#%% + +# Regularisation Parameter +alpha = 0.05 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = 0.5 * L2NormSquared(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000, verbose = True) + + +#%% +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) +fig.suptitle('TV Reconstruction',fontsize=20) + + +plt.subplot(2,3,1) +plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Axial View') + +plt.subplot(2,3,2) +plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Coronal View') + +plt.subplot(2,3,3) +plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.axis('off') +plt.title('Sagittal View') + + +plt.subplot(2,3,4) +plt.imshow(pdhg.get_output().as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.axis('off') +plt.subplot(2,3,5) +plt.imshow(pdhg.get_output().as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.axis('off') +plt.subplot(2,3,6) +plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.axis('off') +im = plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + diff --git a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Poisson.py new file mode 100644 index 0000000..1d887c1 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Poisson.py @@ -0,0 +1,212 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation Denoising using PDHG algorithm: + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + \int x - g * log(x) + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Poisson Noise + + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, IndicatorBox, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Poisson denoising +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Apply Poisson noise +n1 = random_noise(data.as_array(), mode = 'poisson', seed = 10) +noisy_data = ImageData(n1) + + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + + +# Regularisation Parameter +alpha = 2 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = KullbackLeibler(noisy_data) + f = BlockFunction(f1, f2) + g = IndicatorBox(lower=0) + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = KullbackLeibler(noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & Dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + +# Setup and Run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 3000 +pdhg.update_objective_interval = 200 +pdhg.run(3000, verbose=True) + + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u1 = Variable(ig.shape) + q = Variable() + w = Variable() + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) + fidelity = sum(kl_div(noisy_data.as_array(), u1)) + + constraints = [q>=fidelity, u1>=0] + + solver = ECOS + obj = Minimize( regulariser + q) + prob = Problem(obj, constraints) + result = prob.solve(verbose = True, solver = solver) + + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u1.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u1.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_SaltPepper.py new file mode 100644 index 0000000..c5709c3 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_SaltPepper.py @@ -0,0 +1,213 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation Denoising using PDHG algorithm: + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + ||x-g||_{1} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Salt & Pepper Noise + + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Salt & Pepper denoising +N = 100 + +data = np.zeros((N,N)) +data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 +data = ImageData(data) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Apply Salt & Pepper noise +n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) +noisy_data = ImageData(n1) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(15,15)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = 2 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + f1 = alpha * MixedL21Norm() + f2 = L1Norm(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = L1Norm(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) +opt = {'niter':2000, 'memopt': True} + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 50 +pdhg.run(2000) + + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = pnorm( u - noisy_data.as_array(),1) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) + + + + + diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py new file mode 100644 index 0000000..bc0081f --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py @@ -0,0 +1,189 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= +""" + +Total Generalised Variation (TGV) Tomography 2D using PDHG algorithm: + + +Problem: min_{x>0} \alpha * ||\nabla x - w||_{2,1} + + \beta * || E w ||_{2,1} + + int A x - g log(Ax + \eta) + + \alpha: Regularization parameter + \beta: Regularization parameter + + \nabla: Gradient operator + E: Symmetrized Gradient operator + A: Projection Matrix + + g: Noisy Data with Poisson Noise + + K = [ \nabla, - Identity + ZeroOperator, E + A, ZeroOperator] + +""" + +from ccpi.framework import AcquisitionGeometry, AcquisitionData, ImageData, ImageGeometry + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient, Identity, \ + SymmetrizedGradient, ZeroOperator +from ccpi.optimisation.functions import IndicatorBox, KullbackLeibler, ZeroFunction,\ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple +from ccpi.framework import TestData +import os, sys +from skimage.util import random_noise + +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) + +# Load Data +#N = 50 +#M = 50 +#data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) +# +#ig = data.geometry +#ag = ig +N = 100 + +data = np.zeros((N,N)) + +x1 = np.linspace(0, int(N/2), N) +x2 = np.linspace(int(N/2), 0., N) +xv, yv = np.meshgrid(x1, x2) + +xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T + +data = xv +data = ImageData(data/data.max()) + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +ag = ig + +# Create noisy data. Add Gaussian noise +scale = 0.5 +eta = 0 +n1 = scale * np.random.poisson(eta + sin.as_array()/scale) + +noisy_data = AcquisitionData(n1, ag) + +#Create Acquisition Data and apply poisson noise + +detectors = N +angles = np.linspace(0, np.pi, N) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) + +#device = input('Available device: GPU==1 / CPU==0 ') +device = '0' +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +scale = 0.5 +eta = 0 +n1 = scale * np.random.poisson(eta + sin.as_array()/scale) + +noisy_data = AcquisitionData(n1, ag) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() +#%% +# Regularisation Parameters +alpha = 1 +beta = 5 + +# Create Operators +op11 = Gradient(ig) +op12 = Identity(op11.range_geometry()) + +op22 = SymmetrizedGradient(op11.domain_geometry()) +op21 = ZeroOperator(ig, op22.range_geometry()) + +op31 = Aop +op32 = ZeroOperator(op22.domain_geometry(), ag) + +operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + +f1 = alpha * MixedL21Norm() +f2 = beta * MixedL21Norm() +f3 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2, f3) + +g = BlockFunction(IndicatorBox(lower=0), ZeroFunction()) + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 3000 +pdhg.update_objective_interval = 500 +pdhg.run(3000) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output()[0].as_array()) +plt.title('TGV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TGV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_gaussian.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_gaussian.py new file mode 100644 index 0000000..bd1bb69 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_gaussian.py @@ -0,0 +1,212 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation 2D Tomography Reconstruction using PDHG algorithm: + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + \frac{1}{2}||Ax - g||^{2} + + \nabla: Gradient operator + + A: Projection Matrix + g: Noisy sinogram corrupted with Gaussian Noise + + \alpha: Regularization parameter + +""" + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple + + + +# Create phantom for TV 2D tomography +N = 100 +x = np.zeros((N,N)) +x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 +x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 + +data = ImageData(x) +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) + +detectors = N +angles = np.linspace(0, np.pi, N) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) + +device = input('Available device: GPU==1 / CPU==0 ') + +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + +Aop = AstraProjectorSimple(ig, ag, dev) +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +n1 = np.random.normal(0, 3, size=ag.shape) +noisy_data = AcquisitionData(n1 + sin.as_array(), ag) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = 50 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = 0.5 * L2NormSquared(b=noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 10 +tau = 1/(sigma*normK**2) + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff +import astra +import numpy + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + +if cvx_not_installable: + + ##Construct problem + u = Variable(N*N) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + + # create matrix representation for Astra operator + vol_geom = astra.create_vol_geom(N, N) + proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + + proj_id = astra.create_projector('strip', proj_geom, vol_geom) + + matrix_id = astra.projector.matrix(proj_id) + + ProjMat = astra.matrix.get(matrix_id) + + tmp = noisy_data.as_array().ravel() + + fidelity = 0.5 * sum_squares(ProjMat * u - tmp) + + solver = MOSEK + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - np.reshape(u.value, (N,N) )) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(np.reshape(u.value, (N, N))) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N,N) )[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py new file mode 100644 index 0000000..95fe2c1 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py @@ -0,0 +1,228 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation Denoising using PDHG algorithm: + + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + int A x -g log(Ax + \eta) + + \nabla: Gradient operator + + A: Projection Matrix + g: Noisy sinogram corrupted with Poisson Noise + + \eta: Background Noise + \alpha: Regularization parameter + +""" + +from ccpi.framework import AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import KullbackLeibler, \ + MixedL21Norm, BlockFunction, IndicatorBox + +from ccpi.astra.ops import AstraProjectorSimple +from ccpi.framework import TestData +import os, sys + + + +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) + +# Load Data +N = 50 +M = 50 +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) + +ig = data.geometry +ag = ig + +#Create Acquisition Data and apply poisson noise + +detectors = N +angles = np.linspace(0, np.pi, N) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) + +device = input('Available device: GPU==1 / CPU==0 ') + +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +scale = 0.5 +eta = 0 +n1 = scale * np.random.poisson(eta + sin.as_array()/scale) + +noisy_data = AcquisitionData(n1, ag) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = 2 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1,op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = KullbackLeibler(noisy_data) +f = BlockFunction(f1, f2) + +g = IndicatorBox(lower=0) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 3000 +pdhg.update_objective_interval = 500 +pdhg.run(3000, verbose = True) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + +#%% Check with CVX solution +# +from ccpi.optimisation.operators import SparseFiniteDiff +import astra +import numpy + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + + ##Construct problem + u = Variable(N*N) + q = Variable() + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + + # create matrix representation for Astra operator + + vol_geom = astra.create_vol_geom(N, N) + proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) + + proj_id = astra.create_projector('strip', proj_geom, vol_geom) + + matrix_id = astra.projector.matrix(proj_id) + + ProjMat = astra.matrix.get(matrix_id) + + tmp = noisy_data.as_array().ravel() + + fidelity = sum(kl_div(tmp, ProjMat * u)) + + constraints = [q>=fidelity, u>=0] + solver = SCS + obj = Minimize( regulariser + q) + prob = Problem(obj, constraints) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = np.abs(pdhg.get_output().as_array() - np.reshape(u.value, (N, N))) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(np.reshape(u.value, (N, N))) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') + plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N, N))[int(N/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_Tikhonov_Tomo2D.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_Tikhonov_Tomo2D.py new file mode 100644 index 0000000..02cd053 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_Tikhonov_Tomo2D.py @@ -0,0 +1,156 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation Denoising using PDHG algorithm: + +Problem: min_x, x>0 \alpha * ||\nabla x||_{2}^{2} + int A x -g log(Ax + \eta) + + \nabla: Gradient operator + + A: Projection Matrix + g: Noisy sinogram corrupted with Poisson Noise + + \eta: Background Noise + \alpha: Regularization parameter + + + +""" + + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction + +from ccpi.astra.ops import AstraProjectorSimple +from ccpi.framework import TestData +import os, sys + +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) + +# Load Data +N = 100 +M = 100 +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) + +ig = data.geometry +ag = ig + +#Create Acquisition Data and apply poisson noise + +detectors = N +angles = np.linspace(0, np.pi, N) + +ag = AcquisitionGeometry('parallel','2D',angles, detectors) + +device = input('Available device: GPU==1 / CPU==0 ') + +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + +Aop = AstraProjectorSimple(ig, ag, 'cpu') +sin = Aop.direct(data) + +# Create noisy data. Apply Poisson noise +scale = 0.5 +eta = 0 +n1 = scale * np.random.poisson(eta + sin.as_array()/scale) + +noisy_data = AcquisitionData(n1, ag) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(2,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + + +# Regularisation Parameter +alpha = 1000 + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions + +f1 = alpha * L2NormSquared() +f2 = 0.5 * L2NormSquared(b=noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 500 +pdhg.run(2000) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('Tikhonov Reconstruction') +plt.colorbar() +plt.show() +## +plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() + + -- cgit v1.2.3 From c2fe1e798108ac00e209fe403a226ebdc202d701 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 23 May 2019 20:05:26 +0100 Subject: fix dir demos --- Wrappers/Python/demos/PDHG_TGV_Tomo2D.py | 127 ---------- Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py | 258 -------------------- Wrappers/Python/demos/PDHG_TV_Tomo2D_time.py | 169 ------------- Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py | 156 ------------ Wrappers/Python/demos/PDHG_examples/.DS_Store | Bin 0 -> 6148 bytes .../demos/PDHG_examples/PDHG_2D_time_denoising.py | 169 ------------- .../demos/PDHG_examples/PDHG_TGV_Denoising.py | 221 ----------------- .../PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py | 245 ------------------- .../demos/PDHG_examples/PDHG_TV_Denoising.py | 266 --------------------- .../PDHG_examples/PDHG_TV_Denoising_2D_time.py | 192 --------------- .../PDHG_examples/PDHG_TV_Denoising_Gaussian.py | 225 ----------------- .../PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py | 181 -------------- .../PDHG_examples/PDHG_TV_Denoising_Poisson.py | 212 ---------------- .../PDHG_examples/PDHG_TV_Denoising_SaltPepper.py | 213 ----------------- .../demos/PDHG_examples/PDHG_TV_Tomo2D_gaussian.py | 212 ---------------- .../demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py | 226 ----------------- .../demos/PDHG_examples/PDHG_Tikhonov_Denoising.py | 256 -------------------- .../demos/PDHG_examples/PDHG_Tikhonov_Tomo2D.py | 156 ------------ 18 files changed, 3484 deletions(-) delete mode 100644 Wrappers/Python/demos/PDHG_TGV_Tomo2D.py delete mode 100644 Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py delete mode 100644 Wrappers/Python/demos/PDHG_TV_Tomo2D_time.py delete mode 100644 Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py create mode 100644 Wrappers/Python/demos/PDHG_examples/.DS_Store delete mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_2D_time_denoising.py delete mode 100755 Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py delete mode 100755 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_2D_time.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Poisson.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_SaltPepper.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_gaussian.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Denoising.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Tomo2D.py diff --git a/Wrappers/Python/demos/PDHG_TGV_Tomo2D.py b/Wrappers/Python/demos/PDHG_TGV_Tomo2D.py deleted file mode 100644 index 19cf684..0000000 --- a/Wrappers/Python/demos/PDHG_TGV_Tomo2D.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient, Identity, \ - SymmetrizedGradient, ZeroOperator -from ccpi.optimisation.functions import ZeroFunction, IndicatorBox, KullbackLeibler, \ - MixedL21Norm, BlockFunction - -from ccpi.astra.ops import AstraProjectorSimple - -# Create phantom for TV 2D tomography -N = 75 - -data = np.zeros((N,N)) - -x1 = np.linspace(0, int(N/2), N) -x2 = np.linspace(int(N/2), 0., N) -xv, yv = np.meshgrid(x1, x2) - -xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T -data = xv -data = ImageData(data/data.max()) - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -detectors = N -angles = np.linspace(0, np.pi, N, dtype=np.float32) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'gpu') -sin = Aop.direct(data) - -# Create noisy data. Apply Poisson noise -scale = 0.1 -np.random.seed(5) -n1 = scale * np.random.poisson(sin.as_array()/scale) -noisy_data = AcquisitionData(n1, ag) - - -plt.imshow(noisy_data.as_array()) -plt.show() -#%% -# Regularisation Parameters -alpha = 0.7 -beta = 2 - -# Create Operators -op11 = Gradient(ig) -op12 = Identity(op11.range_geometry()) - -op22 = SymmetrizedGradient(op11.domain_geometry()) -op21 = ZeroOperator(ig, op22.range_geometry()) - -op31 = Aop -op32 = ZeroOperator(op22.domain_geometry(), ag) - -operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) - -f1 = alpha * MixedL21Norm() -f2 = beta * MixedL21Norm() -f3 = KullbackLeibler(noisy_data) -f = BlockFunction(f1, f2, f3) - -g = BlockFunction(-1 * IndicatorBox(lower=0), ZeroFunction()) -#g = IndicatorBox(lower=0) -#g = ZeroFunction() - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 10 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output()[0].as_array()) -plt.title('TGV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TGV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - diff --git a/Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py b/Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py deleted file mode 100644 index 72d0670..0000000 --- a/Wrappers/Python/demos/PDHG_TV_Tomo2D_poisson.py +++ /dev/null @@ -1,258 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ - MixedL21Norm, BlockFunction, IndicatorBox - -from ccpi.astra.ops import AstraProjectorSimple - -""" - -Total Variation Denoising using PDHG algorithm: - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + int A x -g log(Ax + \eta) - - \nabla: Gradient operator - - A: Projection Matrix - g: Noisy sinogram corrupted with Poisson Noise - - \eta: Background Noise - \alpha: Regularization parameter - -""" - -# Create phantom for TV 2D tomography -N = 50 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -detectors = N -angles = np.linspace(0, np.pi, N) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -# Create noisy data. Apply Poisson noise -scale = 0.25 -eta = 0 #np.random.randint(0, sin.as_array().max()/2, ag.shape) -n1 = scale * np.random.poisson(eta + sin.as_array()/scale) - -noisy_data = AcquisitionData(n1, ag) - - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,10)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -#%% - -# Regularisation Parameter -alpha = 2 - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Create BlockOperator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -# Create functions - -f1 = alpha * MixedL21Norm() -f2 = KullbackLeibler(noisy_data) -f = BlockFunction(f1, f2) - -#g = ZeroFunction() -g = IndicatorBox(lower=0) - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 2 -tau = 1/(sigma*normK**2) -#sigma = 1/normK -#tau = 1/normK - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 500 -pdhg.run(2000, verbose = True) - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -##%% Check with CVX solution -# -#from ccpi.optimisation.operators import SparseFiniteDiff -#import astra -#import numpy -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -# -#if cvx_not_installable: -# -# -# ##Construct problem -# u = Variable(N*N) -# #q = Variable() -# -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) -# -# # create matrix representation for Astra operator -# -# vol_geom = astra.create_vol_geom(N, N) -# proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) -# -# proj_id = astra.create_projector('strip', proj_geom, vol_geom) -# -# matrix_id = astra.projector.matrix(proj_id) -# -# ProjMat = astra.matrix.get(matrix_id) -# -# tmp = noisy_data.as_array().ravel('F') -# -## fidelity = sum( ProjMat * u - tmp * log(ProjMat * u + 1e-6)) -# #constraints = [q>= fidelity, u>=0] -# constraints = [] -# -# fidelity = sum(kl_div(tmp, ProjMat * u + 1e-6)) -## fidelity = kl_div(cp.multiply(alpha, W), -## cp.multiply(alpha, W + cp.multiply(beta, P))) - \ -## cp.multiply(alpha, cp.multiply(beta, P)) -# -# -# -# solver = SCS -# obj = Minimize( regulariser + fidelity) -# prob = Problem(obj, constraints) -# result = prob.solve(verbose = True, solver = solver) -# -# -####%% Check with CVX solution -## -##from ccpi.optimisation.operators import SparseFiniteDiff -## -##try: -## from cvxpy import * -## cvx_not_installable = True -##except ImportError: -## cvx_not_installable = False -## -## -##if cvx_not_installable: -## -## ##Construct problem -## u = Variable(ig.shape) -## -## DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -## DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -## -## # Define Total Variation as a regulariser -## regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) -## fidelity = pnorm( u - noisy_data.as_array(),1) -## -## # choose solver -## if 'MOSEK' in installed_solvers(): -## solver = MOSEK -## else: -## solver = SCS -## -## obj = Minimize( regulariser + fidelity) -## prob = Problem(obj) -## result = prob.solve(verbose = True, solver = solver) -## -## -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(pdhg.get_output().as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# plt.subplot(3,1,2) -# plt.imshow(np.reshape(u.value, (N, N))) -# plt.title('CVX solution') -# plt.colorbar() -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') -# plt.legend() -# plt.title('Middle Line Profiles') -# plt.show() -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_TV_Tomo2D_time.py b/Wrappers/Python/demos/PDHG_TV_Tomo2D_time.py deleted file mode 100644 index 045458a..0000000 --- a/Wrappers/Python/demos/PDHG_TV_Tomo2D_time.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ - MixedL21Norm, BlockFunction - -from ccpi.astra.ops import AstraProjectorMC - -import os -import tomophantom -from tomophantom import TomoP2D - -# Create phantom for TV 2D dynamic tomography - -model = 102 # note that the selected model is temporal (2D + time) -N = 50 # set dimension of the phantom -# one can specify an exact path to the parameters file -# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' -path = os.path.dirname(tomophantom.__file__) -path_library2D = os.path.join(path, "Phantom2DLibrary.dat") -#This will generate a N_size x N_size x Time frames phantom (2D + time) -phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) - -plt.close('all') -plt.figure(1) -plt.rcParams.update({'font.size': 21}) -plt.title('{}''{}'.format('2D+t phantom using model no.',model)) -for sl in range(0,np.shape(phantom_2Dt)[0]): - im = phantom_2Dt[sl,:,:] - plt.imshow(im, vmin=0, vmax=1) - plt.pause(.1) - plt.draw - - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) -data = ImageData(phantom_2Dt, geometry=ig) - -detectors = N -angles = np.linspace(0,np.pi,N) - -ag = AcquisitionGeometry('parallel','2D', angles, detectors, channels = np.shape(phantom_2Dt)[0]) -Aop = AstraProjectorMC(ig, ag, 'gpu') -sin = Aop.direct(data) - -scale = 2 -n1 = scale * np.random.poisson(sin.as_array()/scale) -noisy_data = AcquisitionData(n1, ag) - -tindex = [3, 6, 10] - -fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) -plt.subplot(1,3,1) -plt.imshow(noisy_data.as_array()[tindex[0],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[0])) -plt.subplot(1,3,2) -plt.imshow(noisy_data.as_array()[tindex[1],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[1])) -plt.subplot(1,3,3) -plt.imshow(noisy_data.as_array()[tindex[2],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[2])) - -fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, - wspace=0.02, hspace=0.02) - -plt.show() - -#%% -# Regularisation Parameter -alpha = 5 - -# Create operators -#op1 = Gradient(ig) -op1 = Gradient(ig, correlation='SpaceChannels') -op2 = Aop - -# Create BlockOperator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -# Create functions - -f1 = alpha * MixedL21Norm() -f2 = KullbackLeibler(noisy_data) -f = BlockFunction(f1, f2) - -g = ZeroFunction() - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000) - - -#%% -fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) - -plt.subplot(2,3,1) -plt.imshow(phantom_2Dt[tindex[0],:,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Time {}'.format(tindex[0])) - -plt.subplot(2,3,2) -plt.imshow(phantom_2Dt[tindex[1],:,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Time {}'.format(tindex[1])) - -plt.subplot(2,3,3) -plt.imshow(phantom_2Dt[tindex[2],:,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Time {}'.format(tindex[2])) - - -plt.subplot(2,3,4) -plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) -plt.axis('off') -plt.subplot(2,3,5) -plt.imshow(pdhg.get_output().as_array()[tindex[1],:,:]) -plt.axis('off') -plt.subplot(2,3,6) -plt.imshow(pdhg.get_output().as_array()[tindex[2],:,:]) -plt.axis('off') -im = plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) - - -fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, - wspace=0.02, hspace=0.02) - -cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) -cbar = fig.colorbar(im, cax=cb_ax) - - -plt.show() - diff --git a/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py b/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py deleted file mode 100644 index 02cd053..0000000 --- a/Wrappers/Python/demos/PDHG_Tikhonov_Tomo2D.py +++ /dev/null @@ -1,156 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation Denoising using PDHG algorithm: - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2}^{2} + int A x -g log(Ax + \eta) - - \nabla: Gradient operator - - A: Projection Matrix - g: Noisy sinogram corrupted with Poisson Noise - - \eta: Background Noise - \alpha: Regularization parameter - - - -""" - - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction - -from ccpi.astra.ops import AstraProjectorSimple -from ccpi.framework import TestData -import os, sys - -loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) - -# Load Data -N = 100 -M = 100 -data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) - -ig = data.geometry -ag = ig - -#Create Acquisition Data and apply poisson noise - -detectors = N -angles = np.linspace(0, np.pi, N) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) - -device = input('Available device: GPU==1 / CPU==0 ') - -if device=='1': - dev = 'gpu' -else: - dev = 'cpu' - -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -# Create noisy data. Apply Poisson noise -scale = 0.5 -eta = 0 -n1 = scale * np.random.poisson(eta + sin.as_array()/scale) - -noisy_data = AcquisitionData(n1, ag) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,10)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - - -# Regularisation Parameter -alpha = 1000 - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Create BlockOperator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -# Create functions - -f1 = alpha * L2NormSquared() -f2 = 0.5 * L2NormSquared(b=noisy_data) -f = BlockFunction(f1, f2) - -g = ZeroFunction() - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 500 -pdhg.run(2000) - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('Tikhonov Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - diff --git a/Wrappers/Python/demos/PDHG_examples/.DS_Store b/Wrappers/Python/demos/PDHG_examples/.DS_Store new file mode 100644 index 0000000..141508d Binary files /dev/null and b/Wrappers/Python/demos/PDHG_examples/.DS_Store differ diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_2D_time_denoising.py b/Wrappers/Python/demos/PDHG_examples/PDHG_2D_time_denoising.py deleted file mode 100644 index 045458a..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_2D_time_denoising.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ - MixedL21Norm, BlockFunction - -from ccpi.astra.ops import AstraProjectorMC - -import os -import tomophantom -from tomophantom import TomoP2D - -# Create phantom for TV 2D dynamic tomography - -model = 102 # note that the selected model is temporal (2D + time) -N = 50 # set dimension of the phantom -# one can specify an exact path to the parameters file -# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' -path = os.path.dirname(tomophantom.__file__) -path_library2D = os.path.join(path, "Phantom2DLibrary.dat") -#This will generate a N_size x N_size x Time frames phantom (2D + time) -phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) - -plt.close('all') -plt.figure(1) -plt.rcParams.update({'font.size': 21}) -plt.title('{}''{}'.format('2D+t phantom using model no.',model)) -for sl in range(0,np.shape(phantom_2Dt)[0]): - im = phantom_2Dt[sl,:,:] - plt.imshow(im, vmin=0, vmax=1) - plt.pause(.1) - plt.draw - - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) -data = ImageData(phantom_2Dt, geometry=ig) - -detectors = N -angles = np.linspace(0,np.pi,N) - -ag = AcquisitionGeometry('parallel','2D', angles, detectors, channels = np.shape(phantom_2Dt)[0]) -Aop = AstraProjectorMC(ig, ag, 'gpu') -sin = Aop.direct(data) - -scale = 2 -n1 = scale * np.random.poisson(sin.as_array()/scale) -noisy_data = AcquisitionData(n1, ag) - -tindex = [3, 6, 10] - -fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) -plt.subplot(1,3,1) -plt.imshow(noisy_data.as_array()[tindex[0],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[0])) -plt.subplot(1,3,2) -plt.imshow(noisy_data.as_array()[tindex[1],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[1])) -plt.subplot(1,3,3) -plt.imshow(noisy_data.as_array()[tindex[2],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[2])) - -fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, - wspace=0.02, hspace=0.02) - -plt.show() - -#%% -# Regularisation Parameter -alpha = 5 - -# Create operators -#op1 = Gradient(ig) -op1 = Gradient(ig, correlation='SpaceChannels') -op2 = Aop - -# Create BlockOperator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -# Create functions - -f1 = alpha * MixedL21Norm() -f2 = KullbackLeibler(noisy_data) -f = BlockFunction(f1, f2) - -g = ZeroFunction() - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000) - - -#%% -fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) - -plt.subplot(2,3,1) -plt.imshow(phantom_2Dt[tindex[0],:,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Time {}'.format(tindex[0])) - -plt.subplot(2,3,2) -plt.imshow(phantom_2Dt[tindex[1],:,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Time {}'.format(tindex[1])) - -plt.subplot(2,3,3) -plt.imshow(phantom_2Dt[tindex[2],:,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Time {}'.format(tindex[2])) - - -plt.subplot(2,3,4) -plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) -plt.axis('off') -plt.subplot(2,3,5) -plt.imshow(pdhg.get_output().as_array()[tindex[1],:,:]) -plt.axis('off') -plt.subplot(2,3,6) -plt.imshow(pdhg.get_output().as_array()[tindex[2],:,:]) -plt.axis('off') -im = plt.imshow(pdhg.get_output().as_array()[tindex[0],:,:]) - - -fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, - wspace=0.02, hspace=0.02) - -cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) -cbar = fig.colorbar(im, cax=cb_ax) - - -plt.show() - diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising.py deleted file mode 100755 index 4d6da00..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising.py +++ /dev/null @@ -1,221 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= -""" - -Total Generalised Variation (TGV) Denoising using PDHG algorithm: - - -Problem: min_{x} \alpha * ||\nabla x - w||_{2,1} + - \beta * || E w ||_{2,1} + - fidelity - - where fidelity can be as follows depending on the noise characteristics - of the data: - * Norm2Squared \frac{1}{2} * || x - g ||_{2}^{2} - * KullbackLeibler \int u - g * log(u) + Id_{u>0} - * L1Norm ||u - g||_{1} - - \alpha: Regularization parameter - \beta: Regularization parameter - - \nabla: Gradient operator - E: Symmetrized Gradient operator - - g: Noisy Data with Salt & Pepper Noise - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity - ZeroOperator, E - Identity, ZeroOperator] - - - Method = 1 (PDHG - explicit ): K = [ \nabla, - Identity - ZeroOperator, E ] - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, \ - Gradient, SymmetrizedGradient, ZeroOperator -from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ - MixedL21Norm, BlockFunction, KullbackLeibler, L2NormSquared -import sys -if int(numpy.version.version.split('.')[1]) > 12: - from skimage.util import random_noise -else: - from demoutil import random_noise - - - -# user supplied input -if len(sys.argv) > 1: - which_noise = int(sys.argv[1]) -else: - which_noise = 0 -print ("Applying {} noise") - -if len(sys.argv) > 2: - method = sys.argv[2] -else: - method = '1' -print ("method ", method) -# Create phantom for TGV SaltPepper denoising - -N = 100 - -x1 = np.linspace(0, int(N/2), N) -x2 = np.linspace(int(N/2), 0., N) -xv, yv = np.meshgrid(x1, x2) - -xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T - -#data = xv - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -data = ig.allocate() -data.fill(xv/xv.max()) -ag = ig - -# Create noisy data. -# Apply Salt & Pepper noise -# gaussian -# poisson -noises = ['gaussian', 'poisson', 's&p'] -noise = noises[which_noise] -if noise == 's&p': - n1 = random_noise(data.as_array(), mode = noise, salt_vs_pepper = 0.9, amount=0.2, seed=10) -elif noise == 'poisson': - n1 = random_noise(data.as_array(), mode = noise, seed = 10) -elif noise == 'gaussian': - n1 = random_noise(data.as_array(), mode = noise, seed = 10) -else: - raise ValueError('Unsupported Noise ', noise) -noisy_data = ImageData(n1) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,5)) -plt.subplot(1,2,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(1,2,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Regularisation Parameters -if noise == 's&p': - alpha = 0.8 -elif noise == 'poisson': - alpha = .1 -elif noise == 'gaussian': - alpha = .3 - -beta = numpy.sqrt(2)* alpha - -# fidelity -if noise == 's&p': - f3 = L1Norm(b=noisy_data) -elif noise == 'poisson': - f3 = KullbackLeibler(noisy_data) -elif noise == 'gaussian': - f3 = L2NormSquared(b=noisy_data) - -if method == '0': - - # Create operators - op11 = Gradient(ig) - op12 = Identity(op11.range_geometry()) - - op22 = SymmetrizedGradient(op11.domain_geometry()) - op21 = ZeroOperator(ig, op22.range_geometry()) - - op31 = Identity(ig, ag) - op32 = ZeroOperator(op22.domain_geometry(), ag) - - operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) - - f1 = alpha * MixedL21Norm() - f2 = beta * MixedL21Norm() - # f3 depends on the noise characteristics - - f = BlockFunction(f1, f2, f3) - g = ZeroFunction() - -else: - - # Create operators - op11 = Gradient(ig) - op12 = Identity(op11.range_geometry()) - op22 = SymmetrizedGradient(op11.domain_geometry()) - op21 = ZeroOperator(ig, op22.range_geometry()) - - operator = BlockOperator(op11, -1*op12, op21, op22, shape=(2,2) ) - - f1 = alpha * MixedL21Norm() - f2 = beta * MixedL21Norm() - - f = BlockFunction(f1, f2) - g = BlockFunction(f3, ZeroFunction()) - -## Compute operator Norm -normK = operator.norm() -# -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000, verbose = True) - -#%% -plt.figure(figsize=(20,5)) -plt.subplot(1,4,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(1,4,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(1,4,3) -plt.imshow(pdhg.get_output()[0].as_array()) -plt.title('TGV Reconstruction') -plt.colorbar() -plt.subplot(1,4,4) -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TGV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py deleted file mode 100644 index 15c0a05..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TGV_Denoising_SaltPepper.py +++ /dev/null @@ -1,245 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= -""" - -Total Generalised Variation (TGV) Denoising using PDHG algorithm: - - -Problem: min_{x} \alpha * ||\nabla x - w||_{2,1} + - \beta * || E w ||_{2,1} + - \frac{1}{2} * || x - g ||_{2}^{2} - - \alpha: Regularization parameter - \beta: Regularization parameter - - \nabla: Gradient operator - E: Symmetrized Gradient operator - - g: Noisy Data with Salt & Pepper Noise - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity - ZeroOperator, E - Identity, ZeroOperator] - - - Method = 1 (PDHG - explicit ): K = [ \nabla, - Identity - ZeroOperator, E ] - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, \ - Gradient, SymmetrizedGradient, ZeroOperator -from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TGV SaltPepper denoising - -N = 100 - -data = np.zeros((N,N)) - -x1 = np.linspace(0, int(N/2), N) -x2 = np.linspace(int(N/2), 0., N) -xv, yv = np.meshgrid(x1, x2) - -xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T - -data = xv -data = ImageData(data/data.max()) - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Add Gaussian noise -n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) -noisy_data = ImageData(n1) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(15,15)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Regularisation Parameters -alpha = 0.8 -beta = numpy.sqrt(2)* alpha - -method = '1' - -if method == '0': - - # Create operators - op11 = Gradient(ig) - op12 = Identity(op11.range_geometry()) - - op22 = SymmetrizedGradient(op11.domain_geometry()) - op21 = ZeroOperator(ig, op22.range_geometry()) - - op31 = Identity(ig, ag) - op32 = ZeroOperator(op22.domain_geometry(), ag) - - operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) - - f1 = alpha * MixedL21Norm() - f2 = beta * MixedL21Norm() - f3 = L1Norm(b=noisy_data) - f = BlockFunction(f1, f2, f3) - g = ZeroFunction() - -else: - - # Create operators - op11 = Gradient(ig) - op12 = Identity(op11.range_geometry()) - op22 = SymmetrizedGradient(op11.domain_geometry()) - op21 = ZeroOperator(ig, op22.range_geometry()) - - operator = BlockOperator(op11, -1*op12, op21, op22, shape=(2,2) ) - - f1 = alpha * MixedL21Norm() - f2 = beta * MixedL21Norm() - - f = BlockFunction(f1, f2) - g = BlockFunction(L1Norm(b=noisy_data), ZeroFunction()) - -## Compute operator Norm -normK = operator.norm() -# -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000, verbose = False) - -#%% -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output()[0].as_array()) -plt.title('TGV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - -if cvx_not_installable: - - u = Variable(ig.shape) - w1 = Variable((N, N)) - w2 = Variable((N, N)) - - # create TGV regulariser - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ - DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ - beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ - 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ - 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) - - constraints = [] - fidelity = pnorm(u - noisy_data.as_array(),1) - solver = MOSEK - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output()[0].as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output()[0].as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising.py deleted file mode 100755 index 0f1effa..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising.py +++ /dev/null @@ -1,266 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation Denoising using PDHG algorithm: - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + ||x-g||_{1} - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Salt & Pepper Noise - - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity] - - - Method = 1 (PDHG - explicit ): K = \nabla - - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ - MixedL21Norm, BlockFunction, L2NormSquared,\ - KullbackLeibler -from ccpi.framework import TestData -import os, sys -if int(numpy.version.version.split('.')[1]) > 12: - from skimage.util import random_noise -else: - from demoutil import random_noise - -# user supplied input -if len(sys.argv) > 1: - which_noise = int(sys.argv[1]) -else: - which_noise = 0 -print ("Applying {} noise") - -if len(sys.argv) > 2: - method = sys.argv[2] -else: - method = '0' -print ("method ", method) -# Create phantom for TV Salt & Pepper denoising -N = 100 - -loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) -data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,N)) -data = loader.load(TestData.PEPPERS, size=(N,N)) -ig = data.geometry -ag = ig - -# Create noisy data. -# Apply Salt & Pepper noise -# gaussian -# poisson -noises = ['gaussian', 'poisson', 's&p'] -noise = noises[which_noise] -if noise == 's&p': - n1 = random_noise(data.as_array(), mode = noise, salt_vs_pepper = 0.9, amount=0.2) -elif noise == 'poisson': - n1 = random_noise(data.as_array(), mode = noise, seed = 10) -elif noise == 'gaussian': - n1 = random_noise(data.as_array(), mode = noise, seed = 10) -else: - raise ValueError('Unsupported Noise ', noise) -noisy_data = ig.allocate() -noisy_data.fill(n1) -#noisy_data = ImageData(n1) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,5)) -plt.subplot(1,2,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(1,2,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Regularisation Parameter -alpha = .2 - -# fidelity -if noise == 's&p': - f2 = L1Norm(b=noisy_data) -elif noise == 'poisson': - f2 = KullbackLeibler(noisy_data) -elif noise == 'gaussian': - f2 = L2NormSquared(b=noisy_data) - -if method == '0': - - # Create operators - op1 = Gradient(ig, correlation=Gradient.CORRELATION_SPACECHANNEL) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - f1 = alpha * MixedL21Norm() - #f2 = L1Norm(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - #g = L1Norm(b = noisy_data) - g = f2 - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -opt = {'niter':2000, 'memopt': True} - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - -if data.geometry.channels > 1: - plt.figure(figsize=(20,15)) - for row in range(data.geometry.channels): - - plt.subplot(3,4,1+row*4) - plt.imshow(data.subset(channel=row).as_array()) - plt.title('Ground Truth') - plt.colorbar() - plt.subplot(3,4,2+row*4) - plt.imshow(noisy_data.subset(channel=row).as_array()) - plt.title('Noisy Data') - plt.colorbar() - plt.subplot(3,4,3+row*4) - plt.imshow(pdhg.get_output().subset(channel=row).as_array()) - plt.title('TV Reconstruction') - plt.colorbar() - plt.subplot(3,4,4+row*4) - plt.plot(np.linspace(0,N,N), data.subset(channel=row).as_array()[int(N/2),:], label = 'GTruth') - plt.plot(np.linspace(0,N,N), pdhg.get_output().subset(channel=row).as_array()[int(N/2),:], label = 'TV reconstruction') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() -else: - plt.figure(figsize=(20,5)) - plt.subplot(1,4,1) - plt.imshow(data.subset(channel=0).as_array()) - plt.title('Ground Truth') - plt.colorbar() - plt.subplot(1,4,2) - plt.imshow(noisy_data.subset(channel=0).as_array()) - plt.title('Noisy Data') - plt.colorbar() - plt.subplot(1,4,3) - plt.imshow(pdhg.get_output().subset(channel=0).as_array()) - plt.title('TV Reconstruction') - plt.colorbar() - plt.subplot(1,4,4) - plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - -##%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = pnorm( u - noisy_data.as_array(),1) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_2D_time.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_2D_time.py deleted file mode 100644 index 14608db..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_2D_time.py +++ /dev/null @@ -1,192 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient, Identity -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from ccpi.astra.ops import AstraProjectorMC - -import os -import tomophantom -from tomophantom import TomoP2D - -# Create phantom for TV 2D dynamic tomography - -model = 102 # note that the selected model is temporal (2D + time) -N = 128 # set dimension of the phantom -# one can specify an exact path to the parameters file -# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' -path = os.path.dirname(tomophantom.__file__) -path_library2D = os.path.join(path, "Phantom2DLibrary.dat") -#This will generate a N_size x N_size x Time frames phantom (2D + time) -phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) - -plt.close('all') -plt.figure(1) -plt.rcParams.update({'font.size': 21}) -plt.title('{}''{}'.format('2D+t phantom using model no.',model)) -for sl in range(0,np.shape(phantom_2Dt)[0]): - im = phantom_2Dt[sl,:,:] - plt.imshow(im, vmin=0, vmax=1) -# plt.pause(.1) -# plt.draw - - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) -data = ImageData(phantom_2Dt, geometry=ig) -ag = ig - -# Create Noisy data. Add Gaussian noise -np.random.seed(10) -noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.25, size=ig.shape) ) - -tindex = [3, 6, 10] - -fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) -plt.subplot(1,3,1) -plt.imshow(noisy_data.as_array()[tindex[0],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[0])) -plt.subplot(1,3,2) -plt.imshow(noisy_data.as_array()[tindex[1],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[1])) -plt.subplot(1,3,3) -plt.imshow(noisy_data.as_array()[tindex[2],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[2])) - -fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, - wspace=0.02, hspace=0.02) - -plt.show() - -#%% -# Regularisation Parameter -alpha = 0.3 - -# Create operators -#op1 = Gradient(ig) -op1 = Gradient(ig, correlation='Space') -op2 = Gradient(ig, correlation='SpaceChannels') - -op3 = Identity(ig, ag) - -# Create BlockOperator -operator1 = BlockOperator(op1, op3, shape=(2,1) ) -operator2 = BlockOperator(op2, op3, shape=(2,1) ) - -# Create functions - -f1 = alpha * MixedL21Norm() -f2 = 0.5 * L2NormSquared(b = noisy_data) -f = BlockFunction(f1, f2) - -g = ZeroFunction() - -# Compute operator Norm -normK1 = operator1.norm() -normK2 = operator2.norm() - -#%% -# Primal & dual stepsizes -sigma1 = 1 -tau1 = 1/(sigma1*normK1**2) - -sigma2 = 1 -tau2 = 1/(sigma2*normK2**2) - -# Setup and run the PDHG algorithm -pdhg1 = PDHG(f=f,g=g,operator=operator1, tau=tau1, sigma=sigma1) -pdhg1.max_iteration = 2000 -pdhg1.update_objective_interval = 200 -pdhg1.run(2000) - -# Setup and run the PDHG algorithm -pdhg2 = PDHG(f=f,g=g,operator=operator2, tau=tau2, sigma=sigma2) -pdhg2.max_iteration = 2000 -pdhg2.update_objective_interval = 200 -pdhg2.run(2000) - - -#%% - -tindex = [3, 6, 10] -fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) - -plt.subplot(3,3,1) -plt.imshow(phantom_2Dt[tindex[0],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[0])) - -plt.subplot(3,3,2) -plt.imshow(phantom_2Dt[tindex[1],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[1])) - -plt.subplot(3,3,3) -plt.imshow(phantom_2Dt[tindex[2],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[2])) - -plt.subplot(3,3,4) -plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) -plt.axis('off') -plt.subplot(3,3,5) -plt.imshow(pdhg1.get_output().as_array()[tindex[1],:,:]) -plt.axis('off') -plt.subplot(3,3,6) -plt.imshow(pdhg1.get_output().as_array()[tindex[2],:,:]) -plt.axis('off') - - -plt.subplot(3,3,7) -plt.imshow(pdhg2.get_output().as_array()[tindex[0],:,:]) -plt.axis('off') -plt.subplot(3,3,8) -plt.imshow(pdhg2.get_output().as_array()[tindex[1],:,:]) -plt.axis('off') -plt.subplot(3,3,9) -plt.imshow(pdhg2.get_output().as_array()[tindex[2],:,:]) -plt.axis('off') - -#%% -im = plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) - - -fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, - wspace=0.02, hspace=0.02) - -cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) -cbar = fig.colorbar(im, cax=cb_ax) - - -plt.show() - diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py deleted file mode 100644 index 9d00ee1..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian.py +++ /dev/null @@ -1,225 +0,0 @@ -#======================================================================== -# CCP in Tomographic Imaging (CCPi) Core Imaging Library (CIL). - -# Copyright 2017 UKRI-STFC -# Copyright 2017 University of Manchester - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#========================================================================= -""" - -Total Variation Denoising using PDHG algorithm: - - -Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Gaussian Noise - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity] - - - Method = 1 (PDHG - explicit ): K = \nabla - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from ccpi.framework import TestData -import os, sys -loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) - -# Load Data -N = 200 -M = 300 - - -# user can change the size of the input data -# you can choose between -# TestData.PEPPERS 2D + Channel -# TestData.BOAT 2D -# TestData.CAMERA 2D -# TestData.RESOLUTION_CHART 2D -# TestData.SIMPLE_PHANTOM_2D 2D -data = loader.load(TestData.BOAT, size=(N,M), scale=(0,1)) - -ig = data.geometry -ag = ig - -# Create Noisy data. Add Gaussian noise -np.random.seed(10) -noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.1, size=data.shape) ) - -print ("min {} max {}".format(data.as_array().min(), data.as_array().max())) - -# Show Ground Truth and Noisy Data -plt.figure() -plt.subplot(1,3,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(1,3,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(1,3,3) -plt.imshow((data - noisy_data).as_array()) -plt.title('diff') -plt.colorbar() - -plt.show() - -# Regularisation Parameter -alpha = .1 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig, correlation=Gradient.CORRELATION_SPACE) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - f1 = alpha * MixedL21Norm() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = 0.5 * L2NormSquared(b = noisy_data) - -# Compute Operator Norm -normK = operator.norm() - -# Primal & Dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -# Setup and Run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 10000 -pdhg.update_objective_interval = 100 -pdhg.run(1000, verbose=True) - -# Show Results -plt.figure() -plt.subplot(1,3,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.clim(0,1) -plt.subplot(1,3,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.clim(0,1) -plt.subplot(1,3,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.clim(0,1) -plt.colorbar() -plt.show() - -plt.plot(np.linspace(0,N,M), noisy_data.as_array()[int(N/2),:], label = 'Noisy data') -plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') - -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = MOSEK) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,M), u.value[int(N/2),:], label = 'CVX') - plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'Truth') - - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py deleted file mode 100644 index 03dc2ef..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Gaussian_3D.py +++ /dev/null @@ -1,181 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= -""" - -Total Variation (3D) Denoising using PDHG algorithm: - - -Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Gaussian Noise - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity] - - - Method = 1 (PDHG - explicit ): K = \nabla - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Gaussian denoising -import timeit -import os -from tomophantom import TomoP3D -import tomophantom - -print ("Building 3D phantom using TomoPhantom software") -tic=timeit.default_timer() -model = 13 # select a model number from the library -N = 64 # Define phantom dimensions using a scalar value (cubic phantom) -path = os.path.dirname(tomophantom.__file__) -path_library3D = os.path.join(path, "Phantom3DLibrary.dat") - -#This will generate a N x N x N phantom (3D) -phantom_tm = TomoP3D.Model(model, N, path_library3D) - -#%% - -# Create noisy data. Add Gaussian noise -ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N, voxel_num_z=N) -ag = ig -n1 = random_noise(phantom_tm, mode = 'gaussian', mean=0, var = 0.001, seed=10) -noisy_data = ImageData(n1) - -sliceSel = int(0.5*N) -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) -plt.title('Axial View') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) -plt.title('Coronal View') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) -plt.title('Sagittal View') -plt.colorbar() -plt.show() - -#%% - -# Regularisation Parameter -alpha = 0.05 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * MixedL21Norm() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = 0.5 * L2NormSquared(b = noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000, verbose = True) - - -#%% -fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) -fig.suptitle('TV Reconstruction',fontsize=20) - - -plt.subplot(2,3,1) -plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Axial View') - -plt.subplot(2,3,2) -plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Coronal View') - -plt.subplot(2,3,3) -plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) -plt.axis('off') -plt.title('Sagittal View') - - -plt.subplot(2,3,4) -plt.imshow(pdhg.get_output().as_array()[sliceSel,:,:],vmin=0, vmax=1) -plt.axis('off') -plt.subplot(2,3,5) -plt.imshow(pdhg.get_output().as_array()[:,sliceSel,:],vmin=0, vmax=1) -plt.axis('off') -plt.subplot(2,3,6) -plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) -plt.axis('off') -im = plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) - - -fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, - wspace=0.02, hspace=0.02) - -cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) -cbar = fig.colorbar(im, cax=cb_ax) - - -plt.show() - diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Poisson.py deleted file mode 100644 index 1d887c1..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_Poisson.py +++ /dev/null @@ -1,212 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation Denoising using PDHG algorithm: - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + \int x - g * log(x) - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Poisson Noise - - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity] - - - Method = 1 (PDHG - explicit ): K = \nabla - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, IndicatorBox, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Poisson denoising -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Apply Poisson noise -n1 = random_noise(data.as_array(), mode = 'poisson', seed = 10) -noisy_data = ImageData(n1) - - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,10)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - - -# Regularisation Parameter -alpha = 2 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * MixedL21Norm() - f2 = KullbackLeibler(noisy_data) - f = BlockFunction(f1, f2) - g = IndicatorBox(lower=0) - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = KullbackLeibler(noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & Dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -# Setup and Run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 3000 -pdhg.update_objective_interval = 200 -pdhg.run(3000, verbose=True) - - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u1 = Variable(ig.shape) - q = Variable() - w = Variable() - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) - fidelity = sum(kl_div(noisy_data.as_array(), u1)) - - constraints = [q>=fidelity, u1>=0] - - solver = ECOS - obj = Minimize( regulariser + q) - prob = Problem(obj, constraints) - result = prob.solve(verbose = True, solver = solver) - - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u1.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u1.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_SaltPepper.py deleted file mode 100644 index c5709c3..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Denoising_SaltPepper.py +++ /dev/null @@ -1,213 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation Denoising using PDHG algorithm: - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + ||x-g||_{1} - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Salt & Pepper Noise - - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity] - - - Method = 1 (PDHG - explicit ): K = \nabla - - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Salt & Pepper denoising -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Apply Salt & Pepper noise -n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) -noisy_data = ImageData(n1) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(15,15)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Regularisation Parameter -alpha = 2 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - f1 = alpha * MixedL21Norm() - f2 = L1Norm(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = L1Norm(b = noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -opt = {'niter':2000, 'memopt': True} - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -##%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = pnorm( u - noisy_data.as_array(),1) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_gaussian.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_gaussian.py deleted file mode 100644 index 6acbfcc..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_gaussian.py +++ /dev/null @@ -1,212 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation 2D Tomography Reconstruction using PDHG algorithm: - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + \frac{1}{2}||Ax - g||^{2} - - \nabla: Gradient operator - - A: Projection Matrix - g: Noisy sinogram corrupted with Gaussian Noise - - \alpha: Regularization parameter - -""" - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from ccpi.astra.ops import AstraProjectorSimple - - - -# Create phantom for TV 2D tomography -N = 100 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -detectors = N -angles = np.linspace(0, np.pi, N) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) - -device = input('Available device: GPU==1 / CPU==0 ') - -if device=='1': - dev = 'gpu' -else: - dev = 'cpu' - -Aop = AstraProjectorSimple(ig, ag, dev) -sin = Aop.direct(data) - -# Create noisy data. Apply Poisson noise -n1 = np.random.normal(0, 3, size=ig.shape) -noisy_data = AcquisitionData(n1 + sin.as_array(), ag) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,10)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Regularisation Parameter -alpha = 50 - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Create BlockOperator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -# Create functions - -f1 = alpha * MixedL21Norm() -f2 = 0.5 * L2NormSquared(b=noisy_data) -f = BlockFunction(f1, f2) - -g = ZeroFunction() - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 10 -tau = 1/(sigma*normK**2) - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000) - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff -import astra -import numpy - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - -if cvx_not_installable: - - ##Construct problem - u = Variable(N*N) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - - # create matrix representation for Astra operator - vol_geom = astra.create_vol_geom(N, N) - proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) - - proj_id = astra.create_projector('strip', proj_geom, vol_geom) - - matrix_id = astra.projector.matrix(proj_id) - - ProjMat = astra.matrix.get(matrix_id) - - tmp = noisy_data.as_array().ravel() - - fidelity = 0.5 * sum_squares(ProjMat * u - tmp) - - solver = MOSEK - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - np.reshape(u.value, (N,N) )) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(np.reshape(u.value, (N, N))) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N,N) )[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py deleted file mode 100644 index c44f393..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Tomo2D_poisson.py +++ /dev/null @@ -1,226 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -from ccpi.framework import AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import KullbackLeibler, \ - MixedL21Norm, BlockFunction, IndicatorBox - -from ccpi.astra.ops import AstraProjectorSimple -from ccpi.framework import TestData -import os, sys - -""" - -Total Variation Denoising using PDHG algorithm: - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + int A x -g log(Ax + \eta) - - \nabla: Gradient operator - - A: Projection Matrix - g: Noisy sinogram corrupted with Poisson Noise - - \eta: Background Noise - \alpha: Regularization parameter - -""" - -loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) - -# Load Data -N = 50 -M = 50 -data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) - -ig = data.geometry -ag = ig - -#Create Acquisition Data and apply poisson noise - -detectors = N -angles = np.linspace(0, np.pi, N) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) - -device = input('Available device: GPU==1 / CPU==0 ') - -if device=='1': - dev = 'gpu' -else: - dev = 'cpu' - -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -# Create noisy data. Apply Poisson noise -scale = 0.5 -eta = 0 -n1 = scale * np.random.poisson(eta + sin.as_array()/scale) - -noisy_data = AcquisitionData(n1, ag) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,10)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Regularisation Parameter -alpha = 2 - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Create BlockOperator -operator = BlockOperator(op1,op2, shape=(2,1) ) - -# Create functions - -f1 = alpha * MixedL21Norm() -f2 = KullbackLeibler(noisy_data) -f = BlockFunction(f1, f2) - -g = IndicatorBox(lower=0) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 3000 -pdhg.update_objective_interval = 500 -pdhg.run(3000, verbose = True) - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution -# -from ccpi.optimisation.operators import SparseFiniteDiff -import astra -import numpy - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - - ##Construct problem - u = Variable(N*N) - q = Variable() - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - - # create matrix representation for Astra operator - - vol_geom = astra.create_vol_geom(N, N) - proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) - - proj_id = astra.create_projector('strip', proj_geom, vol_geom) - - matrix_id = astra.projector.matrix(proj_id) - - ProjMat = astra.matrix.get(matrix_id) - - tmp = noisy_data.as_array().ravel() - - fidelity = sum(kl_div(tmp, ProjMat * u)) - - constraints = [q>=fidelity, u>=0] - solver = SCS - obj = Minimize( regulariser + q) - prob = Problem(obj, constraints) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = np.abs(pdhg.get_output().as_array() - np.reshape(u.value, (N, N))) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(np.reshape(u.value, (N, N))) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N, N))[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Denoising.py b/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Denoising.py deleted file mode 100644 index f00f1cc..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Denoising.py +++ /dev/null @@ -1,256 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= -""" - -Tikhonov Denoising using PDHG algorithm: - - -Problem: min_{x} \alpha * ||\nabla x||_{2}^{2} + \frac{1}{2} * || x - g ||_{2}^{2} - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Gaussian Noise - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity] - - - Method = 1 (PDHG - explicit ): K = \nabla - -""" - -from ccpi.framework import ImageData, ImageGeometry, TestData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared,\ - BlockFunction, KullbackLeibler, L1Norm - -import sys, os -if int(numpy.version.version.split('.')[1]) > 12: - from skimage.util import random_noise -else: - from demoutil import random_noise - - -# user supplied input -if len(sys.argv) > 1: - which_noise = int(sys.argv[1]) -else: - which_noise = 0 -print ("Applying {} noise") - -if len(sys.argv) > 2: - method = sys.argv[2] -else: - method = '0' -print ("method ", method) - -# Create phantom for TV Salt & Pepper denoising -N = 100 - -loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) -data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,N)) -ig = data.geometry -ag = ig - -# Create noisy data. Apply Salt & Pepper noise -# Create noisy data. -# Apply Salt & Pepper noise -# gaussian -# poisson -noises = ['gaussian', 'poisson', 's&p'] -noise = noises[which_noise] -if noise == 's&p': - n1 = random_noise(data.as_array(), mode = noise, salt_vs_pepper = 0.9, amount=0.2) -elif noise == 'poisson': - n1 = random_noise(data.as_array(), mode = noise, seed = 10) -elif noise == 'gaussian': - n1 = random_noise(data.as_array(), mode = noise, seed = 10) -else: - raise ValueError('Unsupported Noise ', noise) -noisy_data = ImageData(n1) - -# fidelity -if noise == 's&p': - f2 = L1Norm(b=noisy_data) -elif noise == 'poisson': - f2 = KullbackLeibler(noisy_data) -elif noise == 'gaussian': - f2 = 0.5 * L2NormSquared(b=noisy_data) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,5)) -plt.subplot(1,2,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(1,2,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - - -# Regularisation Parameter -# no edge preservation alpha is big -if noise == 's&p': - alpha = 8. -elif noise == 'poisson': - alpha = 8. -elif noise == 'gaussian': - alpha = 8. - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * L2NormSquared() - # f2 must change according to noise - #f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * L2NormSquared() - # g must change according to noise - #g = 0.5 * L2NormSquared(b = noisy_data) - g = f2 - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -opt = {'niter':2000, 'memopt': True} - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(1500) - - -plt.figure(figsize=(20,5)) -plt.subplot(1,4,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(1,4,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(1,4,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('Tikhonov Reconstruction') -plt.colorbar() -plt.subplot(1,4,4) -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - - -##%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - - regulariser = alpha * sum_squares(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Tomo2D.py b/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Tomo2D.py deleted file mode 100644 index 02cd053..0000000 --- a/Wrappers/Python/demos/PDHG_examples/PDHG_Tikhonov_Tomo2D.py +++ /dev/null @@ -1,156 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation Denoising using PDHG algorithm: - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2}^{2} + int A x -g log(Ax + \eta) - - \nabla: Gradient operator - - A: Projection Matrix - g: Noisy sinogram corrupted with Poisson Noise - - \eta: Background Noise - \alpha: Regularization parameter - - - -""" - - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction - -from ccpi.astra.ops import AstraProjectorSimple -from ccpi.framework import TestData -import os, sys - -loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) - -# Load Data -N = 100 -M = 100 -data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) - -ig = data.geometry -ag = ig - -#Create Acquisition Data and apply poisson noise - -detectors = N -angles = np.linspace(0, np.pi, N) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) - -device = input('Available device: GPU==1 / CPU==0 ') - -if device=='1': - dev = 'gpu' -else: - dev = 'cpu' - -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -# Create noisy data. Apply Poisson noise -scale = 0.5 -eta = 0 -n1 = scale * np.random.poisson(eta + sin.as_array()/scale) - -noisy_data = AcquisitionData(n1, ag) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,10)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - - -# Regularisation Parameter -alpha = 1000 - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Create BlockOperator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -# Create functions - -f1 = alpha * L2NormSquared() -f2 = 0.5 * L2NormSquared(b=noisy_data) -f = BlockFunction(f1, f2) - -g = ZeroFunction() - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 500 -pdhg.run(2000) - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('Tikhonov Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -- cgit v1.2.3 From a8a53b23990a1cedc77c182f17045b060fa50129 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 23 May 2019 20:20:00 +0100 Subject: delete algs/funcs --- Wrappers/Python/ccpi/optimisation/algs.py | 307 ----------------------------- Wrappers/Python/ccpi/optimisation/funcs.py | 265 ------------------------- 2 files changed, 572 deletions(-) delete mode 100755 Wrappers/Python/ccpi/optimisation/algs.py delete mode 100755 Wrappers/Python/ccpi/optimisation/funcs.py diff --git a/Wrappers/Python/ccpi/optimisation/algs.py b/Wrappers/Python/ccpi/optimisation/algs.py deleted file mode 100755 index f5ba85e..0000000 --- a/Wrappers/Python/ccpi/optimisation/algs.py +++ /dev/null @@ -1,307 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy -import time - - - - -def FISTA(x_init, f=None, g=None, opt=None): - '''Fast Iterative Shrinkage-Thresholding Algorithm - - Beck, A. and Teboulle, M., 2009. A fast iterative shrinkage-thresholding - algorithm for linear inverse problems. - SIAM journal on imaging sciences,2(1), pp.183-202. - - Parameters: - x_init: initial guess - f: data fidelity - g: regularizer - h: - opt: additional algorithm - ''' - # default inputs - if f is None: f = ZeroFun() - if g is None: g = ZeroFun() - - # algorithmic parameters - if opt is None: - opt = {'tol': 1e-4, 'iter': 1000, 'memopt':False} - - max_iter = opt['iter'] if 'iter' in opt.keys() else 1000 - tol = opt['tol'] if 'tol' in opt.keys() else 1e-4 - memopt = opt['memopt'] if 'memopt' in opt.keys() else False - - - # initialization - if memopt: - y = x_init.clone() - x_old = x_init.clone() - x = x_init.clone() - u = x_init.clone() - else: - x_old = x_init - y = x_init; - - timing = numpy.zeros(max_iter) - criter = numpy.zeros(max_iter) - - invL = 1/f.L - - t_old = 1 - -# c = f(x_init) + g(x_init) - - # algorithm loop - for it in range(0, max_iter): - - time0 = time.time() - if memopt: - # u = y - invL*f.grad(y) - # store the result in x_old - f.gradient(y, out=u) - u.__imul__( -invL ) - u.__iadd__( y ) - # x = g.prox(u,invL) - g.proximal(u, invL, out=x) - - t = 0.5*(1 + numpy.sqrt(1 + 4*(t_old**2))) - - # y = x + (t_old-1)/t*(x-x_old) - x.subtract(x_old, out=y) - y.__imul__ ((t_old-1)/t) - y.__iadd__( x ) - - x_old.fill(x) - t_old = t - - - else: - u = y - invL*f.gradient(y) - - x = g.proximal(u,invL) - - t = 0.5*(1 + numpy.sqrt(1 + 4*(t_old**2))) - - y = x + (t_old-1)/t*(x-x_old) - - x_old = x.copy() - t_old = t - - # time and criterion -# timing[it] = time.time() - time0 -# criter[it] = f(x) + g(x); - - # stopping rule - #if np.linalg.norm(x - x_old) < tol * np.linalg.norm(x_old) and it > 10: - # break - - #print(it, 'out of', 10, 'iterations', end='\r'); - - #criter = criter[0:it+1]; -# timing = numpy.cumsum(timing[0:it+1]); - - return x #, it, timing, criter - -def FBPD(x_init, operator=None, constraint=None, data_fidelity=None,\ - regulariser=None, opt=None): - '''FBPD Algorithm - - Parameters: - x_init: initial guess - f: constraint - g: data fidelity - h: regularizer - opt: additional algorithm - ''' - # default inputs - if constraint is None: constraint = ZeroFun() - if data_fidelity is None: data_fidelity = ZeroFun() - if regulariser is None: regulariser = ZeroFun() - - # algorithmic parameters - if opt is None: - opt = {'tol': 1e-4, 'iter': 1000} - else: - try: - max_iter = opt['iter'] - except KeyError as ke: - opt[ke] = 1000 - try: - opt['tol'] = 1000 - except KeyError as ke: - opt[ke] = 1e-4 - tol = opt['tol'] - max_iter = opt['iter'] - memopt = opt['memopts'] if 'memopts' in opt.keys() else False - - # step-sizes - tau = 2 / (data_fidelity.L + 2) - sigma = (1/tau - data_fidelity.L/2) / regulariser.L - inv_sigma = 1/sigma - - # initialization - x = x_init - y = operator.direct(x); - - timing = numpy.zeros(max_iter) - criter = numpy.zeros(max_iter) - - - - - # algorithm loop - for it in range(0, max_iter): - - t = time.time() - - # primal forward-backward step - x_old = x; - x = x - tau * ( data_fidelity.grad(x) + operator.adjoint(y) ); - x = constraint.prox(x, tau); - - # dual forward-backward step - y = y + sigma * operator.direct(2*x - x_old); - y = y - sigma * regulariser.prox(inv_sigma*y, inv_sigma); - - # time and criterion - timing[it] = time.time() - t - criter[it] = constraint(x) + data_fidelity(x) + regulariser(operator.direct(x)) - - # stopping rule - #if np.linalg.norm(x - x_old) < tol * np.linalg.norm(x_old) and it > 10: - # break - - criter = criter[0:it+1] - timing = numpy.cumsum(timing[0:it+1]) - - return x, it, timing, criter - -def CGLS(x_init, operator , data , opt=None): - '''Conjugate Gradient Least Squares algorithm - - Parameters: - x_init: initial guess - operator: operator for forward/backward projections - data: data to operate on - opt: additional algorithm - ''' - - if opt is None: - opt = {'tol': 1e-4, 'iter': 1000} - else: - try: - max_iter = opt['iter'] - except KeyError as ke: - opt[ke] = 1000 - try: - opt['tol'] = 1000 - except KeyError as ke: - opt[ke] = 1e-4 - tol = opt['tol'] - max_iter = opt['iter'] - - r = data.copy() - x = x_init.copy() - - d = operator.adjoint(r) - - normr2 = (d**2).sum() - - timing = numpy.zeros(max_iter) - criter = numpy.zeros(max_iter) - - # algorithm loop - for it in range(0, max_iter): - - t = time.time() - - Ad = operator.direct(d) - alpha = normr2/( (Ad**2).sum() ) - x = x + alpha*d - r = r - alpha*Ad - s = operator.adjoint(r) - - normr2_new = (s**2).sum() - beta = normr2_new/normr2 - normr2 = normr2_new - d = s + beta*d - - # time and criterion - timing[it] = time.time() - t - criter[it] = (r**2).sum() - - return x, it, timing, criter - -def SIRT(x_init, operator , data , opt=None, constraint=None): - '''Simultaneous Iterative Reconstruction Technique - - Parameters: - x_init: initial guess - operator: operator for forward/backward projections - data: data to operate on - opt: additional algorithm - constraint: func of Indicator type specifying convex constraint. - ''' - - if opt is None: - opt = {'tol': 1e-4, 'iter': 1000} - else: - try: - max_iter = opt['iter'] - except KeyError as ke: - opt[ke] = 1000 - try: - opt['tol'] = 1000 - except KeyError as ke: - opt[ke] = 1e-4 - tol = opt['tol'] - max_iter = opt['iter'] - - x = x_init.clone() - - timing = numpy.zeros(max_iter) - criter = numpy.zeros(max_iter) - - # Relaxation parameter must be strictly between 0 and 2. For now fix at 1.0 - relax_par = 1.0 - - # Set up scaling matrices D and M. - M = 1/operator.direct(operator.domain_geometry().allocate(value=1.0)) - D = 1/operator.adjoint(operator.range_geometry().allocate(value=1.0)) - - # algorithm loop - for it in range(0, max_iter): - t = time.time() - r = data - operator.direct(x) - - x = x + relax_par * (D*operator.adjoint(M*r)) - - if constraint != None: - x = constraint.prox(x,None) - - timing[it] = time.time() - t - if it > 0: - criter[it-1] = (r**2).sum() - - r = data - operator.direct(x) - criter[it] = (r**2).sum() - return x, it, timing, criter - diff --git a/Wrappers/Python/ccpi/optimisation/funcs.py b/Wrappers/Python/ccpi/optimisation/funcs.py deleted file mode 100755 index b2b9791..0000000 --- a/Wrappers/Python/ccpi/optimisation/funcs.py +++ /dev/null @@ -1,265 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018 Jakob Jorgensen, Daniil Kazantsev and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy -from ccpi.framework import DataContainer -import warnings -from ccpi.optimisation.functions import Function -def isSizeCorrect(data1 ,data2): - if issubclass(type(data1), DataContainer) and \ - issubclass(type(data2), DataContainer): - # check dimensionality - if data1.check_dimensions(data2): - return True - elif issubclass(type(data1) , numpy.ndarray) and \ - issubclass(type(data2) , numpy.ndarray): - return data1.shape == data2.shape - else: - raise ValueError("{0}: getting two incompatible types: {1} {2}"\ - .format('Function', type(data1), type(data2))) - return False -class Norm2(Function): - - def __init__(self, - gamma=1.0, - direction=None): - super(Norm2, self).__init__() - self.gamma = gamma; - self.direction = direction; - - def __call__(self, x, out=None): - - if out is None: - xx = numpy.sqrt(numpy.sum(numpy.square(x.as_array()), self.direction, - keepdims=True)) - else: - if isSizeCorrect(out, x): - # check dimensionality - if issubclass(type(out), DataContainer): - arr = out.as_array() - numpy.square(x.as_array(), out=arr) - xx = numpy.sqrt(numpy.sum(arr, self.direction, keepdims=True)) - - elif issubclass(type(out) , numpy.ndarray): - numpy.square(x.as_array(), out=out) - xx = numpy.sqrt(numpy.sum(out, self.direction, keepdims=True)) - else: - raise ValueError ('Wrong size: x{0} out{1}'.format(x.shape,out.shape) ) - - p = numpy.sum(self.gamma*xx) - - return p - - def prox(self, x, tau): - - xx = numpy.sqrt(numpy.sum( numpy.square(x.as_array()), self.direction, - keepdims=True )) - xx = numpy.maximum(0, 1 - tau*self.gamma / xx) - p = x.as_array() * xx - - return type(x)(p,geometry=x.geometry) - def proximal(self, x, tau, out=None): - if out is None: - return self.prox(x,tau) - else: - if isSizeCorrect(out, x): - # check dimensionality - if issubclass(type(out), DataContainer): - numpy.square(x.as_array(), out = out.as_array()) - xx = numpy.sqrt(numpy.sum( out.as_array() , self.direction, - keepdims=True )) - xx = numpy.maximum(0, 1 - tau*self.gamma / xx) - x.multiply(xx, out= out.as_array()) - - - elif issubclass(type(out) , numpy.ndarray): - numpy.square(x.as_array(), out=out) - xx = numpy.sqrt(numpy.sum(out, self.direction, keepdims=True)) - - xx = numpy.maximum(0, 1 - tau*self.gamma / xx) - x.multiply(xx, out= out) - else: - raise ValueError ('Wrong size: x{0} out{1}'.format(x.shape,out.shape) ) - - - - -# Define a class for squared 2-norm -class Norm2sq(Function): - ''' - f(x) = c*||A*x-b||_2^2 - - which has - - grad[f](x) = 2*c*A^T*(A*x-b) - - and Lipschitz constant - - L = 2*c*||A||_2^2 = 2*s1(A)^2 - - where s1(A) is the largest singular value of A. - - ''' - - def __init__(self,A,b,c=1.0,memopt=False): - super(Norm2sq, self).__init__() - - self.A = A # Should be an operator, default identity - self.b = b # Default zero DataSet? - self.c = c # Default 1. - if memopt: - try: - self.range_tmp = A.range_geometry().allocate() - self.domain_tmp = A.domain_geometry().allocate() - self.memopt = True - except NameError as ne: - warnings.warn(str(ne)) - self.memopt = False - except NotImplementedError as nie: - print (nie) - warnings.warn(str(nie)) - self.memopt = False - else: - self.memopt = False - - # Compute the Lipschitz parameter from the operator if possible - # Leave it initialised to None otherwise - try: - self.L = 2.0*self.c*(self.A.norm()**2) - except AttributeError as ae: - pass - except NotImplementedError as noe: - pass - - #def grad(self,x): - # return self.gradient(x, out=None) - - def __call__(self,x): - #return self.c* np.sum(np.square((self.A.direct(x) - self.b).ravel())) - #if out is None: - # return self.c*( ( (self.A.direct(x)-self.b)**2).sum() ) - #else: - y = self.A.direct(x) - y.__isub__(self.b) - #y.__imul__(y) - #return y.sum() * self.c - try: - return y.squared_norm() * self.c - except AttributeError as ae: - # added for compatibility with SIRF - return (y.norm()**2) * self.c - - def gradient(self, x, out = None): - if self.memopt: - #return 2.0*self.c*self.A.adjoint( self.A.direct(x) - self.b ) - - self.A.direct(x, out=self.range_tmp) - self.range_tmp -= self.b - self.A.adjoint(self.range_tmp, out=out) - #self.direct_placehold.multiply(2.0*self.c, out=out) - out *= (self.c * 2.0) - else: - return (2.0*self.c)*self.A.adjoint( self.A.direct(x) - self.b ) - - - -# Box constraints indicator function. Calling returns 0 if argument is within -# the box. The prox operator is projection onto the box. Only implements one -# scalar lower and one upper as constraint on all elements. Should generalise -# to vectors to allow different constraints one elements. -class IndicatorBox(Function): - - def __init__(self,lower=-numpy.inf,upper=numpy.inf): - # Do nothing - super(IndicatorBox, self).__init__() - self.lower = lower - self.upper = upper - - - def __call__(self,x): - - if (numpy.all(x.array>=self.lower) and - numpy.all(x.array <= self.upper) ): - val = 0 - else: - val = numpy.inf - return val - - def prox(self,x,tau=None): - return (x.maximum(self.lower)).minimum(self.upper) - - def proximal(self, x, tau, out=None): - if out is None: - return self.prox(x, tau) - else: - if not x.shape == out.shape: - raise ValueError('Norm1 Incompatible size:', - x.shape, out.shape) - #(x.abs() - tau*self.gamma).maximum(0) * x.sign() - x.abs(out = out) - out.__isub__(tau*self.gamma) - out.maximum(0, out=out) - if self.sign_x is None or not x.shape == self.sign_x.shape: - self.sign_x = x.sign() - else: - x.sign(out=self.sign_x) - - out.__imul__( self.sign_x ) - -# A more interesting example, least squares plus 1-norm minimization. -# Define class to represent 1-norm including prox function -class Norm1(Function): - - def __init__(self,gamma): - super(Norm1, self).__init__() - self.gamma = gamma - self.L = 1 - self.sign_x = None - - def __call__(self,x,out=None): - if out is None: - return self.gamma*(x.abs().sum()) - else: - if not x.shape == out.shape: - raise ValueError('Norm1 Incompatible size:', - x.shape, out.shape) - x.abs(out=out) - return out.sum() * self.gamma - - def prox(self,x,tau): - return (x.abs() - tau*self.gamma).maximum(0) * x.sign() - - def proximal(self, x, tau, out=None): - if out is None: - return self.prox(x, tau) - else: - if isSizeCorrect(x,out): - # check dimensionality - if issubclass(type(out), DataContainer): - v = (x.abs() - tau*self.gamma).maximum(0) - x.sign(out=out) - out *= v - #out.fill(self.prox(x,tau)) - elif issubclass(type(out) , numpy.ndarray): - v = (x.abs() - tau*self.gamma).maximum(0) - out[:] = x.sign() - out *= v - #out[:] = self.prox(x,tau) - else: - raise ValueError ('Wrong size: x{0} out{1}'.format(x.shape,out.shape) ) -- cgit v1.2.3 From d51ad29fc0a79d908f5cff32b9500d1be1f3a7ba Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 23 May 2019 20:20:24 +0100 Subject: fista/cgls cmp --- Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py b/Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py index 0c12cbb..68fb649 100644 --- a/Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py +++ b/Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py @@ -101,12 +101,12 @@ Aop = AstraProjectorSimple(ig, ag, dev) sin = Aop.direct(data) back_proj = Aop.adjoint(sin) -plt.figure() +plt.figure(figsize=(5,5)) plt.imshow(sin.array) plt.title('Simulated data') plt.show() -plt.figure() +plt.figure(figsize=(5,5)) plt.imshow(back_proj.array) plt.title('Backprojected data') plt.show() @@ -117,7 +117,7 @@ plt.show() # demonstrated in the rest of this file. In general all methods need an initial # guess and some algorithm options to be set: -f = FunctionOperatorComposition(L2NormSquared(b=sin), Aop) +f = FunctionOperatorComposition(0.5 * L2NormSquared(b=sin), Aop) g = ZeroFunction() x_init = ig.allocate() @@ -126,7 +126,7 @@ opt = {'memopt':True} fista = FISTA(x_init=x_init, f=f, g=g, opt=opt) fista.max_iteration = 100 fista.update_objective_interval = 1 -fista.run(100, verbose=False) +fista.run(100, verbose=True) plt.figure() plt.imshow(fista.get_output().as_array()) @@ -141,11 +141,11 @@ plt.show() #%% -# Run FISTA for least squares with lower bound 0.1 -fista0 = FISTA(x_init=x_init, f=f, g=IndicatorBox(lower=0, upper=1), opt=opt) +# Run FISTA for least squares with lower/upper bound +fista0 = FISTA(x_init=x_init, f=f, g=IndicatorBox(lower=0,upper=1), opt=opt) fista0.max_iteration = 100 fista0.update_objective_interval = 1 -fista0.run(100, verbose=False) +fista0.run(100, verbose=True) plt.figure() plt.imshow(fista0.get_output().as_array()) -- cgit v1.2.3 From b7ee3884a62cfd1ce0fe13e98a1743c1bba533fe Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Fri, 31 May 2019 17:39:36 +0100 Subject: add colorbay demo --- .../Python/demos/PDHG_examples/ColorbayDemo.py | 266 +++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py diff --git a/Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py b/Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py new file mode 100644 index 0000000..0a2126e --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py @@ -0,0 +1,266 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + + +from ccpi.framework import ImageGeometry, ImageData, AcquisitionGeometry, AcquisitionData, BlockDataContainer + +import numpy as numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG, CGLS +from ccpi.optimisation.algs import CGLS as CGLS_old + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.operators import AstraProjectorMC +from scipy.io import loadmat +import h5py + +#%% + +phantom = 'powder' + +if phantom == 'carbon': + pathname = '/media/newhd/shared/Data/ColourBay/spectral_data_sets/CarbonPd/' + filename = 'carbonPd_full_sinogram_stripes_removed.mat' + X = loadmat(pathname + filename) + X = numpy.transpose(X['SS'],(3,1,2,0)) + +elif phantom == 'powder': + pathname = '/media/newhd/shared/DataProcessed/' + filename = 'S_180.mat' + path = pathname + filename + arrays = {} + f = h5py.File(path) + for k, v in f.items(): + arrays[k] = numpy.array(v) + XX = arrays['S'] + X = numpy.transpose(XX,(0,2,1,3)) + X = X[0:250] + + + +#%% Setup Geometry of Colorbay + +num_channels = X.shape[0] +num_pixels_h = X.shape[3] +num_pixels_v = X.shape[2] +num_angles = X.shape[1] + +# Display a single projection in a single channel +plt.imshow(X[100,5,:,:]) +plt.title('Example of a projection image in one channel' ) +plt.show() + +# Set angles to use +angles = numpy.linspace(-numpy.pi,numpy.pi,num_angles,endpoint=False) + +# Define full 3D acquisition geometry and data container. +# Geometric info is taken from the txt-file in the same dir as the mat-file +ag = AcquisitionGeometry('cone', + '3D', + angles, + pixel_num_h=num_pixels_h, + pixel_size_h=0.25, + pixel_num_v=num_pixels_v, + pixel_size_v=0.25, + dist_source_center=233.0, + dist_center_detector=245.0, + channels=num_channels) +data = AcquisitionData(X, geometry=ag) + +# Reduce to central slice by extracting relevant parameters from data and its +# geometry. Perhaps create function to extract central slice automatically? +data2d = data.subset(vertical=40) +ag2d = AcquisitionGeometry('cone', + '2D', + ag.angles, + pixel_num_h=ag.pixel_num_h, + pixel_size_h=ag.pixel_size_h, + pixel_num_v=1, + pixel_size_v=ag.pixel_size_h, + dist_source_center=ag.dist_source_center, + dist_center_detector=ag.dist_center_detector, + channels=ag.channels) +data2d.geometry = ag2d + +# Set up 2D Image Geometry. +# First need the geometric magnification to scale the voxel size relative +# to the detector pixel size. +mag = (ag.dist_source_center + ag.dist_center_detector)/ag.dist_source_center +ig2d = ImageGeometry(voxel_num_x=ag2d.pixel_num_h, + voxel_num_y=ag2d.pixel_num_h, + voxel_size_x=ag2d.pixel_size_h/mag, + voxel_size_y=ag2d.pixel_size_h/mag, + channels=X.shape[0]) + +# Create GPU multichannel projector/backprojector operator with ASTRA. +Aall = AstraProjectorMC(ig2d,ag2d,'gpu') + +# Compute and simple backprojction and display one channel as image. +Xbp = Aall.adjoint(data2d) +plt.imshow(Xbp.subset(channel=100).array) +plt.show() + +#%% CGLS + +x_init = ig2d.allocate() +cgls1 = CGLS(x_init=x_init, operator=Aall, data=data2d) +cgls1.max_iteration = 100 +cgls1.update_objective_interval = 1 +cgls1.run(5,verbose=True) + +plt.imshow(cgls1.get_output().subset(channel=100).array) +plt.title('CGLS') +plt.show() + +#%% Tikhonov + +alpha = 2.5 +Grad = Gradient(ig2d, correlation=Gradient.CORRELATION_SPACE) # use also CORRELATION_SPACECHANNEL + +# Form Tikhonov as a Block CGLS structure +op_CGLS = BlockOperator( Aall, alpha * Grad, shape=(2,1)) +block_data = BlockDataContainer(data2d, Grad.range_geometry().allocate()) + +cgls2 = CGLS(x_init=x_init, operator=op_CGLS, data=block_data) +cgls2.max_iteration = 100 +cgls2.update_objective_interval = 1 + +cgls2.run(10,verbose=True) + +plt.imshow(cgls2.get_output().subset(channel=100).array) +plt.title('Tikhonov') +plt.show() + +#%% Total Variation + +# Regularisation Parameter +alpha_TV = 50 + +# Create operators +op1 = Gradient(ig2d, correlation=Gradient.CORRELATION_SPACE) +op2 = Aall + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Create functions +f1 = alpha * MixedL21Norm() +f2 = 0.5 * L2NormSquared(b=data2d) +f = BlockFunction(f1, f2) +g = ZeroFunction() + +# Compute operator Norm +normK = 8.70320267279591 # Run one time no need to compute again takes time + +# Primal & dual stepsizes +sigma = 10 +tau = 1/(sigma*normK**2) + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 100 +pdhg.run(1000, verbose =True) + + +#%% Show sinograms +channel_ind = [25,75,125] + +plt.figure(figsize=(15,15)) + +plt.subplot(4,3,1) +plt.imshow(data2d.subset(channel = channel_ind[0]).as_array()) +plt.title('Channel {}'.format(channel_ind[0])) +plt.colorbar() + +plt.subplot(4,3,2) +plt.imshow(data2d.subset(channel = channel_ind[1]).as_array()) +plt.title('Channel {}'.format(channel_ind[1])) +plt.colorbar() + +plt.subplot(4,3,3) +plt.imshow(data2d.subset(channel = channel_ind[2]).as_array()) +plt.title('Channel {}'.format(channel_ind[2])) +plt.colorbar() + +############################################################################### +# Show CGLS +plt.subplot(4,3,4) +plt.imshow(cgls1.get_output().subset(channel = channel_ind[0]).as_array()) +plt.colorbar() + +plt.subplot(4,3,5) +plt.imshow(cgls1.get_output().subset(channel = channel_ind[1]).as_array()) +plt.colorbar() + +plt.subplot(4,3,6) +plt.imshow(cgls1.get_output().subset(channel = channel_ind[2]).as_array()) +plt.colorbar() + +############################################################################### +# Show Tikhonov + +plt.subplot(4,3,7) +plt.imshow(cgls2.get_output().subset(channel = channel_ind[0]).as_array()) +plt.colorbar() + +plt.subplot(4,3,8) +plt.imshow(cgls2.get_output().subset(channel = channel_ind[1]).as_array()) +plt.colorbar() + +plt.subplot(4,3,9) +plt.imshow(cgls2.get_output().subset(channel = channel_ind[2]).as_array()) +plt.colorbar() + + +############################################################################### +# Show Total variation + +plt.subplot(4,3,10) +plt.imshow(pdhg.get_output().subset(channel = channel_ind[0]).as_array()) +plt.colorbar() + +plt.subplot(4,3,11) +plt.imshow(pdhg.get_output().subset(channel = channel_ind[1]).as_array()) +plt.colorbar() + +plt.subplot(4,3,12) +plt.imshow(pdhg.get_output().subset(channel = channel_ind[2]).as_array()) +plt.colorbar() + + +############################################################################### + + + + + + + + + + + + -- cgit v1.2.3 From b6c18977a20b1751c181545a7555c0d2d9a3f2d3 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Fri, 31 May 2019 18:16:49 +0100 Subject: add colorbay demo --- Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py b/Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py index 0a2126e..a735323 100644 --- a/Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py +++ b/Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py @@ -45,6 +45,7 @@ if phantom == 'carbon': filename = 'carbonPd_full_sinogram_stripes_removed.mat' X = loadmat(pathname + filename) X = numpy.transpose(X['SS'],(3,1,2,0)) + X = X[80:100] # delete this to take all channels elif phantom == 'powder': pathname = '/media/newhd/shared/DataProcessed/' @@ -56,7 +57,7 @@ elif phantom == 'powder': arrays[k] = numpy.array(v) XX = arrays['S'] X = numpy.transpose(XX,(0,2,1,3)) - X = X[0:250] + X = X[0:20] @@ -68,7 +69,7 @@ num_pixels_v = X.shape[2] num_angles = X.shape[1] # Display a single projection in a single channel -plt.imshow(X[100,5,:,:]) +plt.imshow(X[5,5,:,:]) plt.title('Example of a projection image in one channel' ) plt.show() @@ -119,7 +120,7 @@ Aall = AstraProjectorMC(ig2d,ag2d,'gpu') # Compute and simple backprojction and display one channel as image. Xbp = Aall.adjoint(data2d) -plt.imshow(Xbp.subset(channel=100).array) +plt.imshow(Xbp.subset(channel=5).array) plt.show() #%% CGLS @@ -130,7 +131,7 @@ cgls1.max_iteration = 100 cgls1.update_objective_interval = 1 cgls1.run(5,verbose=True) -plt.imshow(cgls1.get_output().subset(channel=100).array) +plt.imshow(cgls1.get_output().subset(channel=5).array) plt.title('CGLS') plt.show() @@ -149,14 +150,15 @@ cgls2.update_objective_interval = 1 cgls2.run(10,verbose=True) -plt.imshow(cgls2.get_output().subset(channel=100).array) +plt.imshow(cgls2.get_output().subset(channel=5).array) plt.title('Tikhonov') plt.show() #%% Total Variation # Regularisation Parameter -alpha_TV = 50 +#alpha_TV = 0.08 # for carbon +alpha_TV = 0.08 # for powder # Create operators op1 = Gradient(ig2d, correlation=Gradient.CORRELATION_SPACE) @@ -166,7 +168,7 @@ op2 = Aall operator = BlockOperator(op1, op2, shape=(2,1) ) # Create functions -f1 = alpha * MixedL21Norm() +f1 = alpha_TV * MixedL21Norm() f2 = 0.5 * L2NormSquared(b=data2d) f = BlockFunction(f1, f2) g = ZeroFunction() @@ -175,7 +177,7 @@ g = ZeroFunction() normK = 8.70320267279591 # Run one time no need to compute again takes time # Primal & dual stepsizes -sigma = 10 +sigma = 1 tau = 1/(sigma*normK**2) # Setup and run the PDHG algorithm @@ -186,7 +188,7 @@ pdhg.run(1000, verbose =True) #%% Show sinograms -channel_ind = [25,75,125] +channel_ind = [10,15,15] plt.figure(figsize=(15,15)) -- cgit v1.2.3 From 424230bbe06d24604fa8db4c5ebc5f36f0791355 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Mon, 3 Jun 2019 11:41:37 +0100 Subject: fix dot for numpy method --- Wrappers/Python/ccpi/framework/framework.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/framework/framework.py b/Wrappers/Python/ccpi/framework/framework.py index 3840f2c..e906ca6 100755 --- a/Wrappers/Python/ccpi/framework/framework.py +++ b/Wrappers/Python/ccpi/framework/framework.py @@ -821,7 +821,7 @@ class DataContainer(object): if self.shape == other.shape: # return (self*other).sum() if method == 'numpy': - return numpy.dot(self.as_array().ravel(), other.as_array()) + return numpy.dot(self.as_array().ravel(), other.as_array().ravel()) elif method == 'reduce': # see https://github.com/vais-ral/CCPi-Framework/pull/273 # notice that Python seems to be smart enough to use -- cgit v1.2.3 From c21dc77179d2b8eb71b0bfeaa6e6e73f847fec67 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 3 Jun 2019 13:12:11 +0100 Subject: remove memopt --- .../Python/ccpi/optimisation/algorithms/FISTA.py | 78 +++++++++++----------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py index ee51049..d509621 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py @@ -57,21 +57,21 @@ class FISTA(Algorithm): # algorithmic parameters if opt is None: - opt = {'tol': 1e-4, 'memopt':False} + opt = {'tol': 1e-4} - self.tol = opt['tol'] if 'tol' in opt.keys() else 1e-4 - memopt = opt['memopt'] if 'memopt' in opt.keys() else False - self.memopt = memopt +# self.tol = opt['tol'] if 'tol' in opt.keys() else 1e-4 +# memopt = opt['memopt'] if 'memopt' in opt.keys() else False +# self.memopt = memopt # initialization - if memopt: - self.y = x_init.copy() - self.x_old = x_init.copy() - self.x = x_init.copy() - self.u = x_init.copy() - else: - self.x_old = x_init.copy() - self.y = x_init.copy() +# if memopt: + self.y = x_init.copy() + self.x_old = x_init.copy() + self.x = x_init.copy() + self.u = x_init.copy() +# else: +# self.x_old = x_init.copy() +# self.y = x_init.copy() #timing = numpy.zeros(max_iter) #criter = numpy.zeros(max_iter) @@ -85,37 +85,37 @@ class FISTA(Algorithm): # algorithm loop #for it in range(0, max_iter): - if self.memopt: +# if self.memopt: # u = y - invL*f.grad(y) # store the result in x_old - self.f.gradient(self.y, out=self.u) - self.u.__imul__( -self.invL ) - self.u.__iadd__( self.y ) - # x = g.prox(u,invL) - self.g.proximal(self.u, self.invL, out=self.x) - - self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) - - # y = x + (t_old-1)/t*(x-x_old) - self.x.subtract(self.x_old, out=self.y) - self.y.__imul__ ((self.t_old-1)/self.t) - self.y.__iadd__( self.x ) - - self.x_old.fill(self.x) - self.t_old = self.t - - - else: - u = self.y - self.invL*self.f.gradient(self.y) - - self.x = self.g.proximal(u,self.invL) - - self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) + self.f.gradient(self.y, out=self.u) + self.u.__imul__( -self.invL ) + self.u.__iadd__( self.y ) + # x = g.prox(u,invL) + self.g.proximal(self.u, self.invL, out=self.x) + + self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) + + # y = x + (t_old-1)/t*(x-x_old) + self.x.subtract(self.x_old, out=self.y) + self.y.__imul__ ((self.t_old-1)/self.t) + self.y.__iadd__( self.x ) + + self.x_old.fill(self.x) + self.t_old = self.t - self.y = self.x + (self.t_old-1)/self.t*(self.x-self.x_old) - self.x_old = self.x.copy() - self.t_old = self.t +# else: +# u = self.y - self.invL*self.f.gradient(self.y) +# +# self.x = self.g.proximal(u,self.invL) +# +# self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) +# +# self.y = self.x + (self.t_old-1)/self.t*(self.x-self.x_old) +# +# self.x_old = self.x.copy() +# self.t_old = self.t def update_objective(self): self.loss.append( self.f(self.x) + self.g(self.x) ) \ No newline at end of file -- cgit v1.2.3 From 8a553acaa96748e887e93c40e3afe03c5c6f7943 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 3 Jun 2019 13:49:32 +0100 Subject: remove memopt --- .../Python/ccpi/optimisation/algorithms/FISTA.py | 39 +++------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py index d509621..3f285be 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py @@ -59,35 +59,18 @@ class FISTA(Algorithm): if opt is None: opt = {'tol': 1e-4} -# self.tol = opt['tol'] if 'tol' in opt.keys() else 1e-4 -# memopt = opt['memopt'] if 'memopt' in opt.keys() else False -# self.memopt = memopt - - # initialization -# if memopt: self.y = x_init.copy() self.x_old = x_init.copy() self.x = x_init.copy() self.u = x_init.copy() -# else: -# self.x_old = x_init.copy() -# self.y = x_init.copy() - - #timing = numpy.zeros(max_iter) - #criter = numpy.zeros(max_iter) - - + + self.invL = 1/f.L self.t_old = 1 def update(self): - # algorithm loop - #for it in range(0, max_iter): - -# if self.memopt: - # u = y - invL*f.grad(y) - # store the result in x_old + self.f.gradient(self.y, out=self.u) self.u.__imul__( -self.invL ) self.u.__iadd__( self.y ) @@ -96,26 +79,12 @@ class FISTA(Algorithm): self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) - # y = x + (t_old-1)/t*(x-x_old) self.x.subtract(self.x_old, out=self.y) self.y.__imul__ ((self.t_old-1)/self.t) self.y.__iadd__( self.x ) self.x_old.fill(self.x) - self.t_old = self.t - - -# else: -# u = self.y - self.invL*self.f.gradient(self.y) -# -# self.x = self.g.proximal(u,self.invL) -# -# self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) -# -# self.y = self.x + (self.t_old-1)/self.t*(self.x-self.x_old) -# -# self.x_old = self.x.copy() -# self.t_old = self.t + self.t_old = self.t def update_objective(self): self.loss.append( self.f(self.x) + self.g(self.x) ) \ No newline at end of file -- cgit v1.2.3 From ce556c7943815afffaa28d63a2b8a7883c55b7a7 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 3 Jun 2019 20:31:01 +0100 Subject: final demos --- .../PDHG_examples/GatherAll/PDHG_TGV_Denoising.py | 102 +++++----- .../GatherAll/PDHG_Tikhonov_Denoising.py | 213 ++++++++++----------- .../PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py | 142 +++++++------- 3 files changed, 221 insertions(+), 236 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py index 4d6da00..e9bad7e 100755 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py @@ -23,23 +23,21 @@ Total Generalised Variation (TGV) Denoising using PDHG algorithm: -Problem: min_{x} \alpha * ||\nabla x - w||_{2,1} + - \beta * || E w ||_{2,1} + - fidelity - - where fidelity can be as follows depending on the noise characteristics - of the data: - * Norm2Squared \frac{1}{2} * || x - g ||_{2}^{2} - * KullbackLeibler \int u - g * log(u) + Id_{u>0} - * L1Norm ||u - g||_{1} - - \alpha: Regularization parameter - \beta: Regularization parameter - +Problem: min_{u} \alpha * ||\nabla u - w||_{2,1} + + \beta * || E u ||_{2,1} + + Fidelity(u, g) + \nabla: Gradient operator - E: Symmetrized Gradient operator + E: Symmetrized Gradient operator + \alpha: Regularization parameter + \beta: Regularization parameter - g: Noisy Data with Salt & Pepper Noise + g: Noisy Data + + Fidelity = 1) L2NormSquarred ( \frac{1}{2} * || u - g ||_{2}^{2} ) if Noise is Gaussian + 2) L1Norm ( ||u - g||_{1} )if Noise is Salt & Pepper + 3) Kullback Leibler (\int u - g * log(u) + Id_{u>0}) if Noise is Poisson + Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity ZeroOperator, E @@ -48,10 +46,15 @@ Problem: min_{x} \alpha * ||\nabla x - w||_{2,1} + Method = 1 (PDHG - explicit ): K = [ \nabla, - Identity ZeroOperator, E ] + + Default: TGV denoising + noise = Gaussian + Fidelity = L2NormSquarred + method = 0 """ -from ccpi.framework import ImageData, ImageGeometry +from ccpi.framework import ImageData import numpy as np import numpy @@ -63,14 +66,14 @@ from ccpi.optimisation.operators import BlockOperator, Identity, \ Gradient, SymmetrizedGradient, ZeroOperator from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ MixedL21Norm, BlockFunction, KullbackLeibler, L2NormSquared -import sys + +from ccpi.framework import TestData +import os, sys if int(numpy.version.version.split('.')[1]) > 12: from skimage.util import random_noise else: from demoutil import random_noise - - # user supplied input if len(sys.argv) > 1: which_noise = int(sys.argv[1]) @@ -81,35 +84,23 @@ print ("Applying {} noise") if len(sys.argv) > 2: method = sys.argv[2] else: - method = '1' + method = '0' print ("method ", method) -# Create phantom for TGV SaltPepper denoising - -N = 100 - -x1 = np.linspace(0, int(N/2), N) -x2 = np.linspace(int(N/2), 0., N) -xv, yv = np.meshgrid(x1, x2) - -xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T -#data = xv -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -data = ig.allocate() -data.fill(xv/xv.max()) +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) +data = loader.load(TestData.SHAPES) +ig = data.geometry ag = ig # Create noisy data. -# Apply Salt & Pepper noise -# gaussian -# poisson noises = ['gaussian', 'poisson', 's&p'] noise = noises[which_noise] if noise == 's&p': n1 = random_noise(data.as_array(), mode = noise, salt_vs_pepper = 0.9, amount=0.2, seed=10) elif noise == 'poisson': - n1 = random_noise(data.as_array(), mode = noise, seed = 10) + scale = 5 + n1 = random_noise( data.as_array()/scale, mode = noise, seed = 10)*scale elif noise == 'gaussian': n1 = random_noise(data.as_array(), mode = noise, seed = 10) else: @@ -128,23 +119,24 @@ plt.title('Noisy Data') plt.colorbar() plt.show() -# Regularisation Parameters +# Regularisation Parameter depending on the noise distribution if noise == 's&p': alpha = 0.8 elif noise == 'poisson': - alpha = .1 -elif noise == 'gaussian': alpha = .3 +elif noise == 'gaussian': + alpha = .2 -beta = numpy.sqrt(2)* alpha +# TODO add ref why this choice +beta = 2 * alpha -# fidelity +# Fidelity if noise == 's&p': f3 = L1Norm(b=noisy_data) elif noise == 'poisson': f3 = KullbackLeibler(noisy_data) elif noise == 'gaussian': - f3 = L2NormSquared(b=noisy_data) + f3 = 0.5 * L2NormSquared(b=noisy_data) if method == '0': @@ -162,7 +154,6 @@ if method == '0': f1 = alpha * MixedL21Norm() f2 = beta * MixedL21Norm() - # f3 depends on the noise characteristics f = BlockFunction(f1, f2, f3) g = ZeroFunction() @@ -183,28 +174,27 @@ else: f = BlockFunction(f1, f2) g = BlockFunction(f3, ZeroFunction()) -## Compute operator Norm +# Compute operator Norm normK = operator.norm() -# + # Primal & dual stepsizes sigma = 1 tau = 1/(sigma*normK**2) - # Setup and run the PDHG algorithm pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000, verbose = True) +pdhg.update_objective_interval = 100 +pdhg.run(2000) -#%% +# Show results plt.figure(figsize=(20,5)) plt.subplot(1,4,1) -plt.imshow(data.as_array()) +plt.imshow(data.subset(channel=0).as_array()) plt.title('Ground Truth') plt.colorbar() plt.subplot(1,4,2) -plt.imshow(noisy_data.as_array()) +plt.imshow(noisy_data.subset(channel=0).as_array()) plt.title('Noisy Data') plt.colorbar() plt.subplot(1,4,3) @@ -212,10 +202,8 @@ plt.imshow(pdhg.get_output()[0].as_array()) plt.title('TGV Reconstruction') plt.colorbar() plt.subplot(1,4,4) -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TGV reconstruction') +plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), data.as_array()[int(ig.shape[0]/2),:], label = 'GTruth') +plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output()[0].as_array()[int(ig.shape[0]/2),:], label = 'TGV reconstruction') plt.legend() plt.title('Middle Line Profiles') -plt.show() - - +plt.show() \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py index 5aa334d..8a9920c 100644 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py @@ -18,26 +18,37 @@ # limitations under the License. # #========================================================================= + """ -Tikhonov Denoising using PDHG algorithm: +``Tikhonov`` Regularization Denoising using PDHG algorithm: -Problem: min_{x} \alpha * ||\nabla x||_{2}^{2} + \frac{1}{2} * || x - g ||_{2}^{2} +Problem: min_{u}, \alpha * ||\nabla u||_{2,2} + Fidelity(u, g) \alpha: Regularization parameter \nabla: Gradient operator - g: Noisy Data with Gaussian Noise + g: Noisy Data + Fidelity = 1) L2NormSquarred ( \frac{1}{2} * || u - g ||_{2}^{2} ) if Noise is Gaussian + 2) L1Norm ( ||u - g||_{1} )if Noise is Salt & Pepper + 3) Kullback Leibler (\int u - g * log(u) + Id_{u>0}) if Noise is Poisson + Method = 0 ( PDHG - split ) : K = [ \nabla, Identity] - Method = 1 (PDHG - explicit ): K = \nabla - -""" + Method = 1 (PDHG - explicit ): K = \nabla + + + Default: Tikhonov denoising + noise = Gaussian + Fidelity = L2NormSquarred + method = 0 + +""" from ccpi.framework import ImageData, TestData @@ -62,7 +73,7 @@ else: if len(sys.argv) > 1: which_noise = int(sys.argv[1]) else: - which_noise = 0 + which_noise = 2 print ("Applying {} noise") if len(sys.argv) > 2: @@ -71,39 +82,25 @@ else: method = '0' print ("method ", method) -# Create phantom for TV Salt & Pepper denoising -N = 100 - loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) -data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,N)) +data = loader.load(TestData.SHAPES) ig = data.geometry ag = ig -# Create noisy data. Apply Salt & Pepper noise # Create noisy data. -# Apply Salt & Pepper noise -# gaussian -# poisson noises = ['gaussian', 'poisson', 's&p'] noise = noises[which_noise] if noise == 's&p': n1 = random_noise(data.as_array(), mode = noise, salt_vs_pepper = 0.9, amount=0.2) elif noise == 'poisson': - n1 = random_noise(data.as_array(), mode = noise, seed = 10) + scale = 5 + n1 = random_noise( data.as_array()/scale, mode = noise, seed = 10)*scale elif noise == 'gaussian': n1 = random_noise(data.as_array(), mode = noise, seed = 10) else: raise ValueError('Unsupported Noise ', noise) noisy_data = ImageData(n1) -# fidelity -if noise == 's&p': - f2 = L1Norm(b=noisy_data) -elif noise == 'poisson': - f2 = KullbackLeibler(noisy_data) -elif noise == 'gaussian': - f2 = 0.5 * L2NormSquared(b=noisy_data) - # Show Ground Truth and Noisy Data plt.figure(figsize=(10,5)) plt.subplot(1,2,1) @@ -116,15 +113,21 @@ plt.title('Noisy Data') plt.colorbar() plt.show() - -# Regularisation Parameter -# no edge preservation alpha is big +# Regularisation Parameter depending on the noise distribution +if noise == 's&p': + alpha = 20 +elif noise == 'poisson': + alpha = 10 +elif noise == 'gaussian': + alpha = 5 + +# fidelity if noise == 's&p': - alpha = 8. + f2 = L1Norm(b=noisy_data) elif noise == 'poisson': - alpha = 8. + f2 = KullbackLeibler(noisy_data) elif noise == 'gaussian': - alpha = 8. + f2 = 0.5 * L2NormSquared(b=noisy_data) if method == '0': @@ -135,21 +138,14 @@ if method == '0': # Create BlockOperator operator = BlockOperator(op1, op2, shape=(2,1) ) - # Create functions - + # Create functions f1 = alpha * L2NormSquared() - # f2 must change according to noise - #f2 = 0.5 * L2NormSquared(b = noisy_data) f = BlockFunction(f1, f2) g = ZeroFunction() else: - # Without the "Block Framework" operator = Gradient(ig) - f = alpha * L2NormSquared() - # g must change according to noise - #g = 0.5 * L2NormSquared(b = noisy_data) g = f2 @@ -159,13 +155,12 @@ normK = operator.norm() # Primal & dual stepsizes sigma = 1 tau = 1/(sigma*normK**2) -opt = {'niter':2000, 'memopt': True} # Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(1500) +pdhg.update_objective_interval = 100 +pdhg.run(2000) plt.figure(figsize=(20,5)) @@ -179,78 +174,78 @@ plt.title('Noisy Data') plt.colorbar() plt.subplot(1,4,3) plt.imshow(pdhg.get_output().as_array()) -plt.title('Tikhonov Reconstruction') +plt.title('TV Reconstruction') plt.colorbar() plt.subplot(1,4,4) -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), data.as_array()[int(ig.shape[0]/2),:], label = 'GTruth') +plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output().as_array()[int(ig.shape[0]/2),:], label = 'Tikhonov reconstruction') plt.legend() plt.title('Middle Line Profiles') plt.show() -##%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - - regulariser = alpha * sum_squares(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - +###%% Check with CVX solution +# +#from ccpi.optimisation.operators import SparseFiniteDiff +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +# +#if cvx_not_installable: +# +# ##Construct problem +# u = Variable(ig.shape) +# +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# # Define Total Variation as a regulariser +# +# regulariser = alpha * sum_squares(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) +# fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) +# +# # choose solver +# if 'MOSEK' in installed_solvers(): +# solver = MOSEK +# else: +# solver = SCS +# +# obj = Minimize( regulariser + fidelity) +# prob = Problem(obj) +# result = prob.solve(verbose = True, solver = solver) +# +# diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) +# +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(pdhg.get_output().as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# plt.subplot(3,1,2) +# plt.imshow(u.value) +# plt.title('CVX solution') +# plt.colorbar() +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() +# +# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') +# plt.legend() +# plt.title('Middle Line Profiles') +# plt.show() +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) +# +# +# +# +# diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py index 95fe2c1..e7e33cb 100644 --- a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py +++ b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py @@ -84,7 +84,7 @@ sin = Aop.direct(data) # Create noisy data. Apply Poisson noise scale = 0.5 eta = 0 -n1 = scale * np.random.poisson(eta + sin.as_array()/scale) +n1 = np.random.poisson(eta + sin.as_array()) noisy_data = AcquisitionData(n1, ag) @@ -100,6 +100,8 @@ plt.title('Noisy Data') plt.colorbar() plt.show() + +#%% # Regularisation Parameter alpha = 2 @@ -157,72 +159,72 @@ plt.show() #%% Check with CVX solution # -from ccpi.optimisation.operators import SparseFiniteDiff -import astra -import numpy - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - - ##Construct problem - u = Variable(N*N) - q = Variable() - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - - # create matrix representation for Astra operator - - vol_geom = astra.create_vol_geom(N, N) - proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) - - proj_id = astra.create_projector('strip', proj_geom, vol_geom) - - matrix_id = astra.projector.matrix(proj_id) - - ProjMat = astra.matrix.get(matrix_id) - - tmp = noisy_data.as_array().ravel() - - fidelity = sum(kl_div(tmp, ProjMat * u)) - - constraints = [q>=fidelity, u>=0] - solver = SCS - obj = Minimize( regulariser + q) - prob = Problem(obj, constraints) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = np.abs(pdhg.get_output().as_array() - np.reshape(u.value, (N, N))) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(np.reshape(u.value, (N, N))) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N, N))[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file +#from ccpi.optimisation.operators import SparseFiniteDiff +#import astra +#import numpy +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +# +#if cvx_not_installable: +# +# +# ##Construct problem +# u = Variable(N*N) +# q = Variable() +# +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) +# +# # create matrix representation for Astra operator +# +# vol_geom = astra.create_vol_geom(N, N) +# proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) +# +# proj_id = astra.create_projector('strip', proj_geom, vol_geom) +# +# matrix_id = astra.projector.matrix(proj_id) +# +# ProjMat = astra.matrix.get(matrix_id) +# +# tmp = noisy_data.as_array().ravel() +# +# fidelity = sum(kl_div(tmp, ProjMat * u)) +# +# constraints = [q>=fidelity, u>=0] +# solver = SCS +# obj = Minimize( regulariser + q) +# prob = Problem(obj, constraints) +# result = prob.solve(verbose = True, solver = solver) +# +# diff_cvx = np.abs(pdhg.get_output().as_array() - np.reshape(u.value, (N, N))) +# +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(pdhg.get_output().as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# plt.subplot(3,1,2) +# plt.imshow(np.reshape(u.value, (N, N))) +# plt.title('CVX solution') +# plt.colorbar() +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() +# +# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N, N))[int(N/2),:], label = 'CVX') +# plt.legend() +# plt.title('Middle Line Profiles') +# plt.show() +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file -- cgit v1.2.3 From 8ebe128bf1a893843f9ae34a2a7d5fb4ae91da98 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 3 Jun 2019 20:32:11 +0100 Subject: final demos --- .../PDHG_examples/GatherAll/PDHG_TV_Denoising.py | 187 +++++++++++---------- 1 file changed, 96 insertions(+), 91 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py index 0f1effa..c472f36 100755 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py @@ -24,15 +24,18 @@ Total Variation Denoising using PDHG algorithm: -Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + ||x-g||_{1} +Problem: min_{u}, \alpha * ||\nabla u||_{2,1} + Fidelity(u, g) \alpha: Regularization parameter \nabla: Gradient operator - g: Noisy Data with Salt & Pepper Noise - - + g: Noisy Data + + Fidelity = 1) L2NormSquarred ( \frac{1}{2} * || u - g ||_{2}^{2} ) if Noise is Gaussian + 2) L1Norm ( ||u - g||_{1} )if Noise is Salt & Pepper + 3) Kullback Leibler (\int u - g * log(u) + Id_{u>0}) if Noise is Poisson + Method = 0 ( PDHG - split ) : K = [ \nabla, Identity] @@ -40,10 +43,14 @@ Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + ||x-g||_{1} Method = 1 (PDHG - explicit ): K = \nabla + Default: ROF denoising + noise = Gaussian + Fidelity = L2NormSquarred + method = 0 + + """ -from ccpi.framework import ImageData, ImageGeometry - import numpy as np import numpy import matplotlib.pyplot as plt @@ -65,7 +72,7 @@ else: if len(sys.argv) > 1: which_noise = int(sys.argv[1]) else: - which_noise = 0 + which_noise = 1 print ("Applying {} noise") if len(sys.argv) > 2: @@ -73,32 +80,27 @@ if len(sys.argv) > 2: else: method = '0' print ("method ", method) -# Create phantom for TV Salt & Pepper denoising -N = 100 + loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) -data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,N)) -data = loader.load(TestData.PEPPERS, size=(N,N)) +data = loader.load(TestData.SHAPES) ig = data.geometry ag = ig # Create noisy data. -# Apply Salt & Pepper noise -# gaussian -# poisson noises = ['gaussian', 'poisson', 's&p'] noise = noises[which_noise] if noise == 's&p': n1 = random_noise(data.as_array(), mode = noise, salt_vs_pepper = 0.9, amount=0.2) elif noise == 'poisson': - n1 = random_noise(data.as_array(), mode = noise, seed = 10) + scale = 5 + n1 = random_noise( data.as_array()/scale, mode = noise, seed = 10)*scale elif noise == 'gaussian': n1 = random_noise(data.as_array(), mode = noise, seed = 10) else: raise ValueError('Unsupported Noise ', noise) noisy_data = ig.allocate() noisy_data.fill(n1) -#noisy_data = ImageData(n1) # Show Ground Truth and Noisy Data plt.figure(figsize=(10,5)) @@ -112,8 +114,14 @@ plt.title('Noisy Data') plt.colorbar() plt.show() -# Regularisation Parameter -alpha = .2 + +# Regularisation Parameter depending on the noise distribution +if noise == 's&p': + alpha = 0.8 +elif noise == 'poisson': + alpha = 1 +elif noise == 'gaussian': + alpha = .3 # fidelity if noise == 's&p': @@ -121,30 +129,25 @@ if noise == 's&p': elif noise == 'poisson': f2 = KullbackLeibler(noisy_data) elif noise == 'gaussian': - f2 = L2NormSquared(b=noisy_data) + f2 = 0.5 * L2NormSquared(b=noisy_data) if method == '0': # Create operators - op1 = Gradient(ig, correlation=Gradient.CORRELATION_SPACECHANNEL) + op1 = Gradient(ig, correlation=Gradient.CORRELATION_SPACE) op2 = Identity(ig, ag) # Create BlockOperator operator = BlockOperator(op1, op2, shape=(2,1) ) # Create functions - f1 = alpha * MixedL21Norm() - #f2 = L1Norm(b = noisy_data) - f = BlockFunction(f1, f2) - + f = BlockFunction(alpha * MixedL21Norm(), f2) g = ZeroFunction() else: - # Without the "Block Framework" operator = Gradient(ig) f = alpha * MixedL21Norm() - #g = L1Norm(b = noisy_data) g = f2 @@ -154,14 +157,15 @@ normK = operator.norm() # Primal & dual stepsizes sigma = 1 tau = 1/(sigma*normK**2) -opt = {'niter':2000, 'memopt': True} + # Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 +pdhg.update_objective_interval = 100 pdhg.run(2000) + if data.geometry.channels > 1: plt.figure(figsize=(20,15)) for row in range(data.geometry.channels): @@ -179,11 +183,12 @@ if data.geometry.channels > 1: plt.title('TV Reconstruction') plt.colorbar() plt.subplot(3,4,4+row*4) - plt.plot(np.linspace(0,N,N), data.subset(channel=row).as_array()[int(N/2),:], label = 'GTruth') - plt.plot(np.linspace(0,N,N), pdhg.get_output().subset(channel=row).as_array()[int(N/2),:], label = 'TV reconstruction') + plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), data.subset(channel=row).as_array()[int(N/2),:], label = 'GTruth') + plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output().subset(channel=row).as_array()[int(N/2),:], label = 'TV reconstruction') plt.legend() plt.title('Middle Line Profiles') plt.show() + else: plt.figure(figsize=(20,5)) plt.subplot(1,4,1) @@ -199,68 +204,68 @@ else: plt.title('TV Reconstruction') plt.colorbar() plt.subplot(1,4,4) - plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') + plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), data.as_array()[int(ig.shape[0]/2),:], label = 'GTruth') + plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output().as_array()[int(ig.shape[0]/2),:], label = 'TV reconstruction') plt.legend() plt.title('Middle Line Profiles') plt.show() -##%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = pnorm( u - noisy_data.as_array(),1) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) +###%% Check with CVX solution +# +#from ccpi.optimisation.operators import SparseFiniteDiff +# +#try: +# from cvxpy import * +# cvx_not_installable = True +#except ImportError: +# cvx_not_installable = False +# +# +#if cvx_not_installable: +# +# ##Construct problem +# u = Variable(ig.shape) +# +# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') +# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') +# +# # Define Total Variation as a regulariser +# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) +# fidelity = pnorm( u - noisy_data.as_array(),1) +# +# # choose solver +# if 'MOSEK' in installed_solvers(): +# solver = MOSEK +# else: +# solver = SCS +# +# obj = Minimize( regulariser + fidelity) +# prob = Problem(obj) +# result = prob.solve(verbose = True, solver = solver) +# +# diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) +# +# plt.figure(figsize=(15,15)) +# plt.subplot(3,1,1) +# plt.imshow(pdhg.get_output().as_array()) +# plt.title('PDHG solution') +# plt.colorbar() +# plt.subplot(3,1,2) +# plt.imshow(u.value) +# plt.title('CVX solution') +# plt.colorbar() +# plt.subplot(3,1,3) +# plt.imshow(diff_cvx) +# plt.title('Difference') +# plt.colorbar() +# plt.show() +# +# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') +# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') +# plt.legend() +# plt.title('Middle Line Profiles') +# plt.show() +# +# print('Primal Objective (CVX) {} '.format(obj.value)) +# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) -- cgit v1.2.3 From 521cbed2e02c38f8a277d23c02f1a7eb9c8542ca Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 3 Jun 2019 21:35:53 +0100 Subject: fix memopt/input FISTA --- .../Python/ccpi/optimisation/algorithms/FISTA.py | 42 +++++++++------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py index 3f285be..04e7c38 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py @@ -20,40 +20,28 @@ class FISTA(Algorithm): x_init: initial guess f: data fidelity g: regularizer - h: - opt: additional algorithm + opt: additional options ''' - + + def __init__(self, **kwargs): '''initialisation can be done at creation time if all proper variables are passed or later with set_up''' super(FISTA, self).__init__() - self.f = None - self.g = None + self.f = kwargs.get('f', None) + self.g = kwargs.get('g', None) + self.x_init = kwargs.get('x_init',None) self.invL = None self.t_old = 1 - args = ['x_init', 'f', 'g', 'opt'] - for k,v in kwargs.items(): - if k in args: - args.pop(args.index(k)) - if len(args) == 0: - return self.set_up(kwargs['x_init'], - f=kwargs['f'], - g=kwargs['g'], - opt=kwargs['opt']) + if self.f is not None and self.g is not None: + print ("Calling from creator") + self.set_up(self.x_init, self.f, self.g) + - def set_up(self, x_init, f=None, g=None, opt=None): + def set_up(self, x_init, f, g, opt=None, **kwargs): - # default inputs - if f is None: - self.f = ZeroFunction() - else: - self.f = f - if g is None: - g = ZeroFunction() - self.g = g - else: - self.g = g + self.f = f + self.g = g # algorithmic parameters if opt is None: @@ -87,4 +75,6 @@ class FISTA(Algorithm): self.t_old = self.t def update_objective(self): - self.loss.append( self.f(self.x) + self.g(self.x) ) \ No newline at end of file + self.loss.append( self.f(self.x) + self.g(self.x) ) + + -- cgit v1.2.3 From 505a0b06ac81484e535cbee75b1e36de748e25b1 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 3 Jun 2019 22:46:28 +0100 Subject: add also cvx demos --- .../PDHG_examples/GatherAll/PDHG_TGV_Denoising.py | 75 +++++++++++- .../PDHG_examples/GatherAll/PDHG_TV_Denoising.py | 130 +++++++++++---------- .../GatherAll/PDHG_Tikhonov_Denoising.py | 126 ++++++++++---------- 3 files changed, 211 insertions(+), 120 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py index e9bad7e..8453d20 100755 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py @@ -206,4 +206,77 @@ plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), data.as_array()[int(ig.shape[0] plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output()[0].as_array()[int(ig.shape[0]/2),:], label = 'TGV reconstruction') plt.legend() plt.title('Middle Line Profiles') -plt.show() \ No newline at end of file +plt.show() + +#%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + +if cvx_not_installable: + + u = Variable(ig.shape) + w1 = Variable(ig.shape) + w2 = Variable(ig.shape) + + # create TGV regulariser + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ + DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ + beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ + 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) + + constraints = [] + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + # fidelity + if noise == 's&p': + fidelity = pnorm( u - noisy_data.as_array(),1) + elif noise == 'poisson': + fidelity = sum(kl_div(noisy_data.as_array(), u)) + solver = SCS + elif noise == 'gaussian': + fidelity = 0.5 * sum_squares(noisy_data.as_array() - u) + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output()[0].as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output()[0].as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output()[0].as_array()[int(ig.shape[0]/2),:], label = 'PDHG') + plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), u.value[int(ig.shape[0]/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py index c472f36..74e7901 100755 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py @@ -72,7 +72,7 @@ else: if len(sys.argv) > 1: which_noise = int(sys.argv[1]) else: - which_noise = 1 + which_noise = 0 print ("Applying {} noise") if len(sys.argv) > 2: @@ -83,7 +83,7 @@ print ("method ", method) loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) -data = loader.load(TestData.SHAPES) +data = loader.load(TestData.SHAPES, size=(50,50)) ig = data.geometry ag = ig @@ -211,61 +211,71 @@ else: plt.show() -###%% Check with CVX solution -# -#from ccpi.optimisation.operators import SparseFiniteDiff -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -# -#if cvx_not_installable: -# -# ##Construct problem -# u = Variable(ig.shape) -# -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# # Define Total Variation as a regulariser -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) -# fidelity = pnorm( u - noisy_data.as_array(),1) -# -# # choose solver -# if 'MOSEK' in installed_solvers(): -# solver = MOSEK -# else: -# solver = SCS -# -# obj = Minimize( regulariser + fidelity) -# prob = Problem(obj) -# result = prob.solve(verbose = True, solver = solver) -# -# diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) -# -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(pdhg.get_output().as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# plt.subplot(3,1,2) -# plt.imshow(u.value) -# plt.title('CVX solution') -# plt.colorbar() -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') -# plt.legend() -# plt.title('Middle Line Profiles') -# plt.show() -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + fidelity = pnorm( u - noisy_data.as_array(),1) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + # fidelity + if noise == 's&p': + fidelity = pnorm( u - noisy_data.as_array(),1) + elif noise == 'poisson': + fidelity = sum(kl_div(noisy_data.as_array(), u)) + solver = SCS + elif noise == 'gaussian': + fidelity = 0.5 * sum_squares(noisy_data.as_array() - u) + + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output().as_array()[int(ig.shape[0]/2),:], label = 'PDHG') + plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), u.value[int(ig.shape[0]/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py index 8a9920c..e16c5a6 100644 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py @@ -185,65 +185,73 @@ plt.show() -###%% Check with CVX solution -# -#from ccpi.optimisation.operators import SparseFiniteDiff -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -# -#if cvx_not_installable: -# -# ##Construct problem -# u = Variable(ig.shape) -# -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# # Define Total Variation as a regulariser -# -# regulariser = alpha * sum_squares(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) -# fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) -# -# # choose solver -# if 'MOSEK' in installed_solvers(): -# solver = MOSEK -# else: -# solver = SCS -# -# obj = Minimize( regulariser + fidelity) -# prob = Problem(obj) -# result = prob.solve(verbose = True, solver = solver) -# -# diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) -# -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(pdhg.get_output().as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# plt.subplot(3,1,2) -# plt.imshow(u.value) -# plt.title('CVX solution') -# plt.colorbar() -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') -# plt.legend() -# plt.title('Middle Line Profiles') -# plt.show() -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) +##%% Check with CVX solution + +from ccpi.optimisation.operators import SparseFiniteDiff + +try: + from cvxpy import * + cvx_not_installable = True +except ImportError: + cvx_not_installable = False + + +if cvx_not_installable: + + ##Construct problem + u = Variable(ig.shape) + + DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') + DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') + + # Define Total Variation as a regulariser + + regulariser = alpha * sum_squares(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) + + # choose solver + if 'MOSEK' in installed_solvers(): + solver = MOSEK + else: + solver = SCS + + # fidelity + if noise == 's&p': + fidelity = pnorm( u - noisy_data.as_array(),1) + elif noise == 'poisson': + fidelity = sum(kl_div(noisy_data.as_array(), u)) + solver = SCS + elif noise == 'gaussian': + fidelity = 0.5 * sum_squares(noisy_data.as_array() - u) + + obj = Minimize( regulariser + fidelity) + prob = Problem(obj) + result = prob.solve(verbose = True, solver = solver) + + diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) + + plt.figure(figsize=(15,15)) + plt.subplot(3,1,1) + plt.imshow(pdhg.get_output().as_array()) + plt.title('PDHG solution') + plt.colorbar() + plt.subplot(3,1,2) + plt.imshow(u.value) + plt.title('CVX solution') + plt.colorbar() + plt.subplot(3,1,3) + plt.imshow(diff_cvx) + plt.title('Difference') + plt.colorbar() + plt.show() + + plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output().as_array()[int(ig.shape[0]/2),:], label = 'PDHG') + plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), u.value[int(ig.shape[0]/2),:], label = 'CVX') + plt.legend() + plt.title('Middle Line Profiles') + plt.show() + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) # # # -- cgit v1.2.3 From c7ed6d4cf69029311dade3811cd574772f4879a5 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 3 Jun 2019 22:47:52 +0100 Subject: fix dot --- .../Python/ccpi/optimisation/functions/L1Norm.py | 88 ++-------------------- 1 file changed, 7 insertions(+), 81 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/L1Norm.py b/Wrappers/Python/ccpi/optimisation/functions/L1Norm.py index 79040a0..37c2016 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/L1Norm.py +++ b/Wrappers/Python/ccpi/optimisation/functions/L1Norm.py @@ -60,7 +60,7 @@ class L1Norm(Function): y = 0 if self.b is not None: - y = 0 + (self.b * x).sum() + y = 0 + self.b.dot(x) return y def proximal(self, x, tau, out=None): @@ -95,98 +95,24 @@ class L1Norm(Function): return ScaledFunction(self, scalar) -#import numpy as np -##from ccpi.optimisation.funcs import Function -#from ccpi.optimisation.functions import Function -#from ccpi.framework import DataContainer, ImageData -# -# -############################# L1NORM FUNCTIONS ############################# -#class SimpleL1Norm(Function): -# -# def __init__(self, alpha=1): -# -# super(SimpleL1Norm, self).__init__() -# self.alpha = alpha -# -# def __call__(self, x): -# return self.alpha * x.abs().sum() -# -# def gradient(self,x): -# return ValueError('Not Differentiable') -# -# def convex_conjugate(self,x): -# return 0 -# -# def proximal(self, x, tau): -# ''' Soft Threshold''' -# return x.sign() * (x.abs() - tau * self.alpha).maximum(0) -# -# def proximal_conjugate(self, x, tau): -# return x.divide((x.abs()/self.alpha).maximum(1.0)) - -#class L1Norm(SimpleL1Norm): -# -# def __init__(self, alpha=1, **kwargs): -# -# super(L1Norm, self).__init__() -# self.alpha = alpha -# self.b = kwargs.get('b',None) -# -# def __call__(self, x): -# -# if self.b is None: -# return SimpleL1Norm.__call__(self, x) -# else: -# return SimpleL1Norm.__call__(self, x - self.b) -# -# def gradient(self, x): -# return ValueError('Not Differentiable') -# -# def convex_conjugate(self,x): -# if self.b is None: -# return SimpleL1Norm.convex_conjugate(self, x) -# else: -# return SimpleL1Norm.convex_conjugate(self, x) + (self.b * x).sum() -# -# def proximal(self, x, tau): -# -# if self.b is None: -# return SimpleL1Norm.proximal(self, x, tau) -# else: -# return self.b + SimpleL1Norm.proximal(self, x - self.b , tau) -# -# def proximal_conjugate(self, x, tau): -# -# if self.b is None: -# return SimpleL1Norm.proximal_conjugate(self, x, tau) -# else: -# return SimpleL1Norm.proximal_conjugate(self, x - tau*self.b, tau) -# - -############################################################################### - - - - if __name__ == '__main__': from ccpi.framework import ImageGeometry import numpy - N, M = 40,40 + N, M = 400,400 ig = ImageGeometry(N, M) scalar = 10 - b = ig.allocate('random_int') - u = ig.allocate('random_int') + b = ig.allocate('random') + u = ig.allocate('random') f = L1Norm() f_scaled = scalar * L1Norm() - + f_b = L1Norm(b=b) f_scaled_b = scalar * L1Norm(b=b) - # call - + # call + a1 = f(u) a2 = f_scaled(u) numpy.testing.assert_equal(scalar * a1, a2) -- cgit v1.2.3 From c9f7f66da692d5ec8d5927775dbff3ac3aeb8c7c Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 3 Jun 2019 22:50:21 +0100 Subject: add nice phantom --- Wrappers/Python/ccpi/framework/TestData.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/TestData.py b/Wrappers/Python/ccpi/framework/TestData.py index 752bc13..cfad7a3 100755 --- a/Wrappers/Python/ccpi/framework/TestData.py +++ b/Wrappers/Python/ccpi/framework/TestData.py @@ -16,6 +16,7 @@ class TestData(object): PEPPERS = 'peppers.tiff' RESOLUTION_CHART = 'resolution_chart.tiff' SIMPLE_PHANTOM_2D = 'simple_jakobs_phantom' + SHAPES = 'shapes.png' def __init__(self, **kwargs): self.data_dir = kwargs.get('data_dir', data_dir) @@ -23,7 +24,7 @@ class TestData(object): def load(self, which, size=(512,512), scale=(0,1), **kwargs): if which not in [TestData.BOAT, TestData.CAMERA, TestData.PEPPERS, TestData.RESOLUTION_CHART, - TestData.SIMPLE_PHANTOM_2D]: + TestData.SIMPLE_PHANTOM_2D, TestData.SHAPES]: raise ValueError('Unknown TestData {}.'.format(which)) if which == TestData.SIMPLE_PHANTOM_2D: N = size[0] @@ -34,6 +35,16 @@ class TestData(object): ig = ImageGeometry(voxel_num_x = N, voxel_num_y = M, dimension_labels=[ImageGeometry.HORIZONTAL_X, ImageGeometry.HORIZONTAL_Y]) data = ig.allocate() data.fill(sdata) + + elif which == TestData.SHAPES: + + tmp = numpy.array(Image.open(os.path.join(self.data_dir, which)).convert('L')) + N = 200 + M = 300 + ig = ImageGeometry(voxel_num_x = N, voxel_num_y = M, dimension_labels=[ImageGeometry.HORIZONTAL_X, ImageGeometry.HORIZONTAL_Y]) + data = ig.allocate() + data.fill(tmp/numpy.max(tmp)) + else: tmp = Image.open(os.path.join(self.data_dir, which)) print (tmp) @@ -94,5 +105,17 @@ class TestData(object): data = data/data.max() - return ImageData(data) + return ImageData(data) + + def shapes(**kwargs): + + tmp = Image.open(os.path.join(data_dir, 'shapes.png')).convert('LA') + + size = kwargs.get('size',(300, 200)) + + data = numpy.array(tmp.resize(size)) + + data = data/data.max() + + return ImageData(data) -- cgit v1.2.3 From 1aa94932776f3a95b02304b1dfd8a18459d7e37c Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 3 Jun 2019 22:50:50 +0100 Subject: fix dot --- Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py index b77d472..2f05119 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py +++ b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py @@ -76,7 +76,7 @@ class L2NormSquared(Function): tmp = 0 if self.b is not None: - tmp = (x * self.b).sum() + tmp = x.dot(self.b) #(x * self.b).sum() return (1./4.) * x.squared_norm() + tmp @@ -151,7 +151,7 @@ if __name__ == '__main__': import numpy # TESTS for L2 and scalar * L2 - M, N, K = 2,3,5 + M, N, K = 20,30,50 ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N, voxel_num_z = K) u = ig.allocate('random_int') b = ig.allocate('random_int') @@ -178,7 +178,7 @@ if __name__ == '__main__': #check convex conjuagate with data d1 = f1.convex_conjugate(u) - d2 = (1/4) * u.squared_norm() + (u*b).sum() + d2 = (1/4) * u.squared_norm() + u.dot(b) numpy.testing.assert_equal(d1, d2) # check proximal no data -- cgit v1.2.3 From 14e06f7ad88202114b22ed478ba6efab952fa30b Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 3 Jun 2019 22:51:43 +0100 Subject: fix call kl div --- Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py index 0d3c8f5..6920829 100644 --- a/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py +++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py @@ -52,8 +52,8 @@ class KullbackLeibler(Function): ''' - # TODO avoid scipy import ???? - tmp = scipy.special.kl_div(self.b.as_array(), x.as_array()) + ind = x.as_array()>0 + tmp = scipy.special.kl_div(self.b.as_array()[ind], x.as_array()[ind]) return numpy.sum(tmp) @@ -78,9 +78,8 @@ class KullbackLeibler(Function): def convex_conjugate(self, x): - # TODO avoid scipy import ???? - xlogy = scipy.special.xlogy(self.b.as_array(), 1 - x.as_array()) - return numpy.sum(-xlogy) + xlogy = - scipy.special.xlogy(self.b.as_array(), 1 - x.as_array()) + return numpy.sum(xlogy) def proximal(self, x, tau, out=None): -- cgit v1.2.3 From 3271aa9c4ca50177bf2f9e37269efa03f52f635e Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 4 Jun 2019 13:36:50 +0100 Subject: fixing tests --- .../Python/ccpi/optimisation/algorithms/FISTA.py | 117 +++++++-------------- .../Python/ccpi/optimisation/functions/Norm2Sq.py | 2 +- .../ccpi/optimisation/functions/ScaledFunction.py | 3 +- .../optimisation/operators/LinearOperatorMatrix.py | 21 ++-- Wrappers/Python/test/test_run_test.py | 14 ++- 5 files changed, 59 insertions(+), 98 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py index 8ea2b6c..04e7c38 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py @@ -20,102 +20,61 @@ class FISTA(Algorithm): x_init: initial guess f: data fidelity g: regularizer - h: - opt: additional algorithm + opt: additional options ''' - + + def __init__(self, **kwargs): '''initialisation can be done at creation time if all proper variables are passed or later with set_up''' super(FISTA, self).__init__() - self.f = None - self.g = None + self.f = kwargs.get('f', None) + self.g = kwargs.get('g', None) + self.x_init = kwargs.get('x_init',None) self.invL = None self.t_old = 1 - args = ['x_init', 'f', 'g', 'opt'] - for k,v in kwargs.items(): - if k in args: - args.pop(args.index(k)) - if len(args) == 0: - return self.set_up(kwargs['x_init'], - f=kwargs['f'], - g=kwargs['g'], - opt=kwargs['opt']) + if self.f is not None and self.g is not None: + print ("Calling from creator") + self.set_up(self.x_init, self.f, self.g) + - def set_up(self, x_init, f=None, g=None, opt=None): + def set_up(self, x_init, f, g, opt=None, **kwargs): - # default inputs - if f is None: - self.f = ZeroFunction() - else: - self.f = f - if g is None: - g = ZeroFunction() - self.g = g - else: - self.g = g + self.f = f + self.g = g # algorithmic parameters if opt is None: - opt = {'tol': 1e-4, 'memopt':False} - - self.tol = opt['tol'] if 'tol' in opt.keys() else 1e-4 - memopt = opt['memopt'] if 'memopt' in opt.keys() else False - self.memopt = memopt - - # initialization - if memopt: - self.y = x_init.clone() - self.x_old = x_init.clone() - self.x = x_init.clone() - self.u = x_init.clone() - else: - self.x_old = x_init.copy() - self.y = x_init.copy() - - #timing = numpy.zeros(max_iter) - #criter = numpy.zeros(max_iter) + opt = {'tol': 1e-4} - + self.y = x_init.copy() + self.x_old = x_init.copy() + self.x = x_init.copy() + self.u = x_init.copy() + + self.invL = 1/f.L self.t_old = 1 def update(self): - # algorithm loop - #for it in range(0, max_iter): - - if self.memopt: - # u = y - invL*f.grad(y) - # store the result in x_old - self.f.gradient(self.y, out=self.u) - self.u.__imul__( -self.invL ) - self.u.__iadd__( self.y ) - # x = g.prox(u,invL) - self.g.proximal(self.u, self.invL, out=self.x) - - self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) - - # y = x + (t_old-1)/t*(x-x_old) - self.x.subtract(self.x_old, out=self.y) - self.y.__imul__ ((self.t_old-1)/self.t) - self.y.__iadd__( self.x ) - - self.x_old.fill(self.x) - self.t_old = self.t - - - else: - u = self.y - self.invL*self.f.gradient(self.y) - - self.x = self.g.proximal(u,self.invL) - - self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) - - self.y = self.x + (self.t_old-1)/self.t*(self.x-self.x_old) - - self.x_old = self.x.copy() - self.t_old = self.t + + self.f.gradient(self.y, out=self.u) + self.u.__imul__( -self.invL ) + self.u.__iadd__( self.y ) + # x = g.prox(u,invL) + self.g.proximal(self.u, self.invL, out=self.x) + + self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) + + self.x.subtract(self.x_old, out=self.y) + self.y.__imul__ ((self.t_old-1)/self.t) + self.y.__iadd__( self.x ) + + self.x_old.fill(self.x) + self.t_old = self.t def update_objective(self): - self.loss.append( self.f(self.x) + self.g(self.x) ) \ No newline at end of file + self.loss.append( self.f(self.x) + self.g(self.x) ) + + diff --git a/Wrappers/Python/ccpi/optimisation/functions/Norm2Sq.py b/Wrappers/Python/ccpi/optimisation/functions/Norm2Sq.py index 0b6bb0b..d9d9010 100755 --- a/Wrappers/Python/ccpi/optimisation/functions/Norm2Sq.py +++ b/Wrappers/Python/ccpi/optimisation/functions/Norm2Sq.py @@ -88,7 +88,7 @@ class Norm2Sq(Function): def gradient(self, x, out = None): if self.memopt: #return 2.0*self.c*self.A.adjoint( self.A.direct(x) - self.b ) - + print (self.range_tmp, self.range_tmp.as_array()) self.A.direct(x, out=self.range_tmp) self.range_tmp -= self.b self.A.adjoint(self.range_tmp, out=out) diff --git a/Wrappers/Python/ccpi/optimisation/functions/ScaledFunction.py b/Wrappers/Python/ccpi/optimisation/functions/ScaledFunction.py index 7caeab2..d292ac8 100755 --- a/Wrappers/Python/ccpi/optimisation/functions/ScaledFunction.py +++ b/Wrappers/Python/ccpi/optimisation/functions/ScaledFunction.py @@ -18,6 +18,7 @@ # limitations under the License. from numbers import Number import numpy +import warnings class ScaledFunction(object): @@ -88,7 +89,7 @@ class ScaledFunction(object): '''Alias of proximal(x, tau, None)''' warnings.warn('''This method will disappear in following versions of the CIL. Use proximal instead''', DeprecationWarning) - return self.proximal(x, out=None) + return self.proximal(x, tau, out=None) diff --git a/Wrappers/Python/ccpi/optimisation/operators/LinearOperatorMatrix.py b/Wrappers/Python/ccpi/optimisation/operators/LinearOperatorMatrix.py index 62e22e0..6306192 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/LinearOperatorMatrix.py +++ b/Wrappers/Python/ccpi/optimisation/operators/LinearOperatorMatrix.py @@ -10,6 +10,9 @@ from ccpi.optimisation.operators import LinearOperator class LinearOperatorMatrix(LinearOperator): def __init__(self,A): self.A = A + M_A, N_A = self.A.shape + self.gm_domain = ImageGeometry(0, N_A) + self.gm_range = ImageGeometry(M_A,0) self.s1 = None # Largest singular value, initially unknown super(LinearOperatorMatrix, self).__init__() @@ -30,22 +33,14 @@ class LinearOperatorMatrix(LinearOperator): def size(self): return self.A.shape - def get_max_sing_val(self): + def norm(self): # If unknown, compute and store. If known, simply return it. if self.s1 is None: self.s1 = svds(self.A,1,return_singular_vectors=False)[0] return self.s1 else: return self.s1 - def allocate_direct(self): - '''allocates the memory to hold the result of adjoint''' - #numpy.dot(self.A.transpose(),x.as_array()) - M_A, N_A = self.A.shape - out = numpy.zeros((N_A,1)) - return DataContainer(out) - def allocate_adjoint(self): - '''allocate the memory to hold the result of direct''' - #numpy.dot(self.A.transpose(),x.as_array()) - M_A, N_A = self.A.shape - out = numpy.zeros((M_A,1)) - return DataContainer(out) + def domain_geometry(self): + return self.gm_domain + def range_geometry(self): + return self.gm_range diff --git a/Wrappers/Python/test/test_run_test.py b/Wrappers/Python/test/test_run_test.py index a6c13f4..ebe494f 100755 --- a/Wrappers/Python/test/test_run_test.py +++ b/Wrappers/Python/test/test_run_test.py @@ -10,7 +10,8 @@ from ccpi.optimisation.algorithms import FISTA #from ccpi.optimisation.algs import FBPD from ccpi.optimisation.functions import Norm2Sq from ccpi.optimisation.functions import ZeroFunction -from ccpi.optimisation.funcs import Norm1 +#from ccpi.optimisation.funcs import Norm1 +from ccpi.optimisation.functions import L1Norm from ccpi.optimisation.funcs import Norm2 from ccpi.optimisation.operators import LinearOperatorMatrix @@ -81,6 +82,7 @@ class TestAlgorithms(unittest.TestCase): opt = {'memopt': True} # Create object instances with the test data A and b. f = Norm2Sq(A, b, c=0.5, memopt=True) + f.L = LinearOperator.PowerMethod(A, 10) g0 = ZeroFunction() # Initial guess @@ -123,6 +125,7 @@ class TestAlgorithms(unittest.TestCase): self.assertTrue(cvx_not_installable) def test_FISTA_Norm1_cvx(self): + print ("test_FISTA_Norm1_cvx") if not cvx_not_installable: try: opt = {'memopt': True} @@ -151,7 +154,8 @@ class TestAlgorithms(unittest.TestCase): x_init = DataContainer(np.zeros((n, 1))) # Create 1-norm object instance - g1 = Norm1(lam) + #g1 = Norm1(lam) + g1 = lam * L1Norm() g1(x_init) g1.prox(x_init, 0.02) @@ -225,7 +229,8 @@ class TestAlgorithms(unittest.TestCase): # Create 1-norm object instance - g1 = Norm1(lam) + #g1 = Norm1(lam) + g1 = lam * L1Norm() # Compare to CVXPY @@ -292,7 +297,8 @@ class TestAlgorithms(unittest.TestCase): # 1-norm regulariser lam1_denoise = 1.0 - g1_denoise = Norm1(lam1_denoise) + #g1_denoise = Norm1(lam1_denoise) + g1_denoise = lam1_denoise * L1Norm() # Initial guess x_init_denoise = ImageData(np.zeros((N, N))) -- cgit v1.2.3 From 910a81042068b084278783b53bac45ad63b852d2 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 4 Jun 2019 16:29:45 +0100 Subject: progress --- Wrappers/Python/ccpi/framework/VectorData.py | 58 ++++++++++++++++++ Wrappers/Python/ccpi/framework/VectorGeometry.py | 69 ++++++++++++++++++++++ Wrappers/Python/ccpi/framework/__init__.py | 2 + Wrappers/Python/ccpi/framework/framework.py | 1 - .../Python/ccpi/optimisation/algorithms/FISTA.py | 14 ++++- .../Python/ccpi/optimisation/functions/Norm2Sq.py | 1 - .../optimisation/operators/LinearOperatorMatrix.py | 16 +++-- 7 files changed, 149 insertions(+), 12 deletions(-) create mode 100755 Wrappers/Python/ccpi/framework/VectorData.py create mode 100755 Wrappers/Python/ccpi/framework/VectorGeometry.py diff --git a/Wrappers/Python/ccpi/framework/VectorData.py b/Wrappers/Python/ccpi/framework/VectorData.py new file mode 100755 index 0000000..fdce3a5 --- /dev/null +++ b/Wrappers/Python/ccpi/framework/VectorData.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy +import sys +from datetime import timedelta, datetime +import warnings +from functools import reduce +from numbers import Number +from ccpi.framework import DataContainer, VectorGeometry + +class VectorData(DataContainer): + def __init__(self, array=None, **kwargs): + self.geometry = kwargs.get('geometry', None) + self.dtype = kwargs.get('dtype', numpy.float32) + + if self.geometry is None: + if array is None: + raise ValueError('Please specify either a geometry or an array') + else: + if len(array.shape) > 1: + raise ValueError('Incompatible size: expected 1D got {}'.format(array.shape)) + out = array + self.geometry = VectorGeometry.VectorGeometry(array.shape[0]) + self.length = self.geometry.length + else: + self.length = self.geometry.length + + if array is None: + out = numpy.zeros((self.length,), dtype=self.dtype) + else: + if self.length == array.shape[0]: + out = array + else: + raise ValueError('Incompatible size: expecting {} got {}'.format((self.length,), array.shape)) + deep_copy = False + super(VectorData, self).__init__(out, deep_copy, None) diff --git a/Wrappers/Python/ccpi/framework/VectorGeometry.py b/Wrappers/Python/ccpi/framework/VectorGeometry.py new file mode 100755 index 0000000..255d2a0 --- /dev/null +++ b/Wrappers/Python/ccpi/framework/VectorGeometry.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy +import sys +from datetime import timedelta, datetime +import warnings +from functools import reduce +from numbers import Number +from ccpi.framework.VectorData import VectorData + +class VectorGeometry(object): + RANDOM = 'random' + RANDOM_INT = 'random_int' + + def __init__(self, + length): + + self.length = length + self.shape = (length, ) + + + def clone(self): + '''returns a copy of VectorGeometry''' + return VectorGeometry(self.length) + + def allocate(self, value=0, **kwargs): + '''allocates an VectorData according to the size expressed in the instance''' + self.dtype = kwargs.get('dtype', numpy.float32) + out = VectorData(geometry=self, dtype=self.dtype) + if isinstance(value, Number): + if value != 0: + out += value + else: + if value == VectorGeometry.RANDOM: + seed = kwargs.get('seed', None) + if seed is not None: + numpy.random.seed(seed) + out.fill(numpy.random.random_sample(self.shape)) + elif value == VectorGeometry.RANDOM_INT: + seed = kwargs.get('seed', None) + if seed is not None: + numpy.random.seed(seed) + max_value = kwargs.get('max_value', 100) + out.fill(numpy.random.randint(max_value,size=self.shape)) + else: + raise ValueError('Value {} unknown'.format(value)) + return out \ No newline at end of file diff --git a/Wrappers/Python/ccpi/framework/__init__.py b/Wrappers/Python/ccpi/framework/__init__.py index 229edb5..9cec708 100755 --- a/Wrappers/Python/ccpi/framework/__init__.py +++ b/Wrappers/Python/ccpi/framework/__init__.py @@ -24,3 +24,5 @@ from .framework import DataProcessor from .framework import AX, PixelByPixelDataProcessor, CastDataContainer from .BlockDataContainer import BlockDataContainer from .BlockGeometry import BlockGeometry +from .VectorGeometry import VectorGeometry +from .VectorData import VectorData diff --git a/Wrappers/Python/ccpi/framework/framework.py b/Wrappers/Python/ccpi/framework/framework.py index 7236e0e..b972be6 100755 --- a/Wrappers/Python/ccpi/framework/framework.py +++ b/Wrappers/Python/ccpi/framework/framework.py @@ -29,7 +29,6 @@ import warnings from functools import reduce from numbers import Number - def find_key(dic, val): """return the key of dictionary dic given the value""" return [k for k, v in dic.items() if v == val][0] diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py index 04e7c38..db4e8b7 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py @@ -34,7 +34,7 @@ class FISTA(Algorithm): self.invL = None self.t_old = 1 if self.f is not None and self.g is not None: - print ("Calling from creator") + print ("FISTA initialising from creator") self.set_up(self.x_init, self.f, self.g) @@ -46,6 +46,8 @@ class FISTA(Algorithm): # algorithmic parameters if opt is None: opt = {'tol': 1e-4} + print(self.x_init.as_array()) + print(x_init.as_array()) self.y = x_init.copy() self.x_old = x_init.copy() @@ -60,18 +62,28 @@ class FISTA(Algorithm): def update(self): self.f.gradient(self.y, out=self.u) + print ('update, self.u' , self.u.as_array()) self.u.__imul__( -self.invL ) self.u.__iadd__( self.y ) + print ('update, self.u' , self.u.as_array()) + # x = g.prox(u,invL) + print ('update, self.x pre prox' , self.x.as_array()) self.g.proximal(self.u, self.invL, out=self.x) + print ('update, self.x post prox' , self.x.as_array()) self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) self.x.subtract(self.x_old, out=self.y) + print ('update, self.y' , self.y.as_array()) + self.y.__imul__ ((self.t_old-1)/self.t) + print ('update, self.x' , self.x.as_array()) self.y.__iadd__( self.x ) + print ('update, self.y' , self.y.as_array()) self.x_old.fill(self.x) + print ('update, self.x_old' , self.x_old.as_array()) self.t_old = self.t def update_objective(self): diff --git a/Wrappers/Python/ccpi/optimisation/functions/Norm2Sq.py b/Wrappers/Python/ccpi/optimisation/functions/Norm2Sq.py index d9d9010..8e77f56 100755 --- a/Wrappers/Python/ccpi/optimisation/functions/Norm2Sq.py +++ b/Wrappers/Python/ccpi/optimisation/functions/Norm2Sq.py @@ -88,7 +88,6 @@ class Norm2Sq(Function): def gradient(self, x, out = None): if self.memopt: #return 2.0*self.c*self.A.adjoint( self.A.direct(x) - self.b ) - print (self.range_tmp, self.range_tmp.as_array()) self.A.direct(x, out=self.range_tmp) self.range_tmp -= self.b self.A.adjoint(self.range_tmp, out=out) diff --git a/Wrappers/Python/ccpi/optimisation/operators/LinearOperatorMatrix.py b/Wrappers/Python/ccpi/optimisation/operators/LinearOperatorMatrix.py index 6306192..292db2c 100644 --- a/Wrappers/Python/ccpi/optimisation/operators/LinearOperatorMatrix.py +++ b/Wrappers/Python/ccpi/optimisation/operators/LinearOperatorMatrix.py @@ -2,8 +2,8 @@ import numpy from scipy.sparse.linalg import svds from ccpi.framework import DataContainer from ccpi.framework import AcquisitionData -from ccpi.framework import ImageData -from ccpi.framework import ImageGeometry +from ccpi.framework import VectorData +from ccpi.framework import VectorGeometry from ccpi.framework import AcquisitionGeometry from numbers import Number from ccpi.optimisation.operators import LinearOperator @@ -11,8 +11,8 @@ class LinearOperatorMatrix(LinearOperator): def __init__(self,A): self.A = A M_A, N_A = self.A.shape - self.gm_domain = ImageGeometry(0, N_A) - self.gm_range = ImageGeometry(M_A,0) + self.gm_domain = VectorGeometry(N_A) + self.gm_range = VectorGeometry(M_A) self.s1 = None # Largest singular value, initially unknown super(LinearOperatorMatrix, self).__init__() @@ -21,18 +21,16 @@ class LinearOperatorMatrix(LinearOperator): return type(x)(numpy.dot(self.A,x.as_array())) else: numpy.dot(self.A, x.as_array(), out=out.as_array()) - - + def adjoint(self,x, out=None): if out is None: return type(x)(numpy.dot(self.A.transpose(),x.as_array())) else: numpy.dot(self.A.transpose(),x.as_array(), out=out.as_array()) - - + def size(self): return self.A.shape - + def norm(self): # If unknown, compute and store. If known, simply return it. if self.s1 is None: -- cgit v1.2.3 From e5d9351e87c92276677a0b26141049205e215860 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Tue, 4 Jun 2019 16:30:46 +0100 Subject: added example file --- Wrappers/Python/wip/fix_test.py | 133 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100755 Wrappers/Python/wip/fix_test.py diff --git a/Wrappers/Python/wip/fix_test.py b/Wrappers/Python/wip/fix_test.py new file mode 100755 index 0000000..7b19910 --- /dev/null +++ b/Wrappers/Python/wip/fix_test.py @@ -0,0 +1,133 @@ +import numpy as np +from ccpi.optimisation.operators import * +from ccpi.optimisation.algorithms import * +from ccpi.optimisation.functions import * +from ccpi.framework import * + +def isSizeCorrect(data1 ,data2): + if issubclass(type(data1), DataContainer) and \ + issubclass(type(data2), DataContainer): + # check dimensionality + if data1.check_dimensions(data2): + return True + elif issubclass(type(data1) , numpy.ndarray) and \ + issubclass(type(data2) , numpy.ndarray): + return data1.shape == data2.shape + else: + raise ValueError("{0}: getting two incompatible types: {1} {2}"\ + .format('Function', type(data1), type(data2))) + return False + +class Norm1(Function): + + def __init__(self,gamma): + super(Norm1, self).__init__() + self.gamma = gamma + self.L = 1 + self.sign_x = None + + def __call__(self,x,out=None): + if out is None: + return self.gamma*(x.abs().sum()) + else: + if not x.shape == out.shape: + raise ValueError('Norm1 Incompatible size:', + x.shape, out.shape) + x.abs(out=out) + return out.sum() * self.gamma + + def prox(self,x,tau): + return (x.abs() - tau*self.gamma).maximum(0) * x.sign() + + def proximal(self, x, tau, out=None): + if out is None: + return self.prox(x, tau) + else: + if isSizeCorrect(x,out): + # check dimensionality + if issubclass(type(out), DataContainer): + v = (x.abs() - tau*self.gamma).maximum(0) + x.sign(out=out) + out *= v + #out.fill(self.prox(x,tau)) + elif issubclass(type(out) , numpy.ndarray): + v = (x.abs() - tau*self.gamma).maximum(0) + out[:] = x.sign() + out *= v + #out[:] = self.prox(x,tau) + else: + raise ValueError ('Wrong size: x{0} out{1}'.format(x.shape,out.shape) ) + +opt = {'memopt': True} +# Problem data. +m = 3 +n = 3 +np.random.seed(1) +#Amat = np.asarray( np.random.randn(m, n), dtype=numpy.float32) +Amat = np.asarray(np.eye(m), dtype=np.float32) * 2 +A = LinearOperatorMatrix(Amat) +bmat = np.asarray( np.random.randn(m), dtype=numpy.float32) +bmat *= 0 +bmat += 2 +print ("bmat", bmat.shape) +print ("A", A.A) +#bmat.shape = (bmat.shape[0], 1) + +# A = Identity() +# Change n to equal to m. +vgb = VectorGeometry(m) +vgx = VectorGeometry(n) +b = vgb.allocate(VectorGeometry.RANDOM, dtype=numpy.float32) +b.fill(bmat) +#b = DataContainer(bmat) + +# Regularization parameter +lam = 10 +opt = {'memopt': True} +# Create object instances with the test data A and b. +f = Norm2Sq(A, b, c=0.5, memopt=True) +#f = FunctionOperatorComposition(A, L2NormSquared(b=bmat)) +g0 = ZeroFunction() + +#f.L = 30.003 +x_init = vgx.allocate(VectorGeometry.RANDOM, dtype=numpy.float32) + +f.L = LinearOperator.PowerMethod(A, 25, x_init)[0] * 2 +print ('f.L', f.L) + +# Initial guess +#x_init = DataContainer(np.zeros((n, 1))) +print ('x_init', x_init.as_array()) +print ('b', b.as_array()) +# Create 1-norm object instance +g1_new = lam * L1Norm() +g1 = Norm1(lam) +g1 = ZeroFunction() +#g1(x_init) +x = g1.prox(x_init, 1/f.L ) +print ("g1.proximal ", x.as_array()) + +x = g1.prox(x_init, 0.03 ) +print ("g1.proximal ", x.as_array()) +x = g1_new.proximal(x_init, 0.03 ) +print ("g1.proximal ", x.as_array()) + + + +# Combine with least squares and solve using generic FISTA implementation +#x_fista1, it1, timing1, criter1 = FISTA(x_init, f, g1, opt=opt) +def callback(it, objective, solution): + print (it, objective, solution.as_array()) + +fa = FISTA(x_init=x_init, f=f, g=g1) +fa.max_iteration = 10 +fa.run(3, callback = callback, verbose=False) + + +# Print for comparison +print("FISTA least squares plus 1-norm solution and objective value:") +print(fa.get_output().as_array()) +print(fa.get_last_objective()) + +print (A.direct(fa.get_output()).as_array()) +print (b.as_array()) \ No newline at end of file -- cgit v1.2.3 From 935361ba734c7a2ecae8835d5f6959d32f4c7403 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Wed, 5 Jun 2019 09:58:00 +0100 Subject: fixed test --- Wrappers/Python/ccpi/framework/VectorData.py | 2 +- Wrappers/Python/ccpi/framework/framework.py | 3 +- .../Python/ccpi/optimisation/algorithms/FISTA.py | 10 ------ Wrappers/Python/wip/fix_test.py | 39 ++++++++++++++++++---- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/VectorData.py b/Wrappers/Python/ccpi/framework/VectorData.py index fdce3a5..d0fed10 100755 --- a/Wrappers/Python/ccpi/framework/VectorData.py +++ b/Wrappers/Python/ccpi/framework/VectorData.py @@ -54,5 +54,5 @@ class VectorData(DataContainer): out = array else: raise ValueError('Incompatible size: expecting {} got {}'.format((self.length,), array.shape)) - deep_copy = False + deep_copy = True super(VectorData, self).__init__(out, deep_copy, None) diff --git a/Wrappers/Python/ccpi/framework/framework.py b/Wrappers/Python/ccpi/framework/framework.py index b972be6..f91343c 100755 --- a/Wrappers/Python/ccpi/framework/framework.py +++ b/Wrappers/Python/ccpi/framework/framework.py @@ -329,7 +329,7 @@ class DataContainer(object): self.dimension_labels = {} self.geometry = None # Only relevant for AcquisitionData and ImageData - if dimension_labels is not None and \ + if dimension_labels != {} and \ len (dimension_labels) == self.number_of_dimensions: for i in range(self.number_of_dimensions): self.dimension_labels[i] = dimension_labels[i] @@ -632,6 +632,7 @@ class DataContainer(object): def pixel_wise_binary(self, pwop, x2, *args, **kwargs): out = kwargs.get('out', None) + if out is None: if isinstance(x2, (int, float, complex)): out = pwop(self.as_array() , x2 , *args, **kwargs ) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py index db4e8b7..4754d9c 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py @@ -62,28 +62,18 @@ class FISTA(Algorithm): def update(self): self.f.gradient(self.y, out=self.u) - print ('update, self.u' , self.u.as_array()) self.u.__imul__( -self.invL ) self.u.__iadd__( self.y ) - print ('update, self.u' , self.u.as_array()) - # x = g.prox(u,invL) - print ('update, self.x pre prox' , self.x.as_array()) self.g.proximal(self.u, self.invL, out=self.x) - print ('update, self.x post prox' , self.x.as_array()) self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) self.x.subtract(self.x_old, out=self.y) - print ('update, self.y' , self.y.as_array()) - self.y.__imul__ ((self.t_old-1)/self.t) - print ('update, self.x' , self.x.as_array()) self.y.__iadd__( self.x ) - print ('update, self.y' , self.y.as_array()) self.x_old.fill(self.x) - print ('update, self.x_old' , self.x_old.as_array()) self.t_old = self.t def update_objective(self): diff --git a/Wrappers/Python/wip/fix_test.py b/Wrappers/Python/wip/fix_test.py index 7b19910..094f571 100755 --- a/Wrappers/Python/wip/fix_test.py +++ b/Wrappers/Python/wip/fix_test.py @@ -60,11 +60,11 @@ class Norm1(Function): opt = {'memopt': True} # Problem data. -m = 3 -n = 3 +m = 30 +n = 30 np.random.seed(1) -#Amat = np.asarray( np.random.randn(m, n), dtype=numpy.float32) -Amat = np.asarray(np.eye(m), dtype=np.float32) * 2 +Amat = np.asarray( np.random.randn(m, n), dtype=numpy.float32) +#Amat = np.asarray(np.eye(m), dtype=np.float32) * 2 A = LinearOperatorMatrix(Amat) bmat = np.asarray( np.random.randn(m), dtype=numpy.float32) bmat *= 0 @@ -92,8 +92,15 @@ g0 = ZeroFunction() #f.L = 30.003 x_init = vgx.allocate(VectorGeometry.RANDOM, dtype=numpy.float32) +a = VectorData(x_init.as_array(), deep_copy=True) + +assert id(x_init.as_array()) != id(a.as_array()) + +#%% f.L = LinearOperator.PowerMethod(A, 25, x_init)[0] * 2 print ('f.L', f.L) +rate = (1 / f.L) / 3 +f.L *= 6 # Initial guess #x_init = DataContainer(np.zeros((n, 1))) @@ -102,6 +109,7 @@ print ('b', b.as_array()) # Create 1-norm object instance g1_new = lam * L1Norm() g1 = Norm1(lam) + g1 = ZeroFunction() #g1(x_init) x = g1.prox(x_init, 1/f.L ) @@ -112,6 +120,16 @@ print ("g1.proximal ", x.as_array()) x = g1_new.proximal(x_init, 0.03 ) print ("g1.proximal ", x.as_array()) +x1 = vgx.allocate(VectorGeometry.RANDOM, dtype=numpy.float32) +pippo = vgx.allocate() + +print ("x_init", x_init.as_array()) +print ("x1", x1.as_array()) +a = x_init.subtract(x1, out=pippo) + +print ("pippo", pippo.as_array()) +print ("x_init", x_init.as_array()) +print ("x1", x1.as_array()) # Combine with least squares and solve using generic FISTA implementation @@ -120,8 +138,14 @@ def callback(it, objective, solution): print (it, objective, solution.as_array()) fa = FISTA(x_init=x_init, f=f, g=g1) -fa.max_iteration = 10 -fa.run(3, callback = callback, verbose=False) +fa.max_iteration = 1000 +fa.update_objective_interval = int( fa.max_iteration / 10 ) +fa.run(fa.max_iteration, callback = None, verbose=True) + +gd = GradientDescent(x_init=x_init, objective_function=f, rate = rate ) +gd.max_iteration = 100000 +gd.update_objective_interval = 10000 +gd.run(gd.max_iteration, callback = None, verbose=True) # Print for comparison @@ -130,4 +154,5 @@ print(fa.get_output().as_array()) print(fa.get_last_objective()) print (A.direct(fa.get_output()).as_array()) -print (b.as_array()) \ No newline at end of file +print (b.as_array()) +print (A.direct(gd.get_output()).as_array()) -- cgit v1.2.3 From 2c0dcf71177795852201bbc999ac75ea88e63bd5 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 5 Jun 2019 12:34:41 +0100 Subject: fista trials --- .../Python/ccpi/optimisation/algorithms/FISTA.py | 3 +- .../CGLS_FISTA_PDHG_LeastSquares.py | 67 ++++++++++------------ Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py | 50 ++++++++-------- .../FISTA_Tikhonov_Poisson_Denoising.py | 13 ++--- .../demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py | 7 --- 5 files changed, 61 insertions(+), 79 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py index 04e7c38..419958a 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py @@ -67,7 +67,8 @@ class FISTA(Algorithm): self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2))) - self.x.subtract(self.x_old, out=self.y) +# self.x.subtract(self.x_old, out=self.y) + self.y = self.x - self.x_old self.y.__imul__ ((self.t_old-1)/self.t) self.y.__iadd__( self.x ) diff --git a/Wrappers/Python/demos/CompareAlgorithms/CGLS_FISTA_PDHG_LeastSquares.py b/Wrappers/Python/demos/CompareAlgorithms/CGLS_FISTA_PDHG_LeastSquares.py index 0875c20..672d4bc 100644 --- a/Wrappers/Python/demos/CompareAlgorithms/CGLS_FISTA_PDHG_LeastSquares.py +++ b/Wrappers/Python/demos/CompareAlgorithms/CGLS_FISTA_PDHG_LeastSquares.py @@ -32,7 +32,7 @@ Problem: min_x || A x - g ||_{2}^{2} """ -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry +from ccpi.framework import ImageData, TestData, AcquisitionGeometry import numpy as np import numpy @@ -42,17 +42,17 @@ from ccpi.optimisation.algorithms import PDHG, CGLS, FISTA from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, FunctionOperatorComposition from ccpi.astra.ops import AstraProjectorSimple -import astra +import astra +import os, sys -# Create Ground truth phantom and Sinogram -N = 128 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) +# Load Data +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) + +N = 50 +M = 50 +data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) +ig = data.geometry detectors = N angles = np.linspace(0, np.pi, N, dtype=np.float32) @@ -69,13 +69,14 @@ sin = Aop.direct(data) noisy_data = sin +############################################################################### # Setup and run Astra CGLS algorithm vol_geom = astra.create_vol_geom(N, N) proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) -proj_id = astra.create_projector('strip', proj_geom, vol_geom) +proj_id = astra.create_projector('linear', proj_geom, vol_geom) # Create a sinogram from a phantom -sinogram_id, sinogram = astra.create_sino(x, proj_id) +sinogram_id, sinogram = astra.create_sino(data.as_array(), proj_id) # Create a data object for the reconstruction rec_id = astra.data2d.create('-vol', vol_geom) @@ -92,6 +93,7 @@ astra.algorithm.run(alg_id, 1000) recon_cgls_astra = astra.data2d.get(rec_id) +############################################################################### # Setup and run the CGLS algorithm x_init = ig.allocate() cgls = CGLS(x_init=x_init, operator=Aop, data=noisy_data) @@ -99,12 +101,15 @@ cgls.max_iteration = 1000 cgls.update_objective_interval = 200 cgls.run(1000, verbose=False) +############################################################################### # Setup and run the PDHG algorithm operator = Aop f = L2NormSquared(b = noisy_data) g = ZeroFunction() + ## Compute operator Norm normK = operator.norm() + ## Primal & dual stepsizes sigma = 1 tau = 1/(sigma*normK**2) @@ -112,17 +117,17 @@ tau = 1/(sigma*normK**2) pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) pdhg.max_iteration = 1000 pdhg.update_objective_interval = 200 -pdhg.run(1000, verbose=False) +pdhg.run(1000, verbose=True) +############################################################################### # Setup and run the FISTA algorithm fidelity = FunctionOperatorComposition(L2NormSquared(b=noisy_data), Aop) regularizer = ZeroFunction() -opt = {'memopt':True} -fista = FISTA(x_init=x_init , f=fidelity, g=regularizer, opt=opt) +fista = FISTA(x_init=x_init , f=fidelity, g=regularizer) fista.max_iteration = 1000 fista.update_objective_interval = 200 -fista.run(1000, verbose=False) +fista.run(1000, verbose=True) #%% Show results @@ -131,18 +136,22 @@ plt.suptitle('Reconstructions ', fontsize=16) plt.subplot(2,2,1) plt.imshow(cgls.get_output().as_array()) +plt.colorbar() plt.title('CGLS reconstruction') plt.subplot(2,2,2) plt.imshow(fista.get_output().as_array()) +plt.colorbar() plt.title('FISTA reconstruction') plt.subplot(2,2,3) plt.imshow(pdhg.get_output().as_array()) +plt.colorbar() plt.title('PDHG reconstruction') plt.subplot(2,2,4) plt.imshow(recon_cgls_astra) +plt.colorbar() plt.title('CGLS astra') diff1 = pdhg.get_output() - cgls.get_output() @@ -167,28 +176,14 @@ plt.title('Diff CLGS astra vs CGLS') plt.colorbar() +#%% +print('Primal Objective (FISTA) {} '.format(fista.objective[-1])) +print('Primal Objective (CGLS) {} '.format(cgls.objective[-1])) +print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) +true_obj = (Aop.direct(cglsd.get_output())-noisy_data).squared_norm() +print('True objective {}'.format(true_obj)) - - - - - - - - - - - - -# -# -# -# -# -# -# -# diff --git a/Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py b/Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py index 68fb649..1a96a2d 100644 --- a/Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py +++ b/Wrappers/Python/demos/FISTA_examples/FISTA_CGLS.py @@ -21,14 +21,15 @@ from ccpi.framework import AcquisitionGeometry from ccpi.optimisation.algorithms import FISTA -from ccpi.optimisation.functions import IndicatorBox, ZeroFunction, L2NormSquared, FunctionOperatorComposition -from ccpi.astra.ops import AstraProjectorSimple +from ccpi.optimisation.functions import IndicatorBox, ZeroFunction, \ + L2NormSquared, FunctionOperatorComposition +from ccpi.astra.operators import AstraProjectorSimple import numpy as np import matplotlib.pyplot as plt from ccpi.framework import TestData import os, sys - +from ccpi.optimisation.funcs import Norm2sq # Load Data loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) @@ -117,16 +118,15 @@ plt.show() # demonstrated in the rest of this file. In general all methods need an initial # guess and some algorithm options to be set: -f = FunctionOperatorComposition(0.5 * L2NormSquared(b=sin), Aop) +f = FunctionOperatorComposition(L2NormSquared(b=sin), Aop) +#f = Norm2sq(Aop, sin, c=0.5, memopt=True) g = ZeroFunction() x_init = ig.allocate() -opt = {'memopt':True} - -fista = FISTA(x_init=x_init, f=f, g=g, opt=opt) -fista.max_iteration = 100 -fista.update_objective_interval = 1 -fista.run(100, verbose=True) +fista = FISTA(x_init=x_init, f=f, g=g) +fista.max_iteration = 1000 +fista.update_objective_interval = 100 +fista.run(1000, verbose=True) plt.figure() plt.imshow(fista.get_output().as_array()) @@ -134,18 +134,12 @@ plt.title('FISTA unconstrained') plt.colorbar() plt.show() -plt.figure() -plt.semilogy(fista.objective) -plt.title('FISTA objective') -plt.show() - -#%% # Run FISTA for least squares with lower/upper bound -fista0 = FISTA(x_init=x_init, f=f, g=IndicatorBox(lower=0,upper=1), opt=opt) -fista0.max_iteration = 100 -fista0.update_objective_interval = 1 -fista0.run(100, verbose=True) +fista0 = FISTA(x_init=x_init, f=f, g=IndicatorBox(lower=0,upper=1)) +fista0.max_iteration = 1000 +fista0.update_objective_interval = 100 +fista0.run(1000, verbose=True) plt.figure() plt.imshow(fista0.get_output().as_array()) @@ -153,10 +147,10 @@ plt.title('FISTA constrained in [0,1]') plt.colorbar() plt.show() -plt.figure() -plt.semilogy(fista0.objective) -plt.title('FISTA constrained in [0,1]') -plt.show() +#plt.figure() +#plt.semilogy(fista0.objective) +#plt.title('FISTA constrained in [0,1]') +#plt.show() #%% Check with CVX solution @@ -178,10 +172,10 @@ if cvx_not_installable: vol_geom = astra.create_vol_geom(N, N) proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) - proj_id = astra.create_projector('strip', proj_geom, vol_geom) + proj_id = astra.create_projector('linear', proj_geom, vol_geom) matrix_id = astra.projector.matrix(proj_id) - + ProjMat = astra.matrix.get(matrix_id) tmp = sin.as_array().ravel() @@ -216,4 +210,6 @@ if cvx_not_installable: plt.legend() plt.title('Middle Line Profiles') plt.show() - \ No newline at end of file + + print('Primal Objective (CVX) {} '.format(obj.value)) + print('Primal Objective (FISTA) {} '.format(fista0.loss[1])) \ No newline at end of file diff --git a/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py b/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py index 41219f4..6007990 100644 --- a/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py +++ b/Wrappers/Python/demos/FISTA_examples/FISTA_Tikhonov_Poisson_Denoising.py @@ -21,7 +21,7 @@ """ -Tikhonov for Poisson denoising using FISTA algorithm: +"Tikhonov regularization" for Poisson denoising using FISTA algorithm: Problem: min_x, x>0 \alpha * ||\nabla x||_{2}^{2} + \int x - g * log(x) @@ -52,14 +52,14 @@ from skimage.util import random_noise loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) # Load Data -N = 150 -M = 150 +N = 100 +M = 100 data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) ig = data.geometry ag = ig -# Create Noisy data. Add Gaussian noise +# Create Noisy data with Poisson noise n1 = random_noise(data.as_array(), mode = 'poisson', seed = 10) noisy_data = ImageData(n1) @@ -75,7 +75,6 @@ plt.title('Noisy Data') plt.colorbar() plt.show() -#%% # Regularisation Parameter alpha = 10 @@ -114,8 +113,7 @@ fid.proximal = KL_Prox_PosCone reg = FunctionOperatorComposition(alpha * L2NormSquared(), operator) x_init = ig.allocate() -opt = {'memopt':True} -fista = FISTA(x_init=x_init , f=reg, g=fid, opt=opt) +fista = FISTA(x_init=x_init , f=reg, g=fid) fista.max_iteration = 2000 fista.update_objective_interval = 500 fista.run(2000, verbose=True) @@ -196,7 +194,6 @@ if cvx_not_installable: plt.title('Middle Line Profiles') plt.show() - #TODO what is the output of fista.objective, fista.loss print('Primal Objective (CVX) {} '.format(obj.value)) print('Primal Objective (FISTA) {} '.format(fista.loss[1])) diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py index bc0081f..422deb9 100644 --- a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py +++ b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py @@ -85,15 +85,8 @@ data = ImageData(data/data.max()) ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) ag = ig -# Create noisy data. Add Gaussian noise -scale = 0.5 -eta = 0 -n1 = scale * np.random.poisson(eta + sin.as_array()/scale) - -noisy_data = AcquisitionData(n1, ag) #Create Acquisition Data and apply poisson noise - detectors = N angles = np.linspace(0, np.pi, N) -- cgit v1.2.3 From 4feb2618627f87e56671c4b8faf4c55b4320235e Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 5 Jun 2019 12:35:48 +0100 Subject: new phantom image --- Wrappers/Python/data/shapes.png | Bin 0 -> 19339 bytes .../MultiChannel/PDHG_TV_Denoising_2D_time.py | 192 +++++++++++++++++++++ .../MultiChannel/PDHG_TV_Denoising_Gaussian_3D.py | 181 +++++++++++++++++++ 3 files changed, 373 insertions(+) create mode 100644 Wrappers/Python/data/shapes.png create mode 100644 Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_2D_time.py create mode 100644 Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_Gaussian_3D.py diff --git a/Wrappers/Python/data/shapes.png b/Wrappers/Python/data/shapes.png new file mode 100644 index 0000000..dd4f680 Binary files /dev/null and b/Wrappers/Python/data/shapes.png differ diff --git a/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_2D_time.py b/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_2D_time.py new file mode 100644 index 0000000..14608db --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_2D_time.py @@ -0,0 +1,192 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient, Identity +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorMC + +import os +import tomophantom +from tomophantom import TomoP2D + +# Create phantom for TV 2D dynamic tomography + +model = 102 # note that the selected model is temporal (2D + time) +N = 128 # set dimension of the phantom +# one can specify an exact path to the parameters file +# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' +path = os.path.dirname(tomophantom.__file__) +path_library2D = os.path.join(path, "Phantom2DLibrary.dat") +#This will generate a N_size x N_size x Time frames phantom (2D + time) +phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) + +plt.close('all') +plt.figure(1) +plt.rcParams.update({'font.size': 21}) +plt.title('{}''{}'.format('2D+t phantom using model no.',model)) +for sl in range(0,np.shape(phantom_2Dt)[0]): + im = phantom_2Dt[sl,:,:] + plt.imshow(im, vmin=0, vmax=1) +# plt.pause(.1) +# plt.draw + + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) +data = ImageData(phantom_2Dt, geometry=ig) +ag = ig + +# Create Noisy data. Add Gaussian noise +np.random.seed(10) +noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.25, size=ig.shape) ) + +tindex = [3, 6, 10] + +fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) +plt.subplot(1,3,1) +plt.imshow(noisy_data.as_array()[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) +plt.subplot(1,3,2) +plt.imshow(noisy_data.as_array()[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) +plt.subplot(1,3,3) +plt.imshow(noisy_data.as_array()[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +plt.show() + +#%% +# Regularisation Parameter +alpha = 0.3 + +# Create operators +#op1 = Gradient(ig) +op1 = Gradient(ig, correlation='Space') +op2 = Gradient(ig, correlation='SpaceChannels') + +op3 = Identity(ig, ag) + +# Create BlockOperator +operator1 = BlockOperator(op1, op3, shape=(2,1) ) +operator2 = BlockOperator(op2, op3, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = 0.5 * L2NormSquared(b = noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK1 = operator1.norm() +normK2 = operator2.norm() + +#%% +# Primal & dual stepsizes +sigma1 = 1 +tau1 = 1/(sigma1*normK1**2) + +sigma2 = 1 +tau2 = 1/(sigma2*normK2**2) + +# Setup and run the PDHG algorithm +pdhg1 = PDHG(f=f,g=g,operator=operator1, tau=tau1, sigma=sigma1) +pdhg1.max_iteration = 2000 +pdhg1.update_objective_interval = 200 +pdhg1.run(2000) + +# Setup and run the PDHG algorithm +pdhg2 = PDHG(f=f,g=g,operator=operator2, tau=tau2, sigma=sigma2) +pdhg2.max_iteration = 2000 +pdhg2.update_objective_interval = 200 +pdhg2.run(2000) + + +#%% + +tindex = [3, 6, 10] +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) + +plt.subplot(3,3,1) +plt.imshow(phantom_2Dt[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) + +plt.subplot(3,3,2) +plt.imshow(phantom_2Dt[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) + +plt.subplot(3,3,3) +plt.imshow(phantom_2Dt[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +plt.subplot(3,3,4) +plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(3,3,5) +plt.imshow(pdhg1.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(3,3,6) +plt.imshow(pdhg1.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') + + +plt.subplot(3,3,7) +plt.imshow(pdhg2.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(3,3,8) +plt.imshow(pdhg2.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(3,3,9) +plt.imshow(pdhg2.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') + +#%% +im = plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + diff --git a/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_Gaussian_3D.py b/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_Gaussian_3D.py new file mode 100644 index 0000000..03dc2ef --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_Gaussian_3D.py @@ -0,0 +1,181 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= +""" + +Total Variation (3D) Denoising using PDHG algorithm: + + +Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Gaussian Noise + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Gaussian denoising +import timeit +import os +from tomophantom import TomoP3D +import tomophantom + +print ("Building 3D phantom using TomoPhantom software") +tic=timeit.default_timer() +model = 13 # select a model number from the library +N = 64 # Define phantom dimensions using a scalar value (cubic phantom) +path = os.path.dirname(tomophantom.__file__) +path_library3D = os.path.join(path, "Phantom3DLibrary.dat") + +#This will generate a N x N x N phantom (3D) +phantom_tm = TomoP3D.Model(model, N, path_library3D) + +#%% + +# Create noisy data. Add Gaussian noise +ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N, voxel_num_z=N) +ag = ig +n1 = random_noise(phantom_tm, mode = 'gaussian', mean=0, var = 0.001, seed=10) +noisy_data = ImageData(n1) + +sliceSel = int(0.5*N) +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.title('Axial View') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.title('Coronal View') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.title('Sagittal View') +plt.colorbar() +plt.show() + +#%% + +# Regularisation Parameter +alpha = 0.05 + +method = '0' + +if method == '0': + + # Create operators + op1 = Gradient(ig) + op2 = Identity(ig, ag) + + # Create BlockOperator + operator = BlockOperator(op1, op2, shape=(2,1) ) + + # Create functions + + f1 = alpha * MixedL21Norm() + f2 = 0.5 * L2NormSquared(b = noisy_data) + f = BlockFunction(f1, f2) + + g = ZeroFunction() + +else: + + # Without the "Block Framework" + operator = Gradient(ig) + f = alpha * MixedL21Norm() + g = 0.5 * L2NormSquared(b = noisy_data) + + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000, verbose = True) + + +#%% +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) +fig.suptitle('TV Reconstruction',fontsize=20) + + +plt.subplot(2,3,1) +plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Axial View') + +plt.subplot(2,3,2) +plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Coronal View') + +plt.subplot(2,3,3) +plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.axis('off') +plt.title('Sagittal View') + + +plt.subplot(2,3,4) +plt.imshow(pdhg.get_output().as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.axis('off') +plt.subplot(2,3,5) +plt.imshow(pdhg.get_output().as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.axis('off') +plt.subplot(2,3,6) +plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.axis('off') +im = plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + -- cgit v1.2.3 From abcdbb9db1d5eb335bf8b3e82d888f958c9ddf7a Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 5 Jun 2019 12:36:30 +0100 Subject: color denoising --- .../demos/PDHG_examples/PDHG_TV_Color_Denoising.py | 115 +++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 Wrappers/Python/demos/PDHG_examples/PDHG_TV_Color_Denoising.py diff --git a/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Color_Denoising.py b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Color_Denoising.py new file mode 100644 index 0000000..ddf5ace --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/PDHG_TV_Color_Denoising.py @@ -0,0 +1,115 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" +Total Variation Denoising using PDHG algorithm: +Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + ||x-g||_{1} + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: Noisy Data with Salt & Pepper Noise + + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + + +""" + +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import Gradient, BlockOperator, FiniteDiff +from ccpi.optimisation.functions import MixedL21Norm, MixedL11Norm, L2NormSquared, BlockFunction, L1Norm +from ccpi.framework import TestData, ImageGeometry +import os, sys +if int(numpy.version.version.split('.')[1]) > 12: + from skimage.util import random_noise +else: + from demoutil import random_noise + +loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) +data = loader.load(TestData.PEPPERS, size=(256,256)) +ig = data.geometry +ag = ig + +# Create noisy data. +n1 = random_noise(data.as_array(), mode = 'gaussian', var = 0.15, seed = 50) +noisy_data = ig.allocate() +noisy_data.fill(n1) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,5)) +plt.subplot(1,2,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(1,2,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Regularisation Parameter +operator = Gradient(ig, correlation=Gradient.CORRELATION_SPACE) +f1 = 5 * MixedL21Norm() +g = 0.5 * L2NormSquared(b=noisy_data) + +# Compute operator Norm +normK = operator.norm() + +# Primal & dual stepsizes +sigma = 1 +tau = 1/(sigma*normK**2) + +# Setup and run the PDHG algorithm +pdhg1 = PDHG(f=f1,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg1.max_iteration = 2000 +pdhg1.update_objective_interval = 200 +pdhg1.run(1000) + + +# Show results +plt.figure(figsize=(10,10)) +plt.subplot(2,2,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(2,2,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(2,2,3) +plt.imshow(pdhg1.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.subplot(2,2,4) +plt.imshow(pdhg2.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() + -- cgit v1.2.3 From 940a1371fdf88e8c9e8230cece6fa1c73842804c Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 6 Jun 2019 10:07:37 +0100 Subject: add callback --- Wrappers/Python/wip/compare_CGLS_algos.py | 28 +++++++++++++++++----------- Wrappers/Python/wip/fix_test.py | 31 ++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/Wrappers/Python/wip/compare_CGLS_algos.py b/Wrappers/Python/wip/compare_CGLS_algos.py index 119752c..52f3f31 100644 --- a/Wrappers/Python/wip/compare_CGLS_algos.py +++ b/Wrappers/Python/wip/compare_CGLS_algos.py @@ -12,6 +12,8 @@ from ccpi.optimisation.algorithms import CGLS as CGLSalg import numpy as np import matplotlib.pyplot as plt +from ccpi.optimisation.functions import Norm2Sq + # Choose either a parallel-beam (1=parallel2D) or fan-beam (2=cone2D) test case test_case = 1 @@ -101,25 +103,29 @@ x_CGLS, it_CGLS, timing_CGLS, criter_CGLS = CGLS(x_init, Aop, b, opt) #plt.title('CGLS criterion') #plt.show() +f = Norm2Sq(Aop, b, c=1.) +def callback(it, objective, solution): + print (objective, f(solution)) # Now CLGS using the algorithm class CGLS_alg = CGLSalg() CGLS_alg.set_up(x_init, Aop, b ) -CGLS_alg.max_iteration = 2000 -CGLS_alg.run(opt['iter']) +CGLS_alg.max_iteration = 500 +CGLS_alg.update_objective_interval = 10 +CGLS_alg.run(300, callback=callback) x_CGLS_alg = CGLS_alg.get_output() -#plt.figure() -#plt.imshow(x_CGLS_alg.as_array()) -#plt.title('CGLS ALG') -#plt.colorbar() -#plt.show() +plt.figure() +plt.imshow(x_CGLS_alg.as_array()) +plt.title('CGLS ALG') +plt.colorbar() +plt.show() -#plt.figure() -#plt.semilogy(CGLS_alg.objective) -#plt.title('CGLS criterion') -#plt.show() +plt.figure() +plt.semilogy(CGLS_alg.objective) +plt.title('CGLS criterion') +plt.show() print(criter_CGLS) print(CGLS_alg.objective) diff --git a/Wrappers/Python/wip/fix_test.py b/Wrappers/Python/wip/fix_test.py index 094f571..5e40d70 100755 --- a/Wrappers/Python/wip/fix_test.py +++ b/Wrappers/Python/wip/fix_test.py @@ -1,4 +1,5 @@ import numpy as np +import numpy from ccpi.optimisation.operators import * from ccpi.optimisation.algorithms import * from ccpi.optimisation.functions import * @@ -97,10 +98,10 @@ a = VectorData(x_init.as_array(), deep_copy=True) assert id(x_init.as_array()) != id(a.as_array()) #%% -f.L = LinearOperator.PowerMethod(A, 25, x_init)[0] * 2 +f.L = LinearOperator.PowerMethod(A, 25, x_init)[0] print ('f.L', f.L) -rate = (1 / f.L) / 3 -f.L *= 6 +rate = (1 / f.L) / 6 +f.L *= 12 # Initial guess #x_init = DataContainer(np.zeros((n, 1))) @@ -138,7 +139,7 @@ def callback(it, objective, solution): print (it, objective, solution.as_array()) fa = FISTA(x_init=x_init, f=f, g=g1) -fa.max_iteration = 1000 +fa.max_iteration = 10000 fa.update_objective_interval = int( fa.max_iteration / 10 ) fa.run(fa.max_iteration, callback = None, verbose=True) @@ -147,12 +148,28 @@ gd.max_iteration = 100000 gd.update_objective_interval = 10000 gd.run(gd.max_iteration, callback = None, verbose=True) +cgls = CGLS(x_init= x_init, operator=A, data=b) +cgls.max_iteration = 200 +cgls.update_objective_interval = 1 +def stop_criterion(alg): + try: + x = alg.get_last_objective() + print (x) + a = True if x < numpy.finfo(numpy.float32).eps else False + except IndexError as ie: + a = False + def f (): + return a or alg.max_iteration_stop_cryterion() + return f +#cgls.should_stop = stop_criterion(cgls) +cgls.run(cgls.max_iteration, callback = None, verbose=True) # Print for comparison print("FISTA least squares plus 1-norm solution and objective value:") print(fa.get_output().as_array()) print(fa.get_last_objective()) -print (A.direct(fa.get_output()).as_array()) -print (b.as_array()) -print (A.direct(gd.get_output()).as_array()) +print ("data ", b.as_array()) +print ('FISTA ', A.direct(fa.get_output()).as_array()) +print ('GradientDescent', A.direct(gd.get_output()).as_array()) +print ('CGLS ', A.direct(cgls.get_output()).as_array()) -- cgit v1.2.3 From b234f4cf26ee56da94211dc15c9b277c7c29fff4 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 6 Jun 2019 10:18:31 +0100 Subject: add prints --- Wrappers/Python/wip/fix_test.py | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/Wrappers/Python/wip/fix_test.py b/Wrappers/Python/wip/fix_test.py index 5e40d70..316606e 100755 --- a/Wrappers/Python/wip/fix_test.py +++ b/Wrappers/Python/wip/fix_test.py @@ -61,8 +61,8 @@ class Norm1(Function): opt = {'memopt': True} # Problem data. -m = 30 -n = 30 +m = 4 +n = 10 np.random.seed(1) Amat = np.asarray( np.random.randn(m, n), dtype=numpy.float32) #Amat = np.asarray(np.eye(m), dtype=np.float32) * 2 @@ -78,20 +78,21 @@ print ("A", A.A) # Change n to equal to m. vgb = VectorGeometry(m) vgx = VectorGeometry(n) -b = vgb.allocate(VectorGeometry.RANDOM, dtype=numpy.float32) -b.fill(bmat) +b = vgb.allocate(2, dtype=numpy.float32) +# b.fill(bmat) #b = DataContainer(bmat) # Regularization parameter lam = 10 opt = {'memopt': True} # Create object instances with the test data A and b. -f = Norm2Sq(A, b, c=0.5, memopt=True) +f = Norm2Sq(A, b, c=1., memopt=True) #f = FunctionOperatorComposition(A, L2NormSquared(b=bmat)) g0 = ZeroFunction() #f.L = 30.003 x_init = vgx.allocate(VectorGeometry.RANDOM, dtype=numpy.float32) +x_initcgls = x_init.copy() a = VectorData(x_init.as_array(), deep_copy=True) @@ -136,33 +137,24 @@ print ("x1", x1.as_array()) # Combine with least squares and solve using generic FISTA implementation #x_fista1, it1, timing1, criter1 = FISTA(x_init, f, g1, opt=opt) def callback(it, objective, solution): - print (it, objective, solution.as_array()) + print (objective, f(solution)) fa = FISTA(x_init=x_init, f=f, g=g1) -fa.max_iteration = 10000 +fa.max_iteration = 100 fa.update_objective_interval = int( fa.max_iteration / 10 ) fa.run(fa.max_iteration, callback = None, verbose=True) gd = GradientDescent(x_init=x_init, objective_function=f, rate = rate ) -gd.max_iteration = 100000 -gd.update_objective_interval = 10000 +gd.max_iteration = 100 +gd.update_objective_interval = int( gd.max_iteration / 10 ) gd.run(gd.max_iteration, callback = None, verbose=True) -cgls = CGLS(x_init= x_init, operator=A, data=b) -cgls.max_iteration = 200 -cgls.update_objective_interval = 1 -def stop_criterion(alg): - try: - x = alg.get_last_objective() - print (x) - a = True if x < numpy.finfo(numpy.float32).eps else False - except IndexError as ie: - a = False - def f (): - return a or alg.max_iteration_stop_cryterion() - return f +cgls = CGLS(x_init= x_initcgls, operator=A, data=b) +cgls.max_iteration = 1000 +cgls.update_objective_interval = 2 + #cgls.should_stop = stop_criterion(cgls) -cgls.run(cgls.max_iteration, callback = None, verbose=True) +cgls.run(10, callback = callback, verbose=True) # Print for comparison print("FISTA least squares plus 1-norm solution and objective value:") -- cgit v1.2.3 From 1bdd5f572988caa3888b33a0b422692fa78962ef Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 6 Jun 2019 10:19:57 +0100 Subject: add memopt and some checks --- .../Python/ccpi/optimisation/algorithms/CGLS.py | 49 +++++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py b/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py index e65bc89..cb4f049 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py @@ -23,6 +23,8 @@ Created on Thu Feb 21 11:11:23 2019 """ from ccpi.optimisation.algorithms import Algorithm +import numpy + class CGLS(Algorithm): '''Conjugate Gradient Least Squares algorithm @@ -54,13 +56,16 @@ class CGLS(Algorithm): self.normr2 = self.d.squared_norm() + + self.s = self.operator.domain_geometry().allocate() #if isinstance(self.normr2, Iterable): # self.normr2 = sum(self.normr2) #self.normr2 = numpy.sqrt(self.normr2) #print ("set_up" , self.normr2) def update(self): - + self.update_new() + def update_old(self): Ad = self.operator.direct(self.d) #norm = (Ad*Ad).sum() #if isinstance(norm, Iterable): @@ -82,5 +87,45 @@ class CGLS(Algorithm): self.normr2 = normr2_new self.d = s + beta*self.d + def update_new(self): + + Ad = self.operator.direct(self.d) + norm = Ad.squared_norm() + if norm == 0.: + print ('cannot update solution') + raise StopIteration() + alpha = self.normr2/norm + if alpha == 0.: + print ('cannot update solution') + raise StopIteration() + self.d *= alpha + Ad *= alpha + self.r -= Ad + if numpy.isnan(self.r.as_array()).any(): + print ("some nan") + raise StopIteration() + self.x += self.d + + self.operator.adjoint(self.r, out=self.s) + s = self.s + + normr2_new = s.squared_norm() + + beta = normr2_new/self.normr2 + self.normr2 = normr2_new + self.d *= (beta/alpha) + self.d += s + def update_objective(self): - self.loss.append(self.r.squared_norm()) + a = self.r.squared_norm() + if a is numpy.nan: + raise StopIteration() + self.loss.append(a) + +# def should_stop(self): +# if self.iteration > 0: +# x = self.get_last_objective() +# a = x > 0 +# return self.max_iteration_stop_cryterion() or (not a) +# else: +# return False -- cgit v1.2.3 From 218dc267849a8fd53f3e92ebe56d2a5926a302b9 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 7 Jun 2019 09:30:34 +0100 Subject: reinstated check of dimension_label None --- Wrappers/Python/ccpi/framework/framework.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/framework/framework.py b/Wrappers/Python/ccpi/framework/framework.py index f91343c..e1927c3 100755 --- a/Wrappers/Python/ccpi/framework/framework.py +++ b/Wrappers/Python/ccpi/framework/framework.py @@ -329,7 +329,7 @@ class DataContainer(object): self.dimension_labels = {} self.geometry = None # Only relevant for AcquisitionData and ImageData - if dimension_labels != {} and \ + if dimension_labels is not None and \ len (dimension_labels) == self.number_of_dimensions: for i in range(self.number_of_dimensions): self.dimension_labels[i] = dimension_labels[i] -- cgit v1.2.3 From 6005c3075db20bada44caf31429f0adc28b3907e Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 7 Jun 2019 16:24:16 +0100 Subject: added bool configured to Algorithm Algorithms will not run if not configured (meaning set_up has been run). closes #304 --- .../ccpi/optimisation/algorithms/Algorithm.py | 3 ++ .../Python/ccpi/optimisation/algorithms/CGLS.py | 6 ++- .../Python/ccpi/optimisation/algorithms/FBPD.py | 48 ++++++++++------------ .../Python/ccpi/optimisation/algorithms/FISTA.py | 1 + .../optimisation/algorithms/GradientDescent.py | 3 +- .../Python/ccpi/optimisation/algorithms/PDHG.py | 10 ++++- .../Python/ccpi/optimisation/algorithms/SIRT.py | 8 ++-- 7 files changed, 47 insertions(+), 32 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py index 4fbf83b..c62d0ea 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py @@ -51,6 +51,7 @@ class Algorithm(object): self.__max_iteration = kwargs.get('max_iteration', 0) self.__loss = [] self.memopt = False + self.configured = False self.timing = [] self.update_objective_interval = kwargs.get('update_objective_interval', 1) def set_up(self, *args, **kwargs): @@ -86,6 +87,8 @@ class Algorithm(object): raise StopIteration() else: time0 = time.time() + if not self.configured: + raise ValueError('Algorithm not configured correctly. Please run set_up.') self.update() self.timing.append( time.time() - time0 ) if self.iteration % self.update_objective_interval == 0: diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py b/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py index 4d4843c..6b610a0 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py @@ -23,6 +23,7 @@ Created on Thu Feb 21 11:11:23 2019 """ from ccpi.optimisation.algorithms import Algorithm +from ccpi.optimisation.functions import Norm2Sq class CGLS(Algorithm): @@ -59,6 +60,9 @@ class CGLS(Algorithm): # self.normr2 = sum(self.normr2) #self.normr2 = numpy.sqrt(self.normr2) #print ("set_up" , self.normr2) + n = Norm2Sq(operator, self.data) + self.loss.append(n(x_init)) + self.configured = True def update(self): @@ -84,4 +88,4 @@ class CGLS(Algorithm): self.d = s + beta*self.d def update_objective(self): - self.loss.append(self.r.squared_norm()) \ No newline at end of file + self.loss.append(self.r.squared_norm()) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/FBPD.py b/Wrappers/Python/ccpi/optimisation/algorithms/FBPD.py index aa07359..269438c 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/FBPD.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/FBPD.py @@ -35,52 +35,48 @@ class FBPD(Algorithm): h: regularizer opt: additional algorithm ''' - constraint = None - data_fidelity = None - regulariser = None def __init__(self, **kwargs): - pass - def set_up(self, x_init, operator=None, constraint=None, data_fidelity=None,\ - regulariser=None, opt=None): + super(FBPD, self).__init__() + self.f = kwargs.get('f', None) + self.g = kwargs.get('g', ZeroFunction()) + self.g = kwargs.get('h', ZeroFunction()) + self.operator = kwargs.get('operator', None) + self.x_init = kwargs.get('x_init',None) + if self.x_init is not None and self.operator is not None: + self.set_up(self.x_init, self.operator, self.f, self.g, self.h) + + def set_up(self, x_init, operator, constraint, data_fidelity, + regulariser, opt=None): - # default inputs - if constraint is None: - self.constraint = ZeroFun() - else: - self.constraint = constraint - if data_fidelity is None: - data_fidelity = ZeroFun() - else: - self.data_fidelity = data_fidelity - if regulariser is None: - self.regulariser = ZeroFun() - else: - self.regulariser = regulariser # algorithmic parameters # step-sizes - self.tau = 2 / (self.data_fidelity.L + 2) - self.sigma = (1/self.tau - self.data_fidelity.L/2) / self.regulariser.L + self.tau = 2 / (data_fidelity.L + 2) + self.sigma = (1/self.tau - data_fidelity.L/2) / regulariser.L self.inv_sigma = 1/self.sigma # initialization self.x = x_init self.y = operator.direct(self.x) + self.update_objective() + self.configured = True def update(self): # primal forward-backward step x_old = self.x - self.x = self.x - self.tau * ( self.data_fidelity.grad(self.x) + self.operator.adjoint(self.y) ) - self.x = self.constraint.prox(self.x, self.tau); + self.x = self.x - self.tau * ( self.g.gradient(self.x) + self.operator.adjoint(self.y) ) + self.x = self.f.proximal(self.x, self.tau) # dual forward-backward step - self.y = self.y + self.sigma * self.operator.direct(2*self.x - x_old); - self.y = self.y - self.sigma * self.regulariser.prox(self.inv_sigma*self.y, self.inv_sigma); + self.y += self.sigma * self.operator.direct(2*self.x - x_old); + self.y -= self.sigma * self.h.proximal(self.inv_sigma*self.y, self.inv_sigma) # time and criterion - self.loss = self.constraint(self.x) + self.data_fidelity(self.x) + self.regulariser(self.operator.direct(self.x)) + + def update_objective(self): + self.loss.append(self.f(self.x) + self.g(self.x) + self.h(self.operator.direct(self.x))) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py index 3c7a8d1..647ae98 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py @@ -58,6 +58,7 @@ class FISTA(Algorithm): self.t_old = 1 self.update_objective() + self.configured = True def update(self): diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/GradientDescent.py b/Wrappers/Python/ccpi/optimisation/algorithms/GradientDescent.py index 14763c5..34bf954 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/GradientDescent.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/GradientDescent.py @@ -40,7 +40,7 @@ class GradientDescent(Algorithm): if k in args: args.pop(args.index(k)) if len(args) == 0: - return self.set_up(x_init=kwargs['x_init'], + self.set_up(x_init=kwargs['x_init'], objective_function=kwargs['objective_function'], rate=kwargs['rate']) @@ -61,6 +61,7 @@ class GradientDescent(Algorithm): self.memopt = False if self.memopt: self.x_update = x_init.copy() + self.configured = True def update(self): '''Single iteration''' diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py index 39b092b..3afd8b0 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py @@ -23,10 +23,16 @@ class PDHG(Algorithm): self.operator = kwargs.get('operator', None) self.g = kwargs.get('g', None) self.tau = kwargs.get('tau', None) - self.sigma = kwargs.get('sigma', None) + self.sigma = kwargs.get('sigma', 1.) + if self.f is not None and self.operator is not None and \ self.g is not None: + if self.tau is None: + # Compute operator Norm + normK = self.operator.norm() + # Primal & dual stepsizes + self.tau = 1/(self.sigma*normK**2) print ("Calling from creator") self.set_up(self.f, self.g, @@ -57,6 +63,8 @@ class PDHG(Algorithm): # relaxation parameter self.theta = 1 + self.update_objective() + self.configured = True def update(self): diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/SIRT.py b/Wrappers/Python/ccpi/optimisation/algorithms/SIRT.py index 30584d4..c73d323 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/SIRT.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/SIRT.py @@ -59,6 +59,7 @@ class SIRT(Algorithm): # Set up scaling matrices D and M. self.M = 1/self.operator.direct(self.operator.domain_geometry().allocate(value=1.0)) self.D = 1/self.operator.adjoint(self.operator.range_geometry().allocate(value=1.0)) + self.configured = True def update(self): @@ -67,8 +68,9 @@ class SIRT(Algorithm): self.x += self.relax_par * (self.D*self.operator.adjoint(self.M*self.r)) - if self.constraint != None: - self.x = self.constraint.prox(self.x,None) + if self.constraint is not None: + self.x = self.constraint.proximal(self.x,None) + # self.constraint.proximal(self.x,None, out=self.x) def update_objective(self): - self.loss.append(self.r.squared_norm()) \ No newline at end of file + self.loss.append(self.r.squared_norm()) -- cgit v1.2.3 From 1d7324cec6f10f00b73af2bab3469202c5cc2e87 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Fri, 7 Jun 2019 16:35:09 +0100 Subject: removed FBPD --- .../Python/ccpi/optimisation/algorithms/FBPD.py | 82 ---------------------- .../ccpi/optimisation/algorithms/__init__.py | 2 - Wrappers/Python/test/test_algorithms.py | 1 - 3 files changed, 85 deletions(-) delete mode 100644 Wrappers/Python/ccpi/optimisation/algorithms/FBPD.py diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/FBPD.py b/Wrappers/Python/ccpi/optimisation/algorithms/FBPD.py deleted file mode 100644 index 269438c..0000000 --- a/Wrappers/Python/ccpi/optimisation/algorithms/FBPD.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2019 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Created on Thu Feb 21 11:09:03 2019 - -@author: ofn77899 -""" - -from ccpi.optimisation.algorithms import Algorithm -from ccpi.optimisation.functions import ZeroFunction - -class FBPD(Algorithm): - '''FBPD Algorithm - - Parameters: - x_init: initial guess - f: constraint - g: data fidelity - h: regularizer - opt: additional algorithm - ''' - def __init__(self, **kwargs): - super(FBPD, self).__init__() - self.f = kwargs.get('f', None) - self.g = kwargs.get('g', ZeroFunction()) - self.g = kwargs.get('h', ZeroFunction()) - self.operator = kwargs.get('operator', None) - self.x_init = kwargs.get('x_init',None) - if self.x_init is not None and self.operator is not None: - self.set_up(self.x_init, self.operator, self.f, self.g, self.h) - - def set_up(self, x_init, operator, constraint, data_fidelity, - regulariser, opt=None): - - - # algorithmic parameters - - - # step-sizes - self.tau = 2 / (data_fidelity.L + 2) - self.sigma = (1/self.tau - data_fidelity.L/2) / regulariser.L - - self.inv_sigma = 1/self.sigma - - # initialization - self.x = x_init - self.y = operator.direct(self.x) - self.update_objective() - self.configured = True - - - def update(self): - - # primal forward-backward step - x_old = self.x - self.x = self.x - self.tau * ( self.g.gradient(self.x) + self.operator.adjoint(self.y) ) - self.x = self.f.proximal(self.x, self.tau) - - # dual forward-backward step - self.y += self.sigma * self.operator.direct(2*self.x - x_old); - self.y -= self.sigma * self.h.proximal(self.inv_sigma*self.y, self.inv_sigma) - - # time and criterion - - def update_objective(self): - self.loss.append(self.f(self.x) + self.g(self.x) + self.h(self.operator.direct(self.x))) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__init__.py b/Wrappers/Python/ccpi/optimisation/algorithms/__init__.py index 2dbacfc..8f255f3 100644 --- a/Wrappers/Python/ccpi/optimisation/algorithms/__init__.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/__init__.py @@ -27,7 +27,5 @@ from .CGLS import CGLS from .SIRT import SIRT from .GradientDescent import GradientDescent from .FISTA import FISTA -from .FBPD import FBPD from .PDHG import PDHG -from .PDHG import PDHG_old diff --git a/Wrappers/Python/test/test_algorithms.py b/Wrappers/Python/test/test_algorithms.py index 4121358..8c398f4 100755 --- a/Wrappers/Python/test/test_algorithms.py +++ b/Wrappers/Python/test/test_algorithms.py @@ -18,7 +18,6 @@ from ccpi.optimisation.functions import Norm2Sq, ZeroFunction, \ from ccpi.optimisation.algorithms import GradientDescent from ccpi.optimisation.algorithms import CGLS from ccpi.optimisation.algorithms import FISTA -from ccpi.optimisation.algorithms import FBPD -- cgit v1.2.3 From 7da4f6510fb9ffb6b8c07f39ae5ffc93ffb24972 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 10 Jun 2019 11:49:17 +0100 Subject: denoising exam with cvx cmp --- Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py | 2 +- Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py | 5 ++--- .../Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py index 8453d20..9dbcf3e 100755 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TGV_Denoising.py @@ -241,7 +241,7 @@ if cvx_not_installable: solver = MOSEK else: solver = SCS - + # fidelity if noise == 's&p': fidelity = pnorm( u - noisy_data.as_array(),1) diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py index 74e7901..d848b9f 100755 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py @@ -239,7 +239,7 @@ if cvx_not_installable: solver = MOSEK else: solver = SCS - + # fidelity if noise == 's&p': fidelity = pnorm( u - noisy_data.as_array(),1) @@ -248,8 +248,7 @@ if cvx_not_installable: solver = SCS elif noise == 'gaussian': fidelity = 0.5 * sum_squares(noisy_data.as_array() - u) - - + obj = Minimize( regulariser + fidelity) prob = Problem(obj) result = prob.solve(verbose = True, solver = solver) diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py index e16c5a6..78d4980 100644 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_Tikhonov_Denoising.py @@ -195,7 +195,6 @@ try: except ImportError: cvx_not_installable = False - if cvx_not_installable: ##Construct problem -- cgit v1.2.3 From bb7657fbf8fe00de7f674e31fa6d1dbd130e79fc Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 10 Jun 2019 12:50:34 +0100 Subject: demos with tomophantom --- .../GatherAll/PDHG_TV_Denoising_2D_time.py | 192 +++++++++++++++++++++ .../GatherAll/PDHG_TV_Denoising_Gaussian_3D.py | 152 ++++++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_2D_time.py create mode 100644 Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_Gaussian_3D.py diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_2D_time.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_2D_time.py new file mode 100644 index 0000000..14608db --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_2D_time.py @@ -0,0 +1,192 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient, Identity +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from ccpi.astra.ops import AstraProjectorMC + +import os +import tomophantom +from tomophantom import TomoP2D + +# Create phantom for TV 2D dynamic tomography + +model = 102 # note that the selected model is temporal (2D + time) +N = 128 # set dimension of the phantom +# one can specify an exact path to the parameters file +# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' +path = os.path.dirname(tomophantom.__file__) +path_library2D = os.path.join(path, "Phantom2DLibrary.dat") +#This will generate a N_size x N_size x Time frames phantom (2D + time) +phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) + +plt.close('all') +plt.figure(1) +plt.rcParams.update({'font.size': 21}) +plt.title('{}''{}'.format('2D+t phantom using model no.',model)) +for sl in range(0,np.shape(phantom_2Dt)[0]): + im = phantom_2Dt[sl,:,:] + plt.imshow(im, vmin=0, vmax=1) +# plt.pause(.1) +# plt.draw + + +ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) +data = ImageData(phantom_2Dt, geometry=ig) +ag = ig + +# Create Noisy data. Add Gaussian noise +np.random.seed(10) +noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.25, size=ig.shape) ) + +tindex = [3, 6, 10] + +fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) +plt.subplot(1,3,1) +plt.imshow(noisy_data.as_array()[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) +plt.subplot(1,3,2) +plt.imshow(noisy_data.as_array()[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) +plt.subplot(1,3,3) +plt.imshow(noisy_data.as_array()[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +plt.show() + +#%% +# Regularisation Parameter +alpha = 0.3 + +# Create operators +#op1 = Gradient(ig) +op1 = Gradient(ig, correlation='Space') +op2 = Gradient(ig, correlation='SpaceChannels') + +op3 = Identity(ig, ag) + +# Create BlockOperator +operator1 = BlockOperator(op1, op3, shape=(2,1) ) +operator2 = BlockOperator(op2, op3, shape=(2,1) ) + +# Create functions + +f1 = alpha * MixedL21Norm() +f2 = 0.5 * L2NormSquared(b = noisy_data) +f = BlockFunction(f1, f2) + +g = ZeroFunction() + +# Compute operator Norm +normK1 = operator1.norm() +normK2 = operator2.norm() + +#%% +# Primal & dual stepsizes +sigma1 = 1 +tau1 = 1/(sigma1*normK1**2) + +sigma2 = 1 +tau2 = 1/(sigma2*normK2**2) + +# Setup and run the PDHG algorithm +pdhg1 = PDHG(f=f,g=g,operator=operator1, tau=tau1, sigma=sigma1) +pdhg1.max_iteration = 2000 +pdhg1.update_objective_interval = 200 +pdhg1.run(2000) + +# Setup and run the PDHG algorithm +pdhg2 = PDHG(f=f,g=g,operator=operator2, tau=tau2, sigma=sigma2) +pdhg2.max_iteration = 2000 +pdhg2.update_objective_interval = 200 +pdhg2.run(2000) + + +#%% + +tindex = [3, 6, 10] +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) + +plt.subplot(3,3,1) +plt.imshow(phantom_2Dt[tindex[0],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[0])) + +plt.subplot(3,3,2) +plt.imshow(phantom_2Dt[tindex[1],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[1])) + +plt.subplot(3,3,3) +plt.imshow(phantom_2Dt[tindex[2],:,:]) +plt.axis('off') +plt.title('Time {}'.format(tindex[2])) + +plt.subplot(3,3,4) +plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(3,3,5) +plt.imshow(pdhg1.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(3,3,6) +plt.imshow(pdhg1.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') + + +plt.subplot(3,3,7) +plt.imshow(pdhg2.get_output().as_array()[tindex[0],:,:]) +plt.axis('off') +plt.subplot(3,3,8) +plt.imshow(pdhg2.get_output().as_array()[tindex[1],:,:]) +plt.axis('off') +plt.subplot(3,3,9) +plt.imshow(pdhg2.get_output().as_array()[tindex[2],:,:]) +plt.axis('off') + +#%% +im = plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_Gaussian_3D.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_Gaussian_3D.py new file mode 100644 index 0000000..3d91bf9 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_Gaussian_3D.py @@ -0,0 +1,152 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= +""" + +Total Variation (3D) Denoising using PDHG algorithm: + + +Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: 3D Noisy Data with Gaussian Noise + + Method = 0 ( PDHG - split ) : K = [ \nabla, + Identity] + + + Method = 1 (PDHG - explicit ): K = \nabla + +""" + +from ccpi.framework import ImageData, ImageGeometry + +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Identity, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction + +from skimage.util import random_noise + +# Create phantom for TV Gaussian denoising +import timeit +import os +from tomophantom import TomoP3D +import tomophantom + +# Create a phantom from Tomophantom +print ("Building 3D phantom using TomoPhantom software") +tic=timeit.default_timer() +model = 13 # select a model number from the library +N = 64 # Define phantom dimensions using a scalar value (cubic phantom) +path = os.path.dirname(tomophantom.__file__) +path_library3D = os.path.join(path, "Phantom3DLibrary.dat") + +#This will generate a N x N x N phantom (3D) +phantom_tm = TomoP3D.Model(model, N, path_library3D) + +# Create noisy data. Apply Gaussian noise +ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N, voxel_num_z=N) +ag = ig +n1 = random_noise(phantom_tm, mode = 'gaussian', mean=0, var = 0.001, seed=10) +noisy_data = ImageData(n1) + +# Show results +sliceSel = int(0.5*N) +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.title('Axial View') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.title('Coronal View') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.title('Sagittal View') +plt.colorbar() +plt.show() + +# Regularisation Parameter +alpha = 0.05 + +# Setup and run the PDHG algorithm +operator = Gradient(ig) +f = alpha * MixedL21Norm() +g = 0.5 * L2NormSquared(b = noisy_data) + +normK = operator.norm() + +sigma = 1 +tau = 1/(sigma*normK**2) + +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000, verbose = True) + +# Show results +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) +fig.suptitle('TV Reconstruction',fontsize=20) + +plt.subplot(2,3,1) +plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Axial View') + +plt.subplot(2,3,2) +plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.axis('off') +plt.title('Coronal View') + +plt.subplot(2,3,3) +plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.axis('off') +plt.title('Sagittal View') + + +plt.subplot(2,3,4) +plt.imshow(pdhg.get_output().as_array()[sliceSel,:,:],vmin=0, vmax=1) +plt.axis('off') +plt.subplot(2,3,5) +plt.imshow(pdhg.get_output().as_array()[:,sliceSel,:],vmin=0, vmax=1) +plt.axis('off') +plt.subplot(2,3,6) +plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) +plt.axis('off') +im = plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) + + +fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, + wspace=0.02, hspace=0.02) + +cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) +cbar = fig.colorbar(im, cax=cb_ax) + + +plt.show() + -- cgit v1.2.3 From 5835b1468ef0d509bb67d7eef590eb172769a2e9 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 10 Jun 2019 13:41:59 +0100 Subject: imat colorbay demos --- .../Python/demos/CGLS_examples/CGLS_Tikhonov.py | 24 +- .../Python/demos/PDHG_examples/ColorbayDemo.py | 21 +- Wrappers/Python/demos/PDHG_examples/IMATDemo.py | 339 +++++++++++++++++++++ Wrappers/Python/wip/demo_colourbay.py | 2 +- 4 files changed, 365 insertions(+), 21 deletions(-) create mode 100644 Wrappers/Python/demos/PDHG_examples/IMATDemo.py diff --git a/Wrappers/Python/demos/CGLS_examples/CGLS_Tikhonov.py b/Wrappers/Python/demos/CGLS_examples/CGLS_Tikhonov.py index d1cbe20..653e191 100644 --- a/Wrappers/Python/demos/CGLS_examples/CGLS_Tikhonov.py +++ b/Wrappers/Python/demos/CGLS_examples/CGLS_Tikhonov.py @@ -82,18 +82,18 @@ plt.colorbar() plt.show() # Setup and run the CGLS algorithm -alpha = 50 -Grad = Gradient(ig) - -# Form Tikhonov as a Block CGLS structure -op_CGLS = BlockOperator( Aop, alpha * Grad, shape=(2,1)) -block_data = BlockDataContainer(noisy_data, Grad.range_geometry().allocate()) - -x_init = ig.allocate() -cgls = CGLS(x_init=x_init, operator=op_CGLS, data=block_data) -cgls.max_iteration = 1000 -cgls.update_objective_interval = 200 -cgls.run(1000,verbose=False) +#alpha = 50 +#Grad = Gradient(ig) +# +## Form Tikhonov as a Block CGLS structure +#op_CGLS = BlockOperator( Aop, alpha * Grad, shape=(2,1)) +#block_data = BlockDataContainer(noisy_data, Grad.range_geometry().allocate()) +# +#x_init = ig.allocate() +#cgls = CGLS(x_init=x_init, operator=op_CGLS, data=block_data) +#cgls.max_iteration = 1000 +#cgls.update_objective_interval = 200 +#cgls.run(1000,verbose=False) #%% # Show results diff --git a/Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py b/Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py index a735323..e69060f 100644 --- a/Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py +++ b/Wrappers/Python/demos/PDHG_examples/ColorbayDemo.py @@ -57,9 +57,8 @@ elif phantom == 'powder': arrays[k] = numpy.array(v) XX = arrays['S'] X = numpy.transpose(XX,(0,2,1,3)) - X = X[0:20] + X = X[100:120] - #%% Setup Geometry of Colorbay @@ -125,11 +124,16 @@ plt.show() #%% CGLS +def callback(iteration, objective, x): + plt.imshow(x.as_array()[5]) + plt.colorbar() + plt.show() + x_init = ig2d.allocate() cgls1 = CGLS(x_init=x_init, operator=Aall, data=data2d) cgls1.max_iteration = 100 cgls1.update_objective_interval = 1 -cgls1.run(5,verbose=True) +cgls1.run(5,verbose=True, callback = callback) plt.imshow(cgls1.get_output().subset(channel=5).array) plt.title('CGLS') @@ -148,7 +152,7 @@ cgls2 = CGLS(x_init=x_init, operator=op_CGLS, data=block_data) cgls2.max_iteration = 100 cgls2.update_objective_interval = 1 -cgls2.run(10,verbose=True) +cgls2.run(10,verbose=True, callback=callback) plt.imshow(cgls2.get_output().subset(channel=5).array) plt.title('Tikhonov') @@ -174,8 +178,9 @@ f = BlockFunction(f1, f2) g = ZeroFunction() # Compute operator Norm -normK = 8.70320267279591 # Run one time no need to compute again takes time - +#normK = 8.70320267279591 # For powder Run one time no need to compute again takes time +normK = 14.60320657253632 # for carbon + # Primal & dual stepsizes sigma = 1 tau = 1/(sigma*normK**2) @@ -184,11 +189,11 @@ tau = 1/(sigma*normK**2) pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) pdhg.max_iteration = 2000 pdhg.update_objective_interval = 100 -pdhg.run(1000, verbose =True) +pdhg.run(1000, verbose =True, callback=callback) #%% Show sinograms -channel_ind = [10,15,15] +channel_ind = [10,15,19] plt.figure(figsize=(15,15)) diff --git a/Wrappers/Python/demos/PDHG_examples/IMATDemo.py b/Wrappers/Python/demos/PDHG_examples/IMATDemo.py new file mode 100644 index 0000000..2051860 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/IMATDemo.py @@ -0,0 +1,339 @@ + + +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Mon Mar 25 12:50:27 2019 + +@author: vaggelis +""" + +from ccpi.framework import ImageData, ImageGeometry, BlockDataContainer, AcquisitionGeometry, AcquisitionData +from astropy.io import fits +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import CGLS, PDHG +from ccpi.optimisation.functions import MixedL21Norm, L2NormSquared, BlockFunction, ZeroFunction, KullbackLeibler, IndicatorBox +from ccpi.optimisation.operators import Gradient, BlockOperator + +from ccpi.astra.operators import AstraProjectorMC, AstraProjectorSimple + +import pickle + + +# load file + +#filename_sino = '/media/newhd/shared/DataProcessed/IMAT_beamtime_Feb_2019/preprocessed_test_flat/sino/rebin_slice_350/sino_log_rebin_282.fits' +#filename_sino = '/media/newhd/shared/DataProcessed/IMAT_beamtime_Feb_2019/preprocessed_test_flat/sino/rebin_slice_350/sino_log_rebin_564.fits' +#filename_sino = '/media/newhd/shared/DataProcessed/IMAT_beamtime_Feb_2019/preprocessed_test_flat/sino/rebin_slice_350/sino_log_rebin_141.fits' +filename_sino = '/media/newhd/shared/DataProcessed/IMAT_beamtime_Feb_2019/preprocessed_test_flat/sino/rebin_slice_350/sino_log_rebin_80_channels.fits' + +sino_handler = fits.open(filename_sino) +sino = numpy.array(sino_handler[0].data, dtype=float) + +# change axis order: channels, angles, detectors +sino_new = numpy.rollaxis(sino, 2) +sino_handler.close() + + +sino_shape = sino_new.shape + +num_channels = sino_shape[0] # channelss +num_pixels_h = sino_shape[2] # detectors +num_pixels_v = sino_shape[2] # detectors +num_angles = sino_shape[1] # angles + + +ig = ImageGeometry(voxel_num_x = num_pixels_h, voxel_num_y = num_pixels_v, channels = num_channels) + +with open("/media/newhd/vaggelis/CCPi/IMAT_reconstruction/CCPi-Framework/Wrappers/Python/ccpi/optimisation/IMAT_data/golden_angles_new.txt") as f: + angles_string = [line.rstrip() for line in f] + angles = numpy.array(angles_string).astype(float) + + +ag = AcquisitionGeometry('parallel', '2D', angles * numpy.pi / 180, pixel_num_h = num_pixels_h, channels = num_channels) +op_MC = AstraProjectorMC(ig, ag, 'gpu') + +sino_aqdata = AcquisitionData(sino_new, ag) +result_bp = op_MC.adjoint(sino_aqdata) + +#%% + +channel = [40, 60] +for j in range(2): + z4 = sino_aqdata.as_array()[channel[j]] + plt.figure(figsize=(10,6)) + plt.imshow(z4, cmap='viridis') + plt.axis('off') + plt.savefig('Sino_141/Sinogram_ch_{}_.png'.format(channel[j]), bbox_inches='tight', transparent=True) + plt.show() + +#%% + +def callback(iteration, objective, x): + plt.imshow(x.as_array()[40]) + plt.colorbar() + plt.show() + +#%% +# CGLS + +x_init = ig.allocate() +cgls1 = CGLS(x_init=x_init, operator=op_MC, data=sino_aqdata) +cgls1.max_iteration = 100 +cgls1.update_objective_interval = 2 +cgls1.run(20,verbose=True, callback=callback) + +plt.imshow(cgls1.get_output().subset(channel=20).array) +plt.title('CGLS') +plt.colorbar() +plt.show() + +#%% +with open('Sino_141/CGLS/CGLS_{}_iter.pkl'.format(20), 'wb') as f: + z = cgls1.get_output() + pickle.dump(z, f) + +#%% +#% Tikhonov Space + +x_init = ig.allocate() +alpha = [1,3,5,10,20,50] + +for a in alpha: + + Grad = Gradient(ig, correlation = Gradient.CORRELATION_SPACE) + operator = BlockOperator(op_MC, a * Grad, shape=(2,1)) + blockData = BlockDataContainer(sino_aqdata, \ + Grad.range_geometry().allocate()) + cgls2 = CGLS() + cgls2.max_iteration = 500 + cgls2.set_up(x_init, operator, blockData) + cgls2.update_objective_interval = 50 + cgls2.run(100,verbose=True) + + with open('Sino_141/CGLS_Space/CGLS_a_{}.pkl'.format(a), 'wb') as f: + z = cgls2.get_output() + pickle.dump(z, f) + +#% Tikhonov SpaceChannels + +for a1 in alpha: + + Grad1 = Gradient(ig, correlation = Gradient.CORRELATION_SPACECHANNEL) + operator1 = BlockOperator(op_MC, a1 * Grad1, shape=(2,1)) + blockData1 = BlockDataContainer(sino_aqdata, \ + Grad1.range_geometry().allocate()) + cgls3 = CGLS() + cgls3.max_iteration = 500 + cgls3.set_up(x_init, operator1, blockData1) + cgls3.update_objective_interval = 10 + cgls3.run(100, verbose=True) + + with open('Sino_141/CGLS_SpaceChannels/CGLS_a_{}.pkl'.format(a1), 'wb') as f1: + z1 = cgls3.get_output() + pickle.dump(z1, f1) + + + +#%% +# +ig_tmp = ImageGeometry(voxel_num_x = num_pixels_h, voxel_num_y = num_pixels_v) +ag_tmp = AcquisitionGeometry('parallel', '2D', angles * numpy.pi / 180, pixel_num_h = num_pixels_h) +op_tmp = AstraProjectorSimple(ig_tmp, ag_tmp, 'gpu') +normK1 = op_tmp.norm() + +alpha_TV = [2, 5, 10] # for powder + +# Create operators +op1 = Gradient(ig, correlation=Gradient.CORRELATION_SPACECHANNEL) +op2 = op_MC + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + + +for alpha in alpha_TV: +# Create functions + f1 = alpha * MixedL21Norm() + + f2 = KullbackLeibler(sino_aqdata) + f = BlockFunction(f1, f2) + g = IndicatorBox(lower=0) + + # Compute operator Norm + normK = numpy.sqrt(8 + normK1**2) + + # Primal & dual stepsizes + sigma = 1 + tau = 1/(sigma*normK**2) + + # Setup and run the PDHG algorithm + pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) + pdhg.max_iteration = 5000 + pdhg.update_objective_interval = 500 +# pdhg.run(2000, verbose=True, callback=callback) + pdhg.run(5000, verbose=True, callback=callback) +# + with open('Sino_141/TV_SpaceChannels/TV_a = {}.pkl'.format(alpha), 'wb') as f3: + z3 = pdhg.get_output() + pickle.dump(z3, f3) +# +# +# +# +##%% +# +#ig_tmp = ImageGeometry(voxel_num_x = num_pixels_h, voxel_num_y = num_pixels_v) +#ag_tmp = AcquisitionGeometry('parallel', '2D', angles * numpy.pi / 180, pixel_num_h = num_pixels_h) +#op_tmp = AstraProjectorSimple(ig_tmp, ag_tmp, 'gpu') +#normK1 = op_tmp.norm() +# +#alpha_TV = 10 # for powder +# +## Create operators +#op1 = Gradient(ig, correlation=Gradient.CORRELATION_SPACECHANNEL) +#op2 = op_MC +# +## Create BlockOperator +#operator = BlockOperator(op1, op2, shape=(2,1) ) +# +# +## Create functions +#f1 = alpha_TV * MixedL21Norm() +#f2 = 0.5 * L2NormSquared(b=sino_aqdata) +#f = BlockFunction(f1, f2) +#g = ZeroFunction() +# +## Compute operator Norm +##normK = 8.70320267279591 # For powder Run one time no need to compute again takes time +#normK = numpy.sqrt(8 + normK1**2) # for carbon +# +## Primal & dual stepsizes +#sigma = 0.1 +#tau = 1/(sigma*normK**2) +# +#def callback(iteration, objective, x): +# plt.imshow(x.as_array()[100]) +# plt.colorbar() +# plt.show() +# +## Setup and run the PDHG algorithm +#pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +#pdhg.max_iteration = 2000 +#pdhg.update_objective_interval = 100 +#pdhg.run(2000, verbose=True) +# +# +# +# +# +# + + + + + + + + + + +#%% + +#with open('/media/newhd/vaggelis/CCPi/IMAT_reconstruction/CCPi-Framework/Wrappers/Python/ccpi/optimisation/CGLS_Tikhonov/CGLS_Space/CGLS_Space_a = 50.pkl', 'wb') as f: +# z = cgls2.get_output() +# pickle.dump(z, f) +# + + #%% +with open('Sino_141/CGLS_Space/CGLS_Space_a_20.pkl', 'rb') as f1: + x = pickle.load(f1) + +with open('Sino_141/CGLS_SpaceChannels/CGLS_SpaceChannels_a_20.pkl', 'rb') as f1: + x1 = pickle.load(f1) + + + +# +plt.imshow(x.as_array()[40]*mask) +plt.colorbar() +plt.show() + +plt.imshow(x1.as_array()[40]*mask) +plt.colorbar() +plt.show() + +plt.plot(x.as_array()[40,100,:]) +plt.plot(x1.as_array()[40,100,:]) +plt.show() + +#%% + +# Show results + +def circ_mask(h, w, center=None, radius=None): + + if center is None: # use the middle of the image + center = [int(w/2), int(h/2)] + if radius is None: # use the smallest distance between the center and image walls + radius = min(center[0], center[1], w-center[0], h-center[1]) + + Y, X = numpy.ogrid[:h, :w] + dist_from_center = numpy.sqrt((X - center[0])**2 + (Y-center[1])**2) + + mask = dist_from_center <= radius + return mask + +mask = circ_mask(141, 141, center=None, radius = 55) +plt.imshow(numpy.multiply(x.as_array()[40],mask)) +plt.show() +#%% +#channel = [100, 200, 300] +# +#for i in range(3): +# tmp = cgls1.get_output().as_array()[channel[i]] +# +# z = tmp * mask +# plt.figure(figsize=(10,6)) +# plt.imshow(z, vmin=0, cmap='viridis') +# plt.axis('off') +## plt.clim(0, 0.02) +## plt.colorbar() +## del z +# plt.savefig('CGLS_282/CGLS_Chan_{}.png'.format(channel[i]), bbox_inches='tight', transparent=True) +# plt.show() +# +# +##%% Line Profiles +# +#n1, n2, n3 = cgs.get_output().as_array().shape +#mask = circ_mask(564, 564, center=None, radius = 220) +#material = ['Cu', 'Fe', 'Ni'] +#ycoords = [200, 300, 380] +# +#for i in range(3): +# z = cgs.get_output().as_array()[channel[i]] * mask +# +# for k1 in range(len(ycoords)): +# plt.plot(numpy.arange(0,n2), z[ycoords[k1],:]) +# plt.title('Channel {}: {}'.format(channel[i], material[k1])) +# plt.savefig('CGLS/line_profile_chan_{}_material_{}.png'.\ +# format(channel[i], material[k1]), bbox_inches='tight') +# plt.show() +# +# +# +# +# +##%% +# +#%% + + + +#%% + +#plt.imshow(pdhg.get_output().subset(channel=100).as_array()) +#plt.show() diff --git a/Wrappers/Python/wip/demo_colourbay.py b/Wrappers/Python/wip/demo_colourbay.py index 5dbf2e1..0536b07 100644 --- a/Wrappers/Python/wip/demo_colourbay.py +++ b/Wrappers/Python/wip/demo_colourbay.py @@ -18,7 +18,7 @@ from ccpi.optimisation.funcs import Norm2sq, Norm1 # Permute (numpy.transpose) puts into our default ordering which is # (channel, angle, vertical, horizontal). -pathname = '/media/jakob/050d8d45-fab3-4285-935f-260e6c5f162c1/Data/ColourBay/spectral_data_sets/CarbonPd/' +pathname = '/media/newhd/shared/Data/ColourBay/spectral_data_sets/CarbonPd/' filename = 'carbonPd_full_sinogram_stripes_removed.mat' X = loadmat(pathname + filename) -- cgit v1.2.3 From 34d7a6a2d96c35b4f4978b11a4fe8673dc47769e Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Mon, 10 Jun 2019 13:52:43 +0100 Subject: fix tomophantom demo --- .../GatherAll/PDHG_TV_Denoising_Gaussian_3D.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_Gaussian_3D.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_Gaussian_3D.py index 3d91bf9..15709cd 100644 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_Gaussian_3D.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_Gaussian_3D.py @@ -20,7 +20,7 @@ #========================================================================= """ -Total Variation (3D) Denoising using PDHG algorithm: +Total Variation (3D) Denoising using PDHG algorithm and Tomophantom: Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} @@ -39,19 +39,14 @@ Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2} """ -from ccpi.framework import ImageData, ImageGeometry - +from ccpi.framework import ImageData, ImageGeometry import matplotlib.pyplot as plt - from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction +from ccpi.optimisation.operators import Gradient +from ccpi.optimisation.functions import L2NormSquared, MixedL21Norm from skimage.util import random_noise -# Create phantom for TV Gaussian denoising import timeit import os from tomophantom import TomoP3D @@ -105,9 +100,9 @@ sigma = 1 tau = 1/(sigma*normK**2) pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 +pdhg.max_iteration = 1000 pdhg.update_objective_interval = 200 -pdhg.run(2000, verbose = True) +pdhg.run(1000, verbose = True) # Show results fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) -- cgit v1.2.3 From 3f4a7876a29f9ffd0ee3a87c1fe79700834f8fad Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 11 Jun 2019 11:33:52 +0100 Subject: add 2d time demo --- .../GatherAll/PDHG_TV_Denoising_2D_time.py | 79 ++++++++++++++++++---- 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_2D_time.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_2D_time.py index 14608db..febe76d 100644 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_2D_time.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising_2D_time.py @@ -18,8 +18,24 @@ # limitations under the License. # #========================================================================= +""" -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData +Total Variation (Dynamic) Denoising using PDHG algorithm and Tomophantom: + + +Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} + + \alpha: Regularization parameter + + \nabla: Gradient operator + + g: 2D Dynamic noisy data with Gaussian Noise + + K = \nabla + +""" + +from ccpi.framework import ImageData, ImageGeometry import numpy as np import numpy @@ -28,7 +44,7 @@ import matplotlib.pyplot as plt from ccpi.optimisation.algorithms import PDHG from ccpi.optimisation.operators import BlockOperator, Gradient, Identity -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ MixedL21Norm, BlockFunction from ccpi.astra.ops import AstraProjectorMC @@ -41,11 +57,9 @@ from tomophantom import TomoP2D model = 102 # note that the selected model is temporal (2D + time) N = 128 # set dimension of the phantom -# one can specify an exact path to the parameters file -# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' + path = os.path.dirname(tomophantom.__file__) path_library2D = os.path.join(path, "Phantom2DLibrary.dat") -#This will generate a N_size x N_size x Time frames phantom (2D + time) phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) plt.close('all') @@ -58,16 +72,17 @@ for sl in range(0,np.shape(phantom_2Dt)[0]): # plt.pause(.1) # plt.draw - +# Setup geometries ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) data = ImageData(phantom_2Dt, geometry=ig) ag = ig -# Create Noisy data. Add Gaussian noise +# Create noisy data. Apply Gaussian noise np.random.seed(10) noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.25, size=ig.shape) ) -tindex = [3, 6, 10] +# time-frames index +tindex = [8, 16, 24] fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) plt.subplot(1,3,1) @@ -88,15 +103,12 @@ fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, plt.show() -#%% # Regularisation Parameter alpha = 0.3 # Create operators -#op1 = Gradient(ig) op1 = Gradient(ig, correlation='Space') op2 = Gradient(ig, correlation='SpaceChannels') - op3 = Identity(ig, ag) # Create BlockOperator @@ -138,7 +150,7 @@ pdhg2.run(2000) #%% -tindex = [3, 6, 10] +tindex = [8, 16, 24] fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) plt.subplot(3,3,1) @@ -177,7 +189,7 @@ plt.subplot(3,3,9) plt.imshow(pdhg2.get_output().as_array()[tindex[2],:,:]) plt.axis('off') -#%% + im = plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) @@ -186,7 +198,46 @@ fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) cbar = fig.colorbar(im, cax=cb_ax) +plt.show() + +#%% +import matplotlib.animation as animation +fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 30)) +ims1 = [] +ims2 = [] +ims3 = [] +for sl in range(0,np.shape(phantom_2Dt)[0]): + + plt.subplot(1,3,1) + im1 = plt.imshow(phantom_2Dt[sl,:,:], animated=True) + + plt.subplot(1,3,2) + im2 = plt.imshow(pdhg1.get_output().as_array()[sl,:,:]) + + plt.subplot(1,3,3) + im3 = plt.imshow(pdhg2.get_output().as_array()[sl,:,:]) + + ims1.append([im1]) + ims2.append([im2]) + ims3.append([im3]) + + +ani1 = animation.ArtistAnimation(fig, ims1, interval=500, + repeat_delay=10) + +ani2 = animation.ArtistAnimation(fig, ims2, interval=500, + repeat_delay=10) + +ani3 = animation.ArtistAnimation(fig, ims3, interval=500, + repeat_delay=10) +plt.show() +# plt.pause(0.25) +# plt.show() + + + + + -plt.show() -- cgit v1.2.3 From 246343e2c5a4a7ed4b521fb824b4d0949d77de2a Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 11 Jun 2019 12:31:55 +0100 Subject: tv tomo 2D --- .../demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D.py | 173 +++++++++++++++++++++ .../Python/demos/PDHG_examples/Tomo/phantom.mat | Bin 0 -> 5583 bytes 2 files changed, 173 insertions(+) create mode 100644 Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D.py create mode 100755 Wrappers/Python/demos/PDHG_examples/Tomo/phantom.mat diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D.py new file mode 100644 index 0000000..f179e95 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D.py @@ -0,0 +1,173 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation 2D Tomography Reconstruction using PDHG algorithm: + + +Problem: min_u \alpha * ||\nabla u||_{2,1} + \frac{1}{2}||Au - g||^{2} + min_u, u>0 \alpha * ||\nabla u||_{2,1} + \int A u - g log (Au + \eta) + + \nabla: Gradient operator + A: System Matrix + g: Noisy sinogram + \eta: Background noise + + \alpha: Regularization parameter + +""" + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction, KullbackLeibler, IndicatorBox + +from ccpi.astra.ops import AstraProjectorSimple +from ccpi.framework import TestData +from PIL import Image +import os, sys +if int(numpy.version.version.split('.')[1]) > 12: + from skimage.util import random_noise +else: + from demoutil import random_noise + +import scipy.io + +# user supplied input +if len(sys.argv) > 1: + which_noise = int(sys.argv[1]) +else: + which_noise = 1 + +# Load 256 shepp-logan +data256 = scipy.io.loadmat('phantom.mat')['phantom256'] +data = ImageData(numpy.array(Image.fromarray(data256).resize((256,256)))) +N, M = data.shape +ig = ImageGeometry(voxel_num_x=N, voxel_num_y=M) + +# Add it to testdata or use tomophantom +#loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) +#data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(50, 50)) +#ig = data.geometry + +# Create acquisition data and geometry +detectors = N +angles = np.linspace(0, np.pi, 180) +ag = AcquisitionGeometry('parallel','2D',angles, detectors) + +# Select device +device = '0' +#device = input('Available device: GPU==1 / CPU==0 ') +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + +Aop = AstraProjectorSimple(ig, ag, dev) +sin = Aop.direct(data) + +# Create noisy data. Apply Gaussian noise +noises = ['gaussian', 'poisson'] +noise = noises[which_noise] + +if noise == 'poisson': + scale = 5 + eta = 0 + noisy_data = AcquisitionData(np.random.poisson( scale * (eta + sin.as_array()))/scale, ag) +elif noise == 'gaussian': + n1 = np.random.normal(0, 1, size = ag.shape) + noisy_data = AcquisitionData(n1 + sin.as_array(), ag) +else: + raise ValueError('Unsupported Noise ', noise) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(1,2,2) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(1,2,1) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Compute operator Norm +normK = operator.norm() + +# Create functions +if noise == 'poisson': + alpha = 3 + f2 = KullbackLeibler(noisy_data) + g = IndicatorBox(lower=0) + sigma = 1 + tau = 1/(sigma*normK**2) + +elif noise == 'gaussian': + alpha = 20 + f2 = 0.5 * L2NormSquared(b=noisy_data) + g = ZeroFunction() + sigma = 10 + tau = 1/(sigma*normK**2) + +f1 = alpha * MixedL21Norm() +f = BlockFunction(f1, f2) + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/phantom.mat b/Wrappers/Python/demos/PDHG_examples/Tomo/phantom.mat new file mode 100755 index 0000000..c465bbe Binary files /dev/null and b/Wrappers/Python/demos/PDHG_examples/Tomo/phantom.mat differ -- cgit v1.2.3 From 7c0faf8cc10e59a1904eb86154f58c33c77ad8ea Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Tue, 11 Jun 2019 12:35:04 +0100 Subject: tomo 2d TV --- .../PDHG_examples/GatherAll/PDHG_TV_Tomo2D.py | 173 +++++++++++++++++++++ .../demos/PDHG_examples/GatherAll/phantom.mat | Bin 0 -> 5583 bytes 2 files changed, 173 insertions(+) create mode 100644 Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Tomo2D.py create mode 100755 Wrappers/Python/demos/PDHG_examples/GatherAll/phantom.mat diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Tomo2D.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Tomo2D.py new file mode 100644 index 0000000..f179e95 --- /dev/null +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Tomo2D.py @@ -0,0 +1,173 @@ +#======================================================================== +# Copyright 2019 Science Technology Facilities Council +# Copyright 2019 University of Manchester +# +# This work is part of the Core Imaging Library developed by Science Technology +# Facilities Council and University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#========================================================================= + +""" + +Total Variation 2D Tomography Reconstruction using PDHG algorithm: + + +Problem: min_u \alpha * ||\nabla u||_{2,1} + \frac{1}{2}||Au - g||^{2} + min_u, u>0 \alpha * ||\nabla u||_{2,1} + \int A u - g log (Au + \eta) + + \nabla: Gradient operator + A: System Matrix + g: Noisy sinogram + \eta: Background noise + + \alpha: Regularization parameter + +""" + +from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData + +import numpy as np +import numpy +import matplotlib.pyplot as plt + +from ccpi.optimisation.algorithms import PDHG + +from ccpi.optimisation.operators import BlockOperator, Gradient +from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ + MixedL21Norm, BlockFunction, KullbackLeibler, IndicatorBox + +from ccpi.astra.ops import AstraProjectorSimple +from ccpi.framework import TestData +from PIL import Image +import os, sys +if int(numpy.version.version.split('.')[1]) > 12: + from skimage.util import random_noise +else: + from demoutil import random_noise + +import scipy.io + +# user supplied input +if len(sys.argv) > 1: + which_noise = int(sys.argv[1]) +else: + which_noise = 1 + +# Load 256 shepp-logan +data256 = scipy.io.loadmat('phantom.mat')['phantom256'] +data = ImageData(numpy.array(Image.fromarray(data256).resize((256,256)))) +N, M = data.shape +ig = ImageGeometry(voxel_num_x=N, voxel_num_y=M) + +# Add it to testdata or use tomophantom +#loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) +#data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(50, 50)) +#ig = data.geometry + +# Create acquisition data and geometry +detectors = N +angles = np.linspace(0, np.pi, 180) +ag = AcquisitionGeometry('parallel','2D',angles, detectors) + +# Select device +device = '0' +#device = input('Available device: GPU==1 / CPU==0 ') +if device=='1': + dev = 'gpu' +else: + dev = 'cpu' + +Aop = AstraProjectorSimple(ig, ag, dev) +sin = Aop.direct(data) + +# Create noisy data. Apply Gaussian noise +noises = ['gaussian', 'poisson'] +noise = noises[which_noise] + +if noise == 'poisson': + scale = 5 + eta = 0 + noisy_data = AcquisitionData(np.random.poisson( scale * (eta + sin.as_array()))/scale, ag) +elif noise == 'gaussian': + n1 = np.random.normal(0, 1, size = ag.shape) + noisy_data = AcquisitionData(n1 + sin.as_array(), ag) +else: + raise ValueError('Unsupported Noise ', noise) + +# Show Ground Truth and Noisy Data +plt.figure(figsize=(10,10)) +plt.subplot(1,2,2) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(1,2,1) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.show() + +# Create operators +op1 = Gradient(ig) +op2 = Aop + +# Create BlockOperator +operator = BlockOperator(op1, op2, shape=(2,1) ) + +# Compute operator Norm +normK = operator.norm() + +# Create functions +if noise == 'poisson': + alpha = 3 + f2 = KullbackLeibler(noisy_data) + g = IndicatorBox(lower=0) + sigma = 1 + tau = 1/(sigma*normK**2) + +elif noise == 'gaussian': + alpha = 20 + f2 = 0.5 * L2NormSquared(b=noisy_data) + g = ZeroFunction() + sigma = 10 + tau = 1/(sigma*normK**2) + +f1 = alpha * MixedL21Norm() +f = BlockFunction(f1, f2) + +# Setup and run the PDHG algorithm +pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) +pdhg.max_iteration = 2000 +pdhg.update_objective_interval = 200 +pdhg.run(2000) + +plt.figure(figsize=(15,15)) +plt.subplot(3,1,1) +plt.imshow(data.as_array()) +plt.title('Ground Truth') +plt.colorbar() +plt.subplot(3,1,2) +plt.imshow(noisy_data.as_array()) +plt.title('Noisy Data') +plt.colorbar() +plt.subplot(3,1,3) +plt.imshow(pdhg.get_output().as_array()) +plt.title('TV Reconstruction') +plt.colorbar() +plt.show() +plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') +plt.legend() +plt.title('Middle Line Profiles') +plt.show() diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/phantom.mat b/Wrappers/Python/demos/PDHG_examples/GatherAll/phantom.mat new file mode 100755 index 0000000..c465bbe Binary files /dev/null and b/Wrappers/Python/demos/PDHG_examples/GatherAll/phantom.mat differ -- cgit v1.2.3 From 147b807c8883025b85baecfe5e10e007cb1babc9 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 12 Jun 2019 09:54:17 +0100 Subject: tgv tomo --- .../demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py | 82 ++++++++++++++-------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py index 422deb9..b2af753 100644 --- a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py +++ b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py @@ -23,18 +23,20 @@ Total Generalised Variation (TGV) Tomography 2D using PDHG algorithm: -Problem: min_{x>0} \alpha * ||\nabla x - w||_{2,1} + - \beta * || E w ||_{2,1} + - int A x - g log(Ax + \eta) +Problem: min_{x>0} \alpha * ||\nabla x - w||_{2,1} + \beta * || E w ||_{2,1} + + \frac{1}{2}||Au - g||^{2} + + min_{u>0} \alpha * ||\nabla u - w||_{2,1} + \beta * || E w ||_{2,1} + + int A u - g log(Au + \eta) \alpha: Regularization parameter \beta: Regularization parameter \nabla: Gradient operator E: Symmetrized Gradient operator - A: Projection Matrix + A: System Matrix - g: Noisy Data with Poisson Noise + g: Noisy Sinogram K = [ \nabla, - Identity ZeroOperator, E @@ -58,9 +60,12 @@ from ccpi.optimisation.functions import IndicatorBox, KullbackLeibler, ZeroFunct from ccpi.astra.ops import AstraProjectorSimple from ccpi.framework import TestData import os, sys -from skimage.util import random_noise +if int(numpy.version.version.split('.')[1]) > 12: + from skimage.util import random_noise +else: + from demoutil import random_noise -loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) +#loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) # Load Data #N = 50 @@ -102,28 +107,31 @@ else: Aop = AstraProjectorSimple(ig, ag, 'cpu') sin = Aop.direct(data) -# Create noisy data. Apply Poisson noise -scale = 0.5 -eta = 0 -n1 = scale * np.random.poisson(eta + sin.as_array()/scale) - -noisy_data = AcquisitionData(n1, ag) +# Create noisy data. +noises = ['gaussian', 'poisson'] +noise = noises[which_noise] + +if noise == 'poisson': + scale = 5 + eta = 0 + noisy_data = AcquisitionData(np.random.poisson( scale * (eta + sin.as_array()))/scale, ag) +elif noise == 'gaussian': + n1 = np.random.normal(0, 1, size = ag.shape) + noisy_data = AcquisitionData(n1 + sin.as_array(), ag) +else: + raise ValueError('Unsupported Noise ', noise) # Show Ground Truth and Noisy Data plt.figure(figsize=(10,10)) -plt.subplot(2,1,1) +plt.subplot(1,2,2) plt.imshow(data.as_array()) plt.title('Ground Truth') plt.colorbar() -plt.subplot(2,1,2) +plt.subplot(1,2,1) plt.imshow(noisy_data.as_array()) plt.title('Noisy Data') plt.colorbar() plt.show() -#%% -# Regularisation Parameters -alpha = 1 -beta = 5 # Create Operators op11 = Gradient(ig) @@ -136,28 +144,41 @@ op31 = Aop op32 = ZeroOperator(op22.domain_geometry(), ag) operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) + +# Create functions +if noise == 'poisson': + alpha = 1 + beta = 5 + f3 = KullbackLeibler(noisy_data) + g = BlockFunction(IndicatorBox(lower=0), ZeroFunction()) + + # Primal & dual stepsizes + sigma = 1 + tau = 1/(sigma*normK**2) + +elif noise == 'gaussian': + alpha = 20 + f3 = 0.5 * L2NormSquared(b=noisy_data) + g = BlockFunction(ZeroFunction(), ZeroFunction()) + + # Primal & dual stepsizes + sigma = 10 + tau = 1/(sigma*normK**2) f1 = alpha * MixedL21Norm() -f2 = beta * MixedL21Norm() -f3 = KullbackLeibler(noisy_data) +f2 = beta * MixedL21Norm() f = BlockFunction(f1, f2, f3) - -g = BlockFunction(IndicatorBox(lower=0), ZeroFunction()) # Compute operator Norm normK = operator.norm() -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - # Setup and run the PDHG algorithm pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) pdhg.max_iteration = 3000 pdhg.update_objective_interval = 500 pdhg.run(3000) +#%% plt.figure(figsize=(15,15)) plt.subplot(3,1,1) plt.imshow(data.as_array()) @@ -172,9 +193,8 @@ plt.imshow(pdhg.get_output()[0].as_array()) plt.title('TGV Reconstruction') plt.colorbar() plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TGV reconstruction') +plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), data.as_array()[int(N/2),:], label = 'GTruth') +plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TGV reconstruction') plt.legend() plt.title('Middle Line Profiles') plt.show() -- cgit v1.2.3 From ec479c78c8956a1645604a780c2eb8c0d601165b Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 12 Jun 2019 10:41:30 +0100 Subject: delete old demos --- .../MultiChannel/PDHG_TV_Denoising_2D_time.py | 192 --------------------- .../MultiChannel/PDHG_TV_Denoising_Gaussian_3D.py | 181 ------------------- 2 files changed, 373 deletions(-) delete mode 100644 Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_2D_time.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_Gaussian_3D.py diff --git a/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_2D_time.py b/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_2D_time.py deleted file mode 100644 index 14608db..0000000 --- a/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_2D_time.py +++ /dev/null @@ -1,192 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient, Identity -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from ccpi.astra.ops import AstraProjectorMC - -import os -import tomophantom -from tomophantom import TomoP2D - -# Create phantom for TV 2D dynamic tomography - -model = 102 # note that the selected model is temporal (2D + time) -N = 128 # set dimension of the phantom -# one can specify an exact path to the parameters file -# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' -path = os.path.dirname(tomophantom.__file__) -path_library2D = os.path.join(path, "Phantom2DLibrary.dat") -#This will generate a N_size x N_size x Time frames phantom (2D + time) -phantom_2Dt = TomoP2D.ModelTemporal(model, N, path_library2D) - -plt.close('all') -plt.figure(1) -plt.rcParams.update({'font.size': 21}) -plt.title('{}''{}'.format('2D+t phantom using model no.',model)) -for sl in range(0,np.shape(phantom_2Dt)[0]): - im = phantom_2Dt[sl,:,:] - plt.imshow(im, vmin=0, vmax=1) -# plt.pause(.1) -# plt.draw - - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N, channels = np.shape(phantom_2Dt)[0]) -data = ImageData(phantom_2Dt, geometry=ig) -ag = ig - -# Create Noisy data. Add Gaussian noise -np.random.seed(10) -noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.25, size=ig.shape) ) - -tindex = [3, 6, 10] - -fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(10, 10)) -plt.subplot(1,3,1) -plt.imshow(noisy_data.as_array()[tindex[0],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[0])) -plt.subplot(1,3,2) -plt.imshow(noisy_data.as_array()[tindex[1],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[1])) -plt.subplot(1,3,3) -plt.imshow(noisy_data.as_array()[tindex[2],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[2])) - -fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, - wspace=0.02, hspace=0.02) - -plt.show() - -#%% -# Regularisation Parameter -alpha = 0.3 - -# Create operators -#op1 = Gradient(ig) -op1 = Gradient(ig, correlation='Space') -op2 = Gradient(ig, correlation='SpaceChannels') - -op3 = Identity(ig, ag) - -# Create BlockOperator -operator1 = BlockOperator(op1, op3, shape=(2,1) ) -operator2 = BlockOperator(op2, op3, shape=(2,1) ) - -# Create functions - -f1 = alpha * MixedL21Norm() -f2 = 0.5 * L2NormSquared(b = noisy_data) -f = BlockFunction(f1, f2) - -g = ZeroFunction() - -# Compute operator Norm -normK1 = operator1.norm() -normK2 = operator2.norm() - -#%% -# Primal & dual stepsizes -sigma1 = 1 -tau1 = 1/(sigma1*normK1**2) - -sigma2 = 1 -tau2 = 1/(sigma2*normK2**2) - -# Setup and run the PDHG algorithm -pdhg1 = PDHG(f=f,g=g,operator=operator1, tau=tau1, sigma=sigma1) -pdhg1.max_iteration = 2000 -pdhg1.update_objective_interval = 200 -pdhg1.run(2000) - -# Setup and run the PDHG algorithm -pdhg2 = PDHG(f=f,g=g,operator=operator2, tau=tau2, sigma=sigma2) -pdhg2.max_iteration = 2000 -pdhg2.update_objective_interval = 200 -pdhg2.run(2000) - - -#%% - -tindex = [3, 6, 10] -fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) - -plt.subplot(3,3,1) -plt.imshow(phantom_2Dt[tindex[0],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[0])) - -plt.subplot(3,3,2) -plt.imshow(phantom_2Dt[tindex[1],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[1])) - -plt.subplot(3,3,3) -plt.imshow(phantom_2Dt[tindex[2],:,:]) -plt.axis('off') -plt.title('Time {}'.format(tindex[2])) - -plt.subplot(3,3,4) -plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) -plt.axis('off') -plt.subplot(3,3,5) -plt.imshow(pdhg1.get_output().as_array()[tindex[1],:,:]) -plt.axis('off') -plt.subplot(3,3,6) -plt.imshow(pdhg1.get_output().as_array()[tindex[2],:,:]) -plt.axis('off') - - -plt.subplot(3,3,7) -plt.imshow(pdhg2.get_output().as_array()[tindex[0],:,:]) -plt.axis('off') -plt.subplot(3,3,8) -plt.imshow(pdhg2.get_output().as_array()[tindex[1],:,:]) -plt.axis('off') -plt.subplot(3,3,9) -plt.imshow(pdhg2.get_output().as_array()[tindex[2],:,:]) -plt.axis('off') - -#%% -im = plt.imshow(pdhg1.get_output().as_array()[tindex[0],:,:]) - - -fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, - wspace=0.02, hspace=0.02) - -cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) -cbar = fig.colorbar(im, cax=cb_ax) - - -plt.show() - diff --git a/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_Gaussian_3D.py b/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_Gaussian_3D.py deleted file mode 100644 index 03dc2ef..0000000 --- a/Wrappers/Python/demos/PDHG_examples/MultiChannel/PDHG_TV_Denoising_Gaussian_3D.py +++ /dev/null @@ -1,181 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= -""" - -Total Variation (3D) Denoising using PDHG algorithm: - - -Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Gaussian Noise - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity] - - - Method = 1 (PDHG - explicit ): K = \nabla - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Gaussian denoising -import timeit -import os -from tomophantom import TomoP3D -import tomophantom - -print ("Building 3D phantom using TomoPhantom software") -tic=timeit.default_timer() -model = 13 # select a model number from the library -N = 64 # Define phantom dimensions using a scalar value (cubic phantom) -path = os.path.dirname(tomophantom.__file__) -path_library3D = os.path.join(path, "Phantom3DLibrary.dat") - -#This will generate a N x N x N phantom (3D) -phantom_tm = TomoP3D.Model(model, N, path_library3D) - -#%% - -# Create noisy data. Add Gaussian noise -ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N, voxel_num_z=N) -ag = ig -n1 = random_noise(phantom_tm, mode = 'gaussian', mean=0, var = 0.001, seed=10) -noisy_data = ImageData(n1) - -sliceSel = int(0.5*N) -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) -plt.title('Axial View') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) -plt.title('Coronal View') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) -plt.title('Sagittal View') -plt.colorbar() -plt.show() - -#%% - -# Regularisation Parameter -alpha = 0.05 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * MixedL21Norm() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = 0.5 * L2NormSquared(b = noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000, verbose = True) - - -#%% -fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) -fig.suptitle('TV Reconstruction',fontsize=20) - - -plt.subplot(2,3,1) -plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Axial View') - -plt.subplot(2,3,2) -plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Coronal View') - -plt.subplot(2,3,3) -plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) -plt.axis('off') -plt.title('Sagittal View') - - -plt.subplot(2,3,4) -plt.imshow(pdhg.get_output().as_array()[sliceSel,:,:],vmin=0, vmax=1) -plt.axis('off') -plt.subplot(2,3,5) -plt.imshow(pdhg.get_output().as_array()[:,sliceSel,:],vmin=0, vmax=1) -plt.axis('off') -plt.subplot(2,3,6) -plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) -plt.axis('off') -im = plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) - - -fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, - wspace=0.02, hspace=0.02) - -cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) -cbar = fig.colorbar(im, cax=cb_ax) - - -plt.show() - -- cgit v1.2.3 From 5c3621aeb63e6465f1884be81ebd12d8a39dcdbd Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 12 Jun 2019 10:44:43 +0100 Subject: delete old demos from wip --- Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py | 124 ------------ .../Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py | 225 --------------------- .../wip/Demos/PDHG_TV_Denoising_Gaussian_3D.py | 155 -------------- .../Python/wip/Demos/PDHG_TV_Denoising_Poisson.py | 207 ------------------- .../wip/Demos/PDHG_TV_Denoising_SaltPepper.py | 198 ------------------ .../Python/wip/Demos/PDHG_Tikhonov_Denoising.py | 176 ---------------- Wrappers/Python/wip/Demos/fista_test.py | 127 ------------ 7 files changed, 1212 deletions(-) delete mode 100644 Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py delete mode 100644 Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py delete mode 100644 Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian_3D.py delete mode 100644 Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py delete mode 100644 Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py delete mode 100644 Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py delete mode 100644 Wrappers/Python/wip/Demos/fista_test.py diff --git a/Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py b/Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py deleted file mode 100644 index 49d4db6..0000000 --- a/Wrappers/Python/wip/Demos/PDHG_TGV_Tomo2D.py +++ /dev/null @@ -1,124 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient, Identity, \ - SymmetrizedGradient, ZeroOperator -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ - MixedL21Norm, BlockFunction - -from ccpi.astra.ops import AstraProjectorSimple - -# Create phantom for TV 2D tomography -N = 75 - -data = np.zeros((N,N)) - -x1 = np.linspace(0, int(N/2), N) -x2 = np.linspace(int(N/2), 0., N) -xv, yv = np.meshgrid(x1, x2) - -xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T -data = xv -data = ImageData(data/data.max()) - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -detectors = N -angles = np.linspace(0, np.pi, N, dtype=np.float32) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) -Aop = AstraProjectorSimple(ig, ag, 'gpu') -sin = Aop.direct(data) - -# Create noisy data. Apply Poisson noise -scale = 0.1 -np.random.seed(5) -n1 = scale * np.random.poisson(sin.as_array()/scale) -noisy_data = AcquisitionData(n1, ag) - - -plt.imshow(noisy_data.as_array()) -plt.show() -#%% -# Regularisation Parameters -alpha = 0.7 -beta = 2 - -# Create Operators -op11 = Gradient(ig) -op12 = Identity(op11.range_geometry()) - -op22 = SymmetrizedGradient(op11.domain_geometry()) -op21 = ZeroOperator(ig, op22.range_geometry()) - -op31 = Aop -op32 = ZeroOperator(op22.domain_geometry(), ag) - -operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) - -f1 = alpha * MixedL21Norm() -f2 = beta * MixedL21Norm() -f3 = KullbackLeibler(noisy_data) -f = BlockFunction(f1, f2, f3) -g = ZeroFunction() - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output()[0].as_array()) -plt.title('TGV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TGV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py deleted file mode 100644 index 5df02b1..0000000 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian.py +++ /dev/null @@ -1,225 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - - -""" - -Total Variation Denoising using PDHG algorithm: - - min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) - - -Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Gaussian Noise - - Method = 0: K = [ \nabla, - Identity] - - Method = 1: K = \nabla - - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - - - - -from Data import * - -#%% - - -data = ImageData(plt.imread('camera.png')) - -# -## Create phantom for TV Gaussian denoising -#N = 200 -# -#data = np.zeros((N,N)) -#data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -#data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -#data = ImageData(data) -#ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -#ag = ig -# -# -# -## Replace with http://sipi.usc.edu/database/database.php?volume=misc&image=36#top - - - -# Create noisy data. Add Gaussian noise -np.random.seed(10) -noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.05, size=ig.shape) ) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(15,15)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Regularisation Parameter -alpha = 2 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - f1 = alpha * MixedL21Norm() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = 0.5 * L2NormSquared(b = noisy_data) - -# Compute Operator Norm -normK = operator.norm() - -# Primal & Dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -# Setup and Run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 3000 -pdhg.update_objective_interval = 200 -pdhg.run(3000, verbose=False) - -# Show Results -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() - -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = MOSEK) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'Truth') - - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian_3D.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian_3D.py deleted file mode 100644 index c86ddc9..0000000 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Gaussian_3D.py +++ /dev/null @@ -1,155 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Gaussian denoising -import timeit -import os -from tomophantom import TomoP3D -import tomophantom - -print ("Building 3D phantom using TomoPhantom software") -tic=timeit.default_timer() -model = 13 # select a model number from the library -N = 64 # Define phantom dimensions using a scalar value (cubic phantom) -path = os.path.dirname(tomophantom.__file__) -path_library3D = os.path.join(path, "Phantom3DLibrary.dat") - -#This will generate a N x N x N phantom (3D) -phantom_tm = TomoP3D.Model(model, N, path_library3D) - -# Create noisy data. Add Gaussian noise -ig = ImageGeometry(voxel_num_x=N, voxel_num_y=N, voxel_num_z=N) -ag = ig -n1 = random_noise(phantom_tm, mode = 'gaussian', mean=0, var = 0.001, seed=10) -noisy_data = ImageData(n1) - -sliceSel = int(0.5*N) -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) -plt.title('Axial View') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) -plt.title('Coronal View') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) -plt.title('Sagittal View') -plt.colorbar() -plt.show() - - -# Regularisation Parameter -alpha = 0.05 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * MixedL21Norm() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = 0.5 * L2NormSquared(b = noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000) - -fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(10, 8)) - -plt.subplot(2,3,1) -plt.imshow(noisy_data.as_array()[sliceSel,:,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Axial View') - -plt.subplot(2,3,2) -plt.imshow(noisy_data.as_array()[:,sliceSel,:],vmin=0, vmax=1) -plt.axis('off') -plt.title('Coronal View') - -plt.subplot(2,3,3) -plt.imshow(noisy_data.as_array()[:,:,sliceSel],vmin=0, vmax=1) -plt.axis('off') -plt.title('Sagittal View') - - -plt.subplot(2,3,4) -plt.imshow(pdhg.get_output().as_array()[sliceSel,:,:],vmin=0, vmax=1) -plt.axis('off') -plt.subplot(2,3,5) -plt.imshow(pdhg.get_output().as_array()[:,sliceSel,:],vmin=0, vmax=1) -plt.axis('off') -plt.subplot(2,3,6) -plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) -plt.axis('off') -im = plt.imshow(pdhg.get_output().as_array()[:,:,sliceSel],vmin=0, vmax=1) - - -fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8, - wspace=0.02, hspace=0.02) - -cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8]) -cbar = fig.colorbar(im, cax=cb_ax) - - -plt.show() - diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py deleted file mode 100644 index 70f6b9b..0000000 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_Poisson.py +++ /dev/null @@ -1,207 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 STFC, University of Manchester - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" - -Total Variation Denoising using PDHG algorithm: - - min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + \int x - g * log(x) - - \nabla: Gradient operator - g: Noisy Data with Poisson Noise - \alpha: Regularization parameter - - Method = 0: K = [ \nabla, - Identity] - - Method = 1: K = \nabla - - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Poisson denoising -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Apply Poisson noise -n1 = random_noise(data.as_array(), mode = 'poisson', seed = 10) -noisy_data = ImageData(n1) - -# Regularisation Parameter -alpha = 2 - -method = '1' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * MixedL21Norm() - f2 = KullbackLeibler(noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = KullbackLeibler(noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -opt = {'niter':2000, 'memopt': True} - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 - -def pdgap_objectives(niter, objective, solution): - - - print( "{:04}/{:04} {:<5} {:.4f} {:<5} {:.4f} {:<5} {:.4f}".\ - format(niter, pdhg.max_iteration,'', \ - objective[0],'',\ - objective[1],'',\ - objective[2])) - -pdhg.run(2000, callback = pdgap_objectives) - - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u1 = Variable(ig.shape) - q = Variable() - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) - - fidelity = sum( u1 - multiply(noisy_data.as_array(), log(u1)) ) - constraints = [q>= fidelity, u1>=0] - - solver = ECOS - obj = Minimize( regulariser + q) - prob = Problem(obj, constraints) - result = prob.solve(verbose = True, solver = solver) - - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u1.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u1.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - diff --git a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py b/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py deleted file mode 100644 index f5d4ce4..0000000 --- a/Wrappers/Python/wip/Demos/PDHG_TV_Denoising_SaltPepper.py +++ /dev/null @@ -1,198 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" - -Total Variation Denoising using PDHG algorithm: - - min_{x} max_{y} < K x, y > + g(x) - f^{*}(y) - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{1} + ||x-g||_{1} - - \nabla: Gradient operator - g: Noisy Data with Salt & Pepper Noise - \alpha: Regularization parameter - - Method = 0: K = [ \nabla, - Identity] - - Method = 1: K = \nabla - - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Salt & Pepper denoising -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Apply Salt & Pepper noise -n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) -noisy_data = ImageData(n1) - -# Regularisation Parameter -alpha = 2 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * MixedL21Norm() - f2 = L1Norm(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = L1Norm(b = noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -opt = {'niter':2000, 'memopt': True} - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -##%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = pnorm( u - noisy_data.as_array(),1) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - diff --git a/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py b/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py deleted file mode 100644 index 041d4ee..0000000 --- a/Wrappers/Python/wip/Demos/PDHG_Tikhonov_Denoising.py +++ /dev/null @@ -1,176 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Salt & Pepper denoising -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Apply Salt & Pepper noise -n1 = random_noise(data.as_array(), mode = 'gaussian', mean=0, var = 0.05, seed=10) -noisy_data = ImageData(n1) - -# Regularisation Parameter -alpha = 4 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * L2NormSquared() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * L2NormSquared() - g = 0.5 * L2NormSquared(b = noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -opt = {'niter':2000, 'memopt': True} - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('Tikhonov Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'Tikhonov reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -##%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - - regulariser = alpha * sum_squares(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - diff --git a/Wrappers/Python/wip/Demos/fista_test.py b/Wrappers/Python/wip/Demos/fista_test.py deleted file mode 100644 index dd1f6fa..0000000 --- a/Wrappers/Python/wip/Demos/fista_test.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Evangelos Papoutsellis and Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import FISTA, PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient, Identity -from ccpi.optimisation.functions import L2NormSquared, L1Norm, \ - MixedL21Norm, FunctionOperatorComposition, BlockFunction, ZeroFunction - -from skimage.util import random_noise - -# Create phantom for TV Gaussian denoising -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Add Gaussian noise -n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) -noisy_data = ImageData(n1) - -# Regularisation Parameter -alpha = 5 - -operator = Gradient(ig) - -#fidelity = L1Norm(b=noisy_data) -#regulariser = FunctionOperatorComposition(alpha * L2NormSquared(), operator) - -fidelity = FunctionOperatorComposition(alpha * MixedL21Norm(), operator) -regulariser = 0.5 * L2NormSquared(b = noisy_data) - -x_init = ig.allocate() - -## Setup and run the PDHG algorithm -opt = {'tol': 1e-4, 'memopt':True} -fista = FISTA(x_init=x_init , f=regulariser, g=fidelity, opt=opt) -fista.max_iteration = 2000 -fista.update_objective_interval = 50 -fista.run(2000, verbose=True) - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(fista.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() - -# Compare with PDHG -method = '0' -# -if method == '0': -# -# # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) -# -# # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - f = BlockFunction(alpha * L2NormSquared(), fidelity) - g = ZeroFunction() - -## Compute operator Norm -normK = operator.norm() -# -## Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -# -# -## Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) -# -#%% -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(fista.get_output().as_array()) -plt.title('FISTA') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(pdhg.get_output().as_array()) -plt.title('PDHG') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(np.abs(pdhg.get_output().as_array()-fista.get_output().as_array())) -plt.title('Diff FISTA-PDHG') -plt.colorbar() -plt.show() - - -- cgit v1.2.3 From 4a1f878a19631147b80dcd146b75a396f2ffc2ac Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 12 Jun 2019 11:41:50 +0100 Subject: TGV tomophantom --- .../demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py | 64 ++++++++++------------ 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py index b2af753..e74e1c6 100644 --- a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py +++ b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py @@ -55,50 +55,41 @@ from ccpi.optimisation.algorithms import PDHG from ccpi.optimisation.operators import BlockOperator, Gradient, Identity, \ SymmetrizedGradient, ZeroOperator from ccpi.optimisation.functions import IndicatorBox, KullbackLeibler, ZeroFunction,\ - MixedL21Norm, BlockFunction + MixedL21Norm, BlockFunction, L2NormSquared from ccpi.astra.ops import AstraProjectorSimple -from ccpi.framework import TestData import os, sys -if int(numpy.version.version.split('.')[1]) > 12: - from skimage.util import random_noise -else: - from demoutil import random_noise - -#loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) - -# Load Data -#N = 50 -#M = 50 -#data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) -# -#ig = data.geometry -#ag = ig -N = 100 - -data = np.zeros((N,N)) -x1 = np.linspace(0, int(N/2), N) -x2 = np.linspace(int(N/2), 0., N) -xv, yv = np.meshgrid(x1, x2) -xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T +import tomophantom +from tomophantom import TomoP2D -data = xv -data = ImageData(data/data.max()) +# user supplied input +if len(sys.argv) > 1: + which_noise = int(sys.argv[1]) +else: + which_noise = 1 + +# Load Piecewise smooth Shepp-Logan phantom +model = 2 # select a model number from the library +N = 128 # set dimension of the phantom +# one can specify an exact path to the parameters file +# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' +path = os.path.dirname(tomophantom.__file__) +path_library2D = os.path.join(path, "Phantom2DLibrary.dat") +#This will generate a N_size x N_size phantom (2D) +phantom_2D = TomoP2D.Model(model, N, path_library2D) ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - +data = ImageData(phantom_2D) -#Create Acquisition Data and apply poisson noise +#Create Acquisition Data detectors = N angles = np.linspace(0, np.pi, N) - ag = AcquisitionGeometry('parallel','2D',angles, detectors) #device = input('Available device: GPU==1 / CPU==0 ') -device = '0' +device = '1' if device=='1': dev = 'gpu' else: @@ -107,7 +98,7 @@ else: Aop = AstraProjectorSimple(ig, ag, 'cpu') sin = Aop.direct(data) -# Create noisy data. +# Create noisy sinogram. noises = ['gaussian', 'poisson'] noise = noises[which_noise] @@ -144,11 +135,12 @@ op31 = Aop op32 = ZeroOperator(op22.domain_geometry(), ag) operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) +normK = operator.norm() # Create functions if noise == 'poisson': - alpha = 1 - beta = 5 + alpha = 3 + beta = 6 f3 = KullbackLeibler(noisy_data) g = BlockFunction(IndicatorBox(lower=0), ZeroFunction()) @@ -158,6 +150,7 @@ if noise == 'poisson': elif noise == 'gaussian': alpha = 20 + beta = 50 f3 = 0.5 * L2NormSquared(b=noisy_data) g = BlockFunction(ZeroFunction(), ZeroFunction()) @@ -177,7 +170,6 @@ pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) pdhg.max_iteration = 3000 pdhg.update_objective_interval = 500 pdhg.run(3000) - #%% plt.figure(figsize=(15,15)) plt.subplot(3,1,1) @@ -193,8 +185,8 @@ plt.imshow(pdhg.get_output()[0].as_array()) plt.title('TGV Reconstruction') plt.colorbar() plt.show() -plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TGV reconstruction') +plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), data.as_array()[:, int(N/2)], label = 'GTruth') +plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output()[0].as_array()[:, int(N/2)], label = 'TGV reconstruction') plt.legend() plt.title('Middle Line Profiles') plt.show() -- cgit v1.2.3 From 458e4c9a688630651675f9042f152f433c2f7076 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Wed, 12 Jun 2019 12:15:55 +0100 Subject: removed DS_Store files --- Wrappers/Python/demos/PDHG_examples/.DS_Store | Bin 6148 -> 0 bytes .../Python/demos/PDHG_examples/TV_Denoising/.DS_Store | Bin 6148 -> 0 bytes Wrappers/Python/wip/Demos/.DS_Store | Bin 6148 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Wrappers/Python/demos/PDHG_examples/.DS_Store delete mode 100644 Wrappers/Python/demos/PDHG_examples/TV_Denoising/.DS_Store delete mode 100644 Wrappers/Python/wip/Demos/.DS_Store diff --git a/Wrappers/Python/demos/PDHG_examples/.DS_Store b/Wrappers/Python/demos/PDHG_examples/.DS_Store deleted file mode 100644 index 141508d..0000000 Binary files a/Wrappers/Python/demos/PDHG_examples/.DS_Store and /dev/null differ diff --git a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/.DS_Store b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/.DS_Store and /dev/null differ diff --git a/Wrappers/Python/wip/Demos/.DS_Store b/Wrappers/Python/wip/Demos/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/Wrappers/Python/wip/Demos/.DS_Store and /dev/null differ -- cgit v1.2.3 From 095e9923b3fbc7a4ead3159510f3c6dea8959198 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 12 Jun 2019 15:05:09 +0100 Subject: add random --- Wrappers/Python/ccpi/framework/BlockGeometry.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Wrappers/Python/ccpi/framework/BlockGeometry.py b/Wrappers/Python/ccpi/framework/BlockGeometry.py index ed44d99..e58035b 100755 --- a/Wrappers/Python/ccpi/framework/BlockGeometry.py +++ b/Wrappers/Python/ccpi/framework/BlockGeometry.py @@ -10,6 +10,12 @@ from ccpi.framework import BlockDataContainer #from ccpi.optimisation.operators import Operator, LinearOperator class BlockGeometry(object): + + RANDOM = 'random' + RANDOM_INT = 'random_int' + + + '''Class to hold Geometry as column vector''' #__array_priority__ = 1 def __init__(self, *args, **kwargs): -- cgit v1.2.3 From 144c23b09281a4bdd767ea89db70d028cda05b40 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 12 Jun 2019 15:17:26 +0100 Subject: delete old demo --- .../TGV_Denoising/PDHG_TGV_Denoising_SaltPepper.py | 245 --------------------- 1 file changed, 245 deletions(-) delete mode 100644 Wrappers/Python/demos/PDHG_examples/TGV_Denoising/PDHG_TGV_Denoising_SaltPepper.py diff --git a/Wrappers/Python/demos/PDHG_examples/TGV_Denoising/PDHG_TGV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_examples/TGV_Denoising/PDHG_TGV_Denoising_SaltPepper.py deleted file mode 100644 index 15c0a05..0000000 --- a/Wrappers/Python/demos/PDHG_examples/TGV_Denoising/PDHG_TGV_Denoising_SaltPepper.py +++ /dev/null @@ -1,245 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= -""" - -Total Generalised Variation (TGV) Denoising using PDHG algorithm: - - -Problem: min_{x} \alpha * ||\nabla x - w||_{2,1} + - \beta * || E w ||_{2,1} + - \frac{1}{2} * || x - g ||_{2}^{2} - - \alpha: Regularization parameter - \beta: Regularization parameter - - \nabla: Gradient operator - E: Symmetrized Gradient operator - - g: Noisy Data with Salt & Pepper Noise - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity - ZeroOperator, E - Identity, ZeroOperator] - - - Method = 1 (PDHG - explicit ): K = [ \nabla, - Identity - ZeroOperator, E ] - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, \ - Gradient, SymmetrizedGradient, ZeroOperator -from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TGV SaltPepper denoising - -N = 100 - -data = np.zeros((N,N)) - -x1 = np.linspace(0, int(N/2), N) -x2 = np.linspace(int(N/2), 0., N) -xv, yv = np.meshgrid(x1, x2) - -xv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1] = yv[int(N/4):int(3*N/4)-1, int(N/4):int(3*N/4)-1].T - -data = xv -data = ImageData(data/data.max()) - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Add Gaussian noise -n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) -noisy_data = ImageData(n1) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(15,15)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Regularisation Parameters -alpha = 0.8 -beta = numpy.sqrt(2)* alpha - -method = '1' - -if method == '0': - - # Create operators - op11 = Gradient(ig) - op12 = Identity(op11.range_geometry()) - - op22 = SymmetrizedGradient(op11.domain_geometry()) - op21 = ZeroOperator(ig, op22.range_geometry()) - - op31 = Identity(ig, ag) - op32 = ZeroOperator(op22.domain_geometry(), ag) - - operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) - - f1 = alpha * MixedL21Norm() - f2 = beta * MixedL21Norm() - f3 = L1Norm(b=noisy_data) - f = BlockFunction(f1, f2, f3) - g = ZeroFunction() - -else: - - # Create operators - op11 = Gradient(ig) - op12 = Identity(op11.range_geometry()) - op22 = SymmetrizedGradient(op11.domain_geometry()) - op21 = ZeroOperator(ig, op22.range_geometry()) - - operator = BlockOperator(op11, -1*op12, op21, op22, shape=(2,2) ) - - f1 = alpha * MixedL21Norm() - f2 = beta * MixedL21Norm() - - f = BlockFunction(f1, f2) - g = BlockFunction(L1Norm(b=noisy_data), ZeroFunction()) - -## Compute operator Norm -normK = operator.norm() -# -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000, verbose = False) - -#%% -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output()[0].as_array()) -plt.title('TGV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - -if cvx_not_installable: - - u = Variable(ig.shape) - w1 = Variable((N, N)) - w2 = Variable((N, N)) - - # create TGV regulariser - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u) - vec(w1), \ - DY.matrix() * vec(u) - vec(w2)]), 2, axis = 0)) + \ - beta * sum(norm(vstack([ DX.matrix().transpose() * vec(w1), DY.matrix().transpose() * vec(w2), \ - 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ), \ - 0.5 * ( DX.matrix().transpose() * vec(w2) + DY.matrix().transpose() * vec(w1) ) ]), 2, axis = 0 ) ) - - constraints = [] - fidelity = pnorm(u - noisy_data.as_array(),1) - solver = MOSEK - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output()[0].as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output()[0].as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output()[0].as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - -- cgit v1.2.3 From c2ec8d85841b059437d9e97a46540ee4e712b593 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 12 Jun 2019 15:22:05 +0100 Subject: delete old demo --- .../ccpi/optimisation/operators/BlockOperator.py | 2 +- .../demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py | 194 --------------------- 2 files changed, 1 insertion(+), 195 deletions(-) delete mode 100644 Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py diff --git a/Wrappers/Python/ccpi/optimisation/operators/BlockOperator.py b/Wrappers/Python/ccpi/optimisation/operators/BlockOperator.py index 5f04363..cbdc420 100755 --- a/Wrappers/Python/ccpi/optimisation/operators/BlockOperator.py +++ b/Wrappers/Python/ccpi/optimisation/operators/BlockOperator.py @@ -104,7 +104,7 @@ class BlockOperator(Operator): index = row*self.shape[1]+col return self.operators[index] - def calculate_norm(self, **kwargs): + def norm(self, **kwargs): norm = [op.norm(**kwargs)**2 for op in self.operators] return numpy.sqrt(sum(norm)) diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py deleted file mode 100644 index e74e1c6..0000000 --- a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TGV_Tomo2D.py +++ /dev/null @@ -1,194 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= -""" - -Total Generalised Variation (TGV) Tomography 2D using PDHG algorithm: - - -Problem: min_{x>0} \alpha * ||\nabla x - w||_{2,1} + \beta * || E w ||_{2,1} + - \frac{1}{2}||Au - g||^{2} - - min_{u>0} \alpha * ||\nabla u - w||_{2,1} + \beta * || E w ||_{2,1} + - int A u - g log(Au + \eta) - - \alpha: Regularization parameter - \beta: Regularization parameter - - \nabla: Gradient operator - E: Symmetrized Gradient operator - A: System Matrix - - g: Noisy Sinogram - - K = [ \nabla, - Identity - ZeroOperator, E - A, ZeroOperator] - -""" - -from ccpi.framework import AcquisitionGeometry, AcquisitionData, ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient, Identity, \ - SymmetrizedGradient, ZeroOperator -from ccpi.optimisation.functions import IndicatorBox, KullbackLeibler, ZeroFunction,\ - MixedL21Norm, BlockFunction, L2NormSquared - -from ccpi.astra.ops import AstraProjectorSimple -import os, sys - - -import tomophantom -from tomophantom import TomoP2D - -# user supplied input -if len(sys.argv) > 1: - which_noise = int(sys.argv[1]) -else: - which_noise = 1 - -# Load Piecewise smooth Shepp-Logan phantom -model = 2 # select a model number from the library -N = 128 # set dimension of the phantom -# one can specify an exact path to the parameters file -# path_library2D = '../../../PhantomLibrary/models/Phantom2DLibrary.dat' -path = os.path.dirname(tomophantom.__file__) -path_library2D = os.path.join(path, "Phantom2DLibrary.dat") -#This will generate a N_size x N_size phantom (2D) -phantom_2D = TomoP2D.Model(model, N, path_library2D) - -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -data = ImageData(phantom_2D) - -#Create Acquisition Data -detectors = N -angles = np.linspace(0, np.pi, N) -ag = AcquisitionGeometry('parallel','2D',angles, detectors) - -#device = input('Available device: GPU==1 / CPU==0 ') -device = '1' -if device=='1': - dev = 'gpu' -else: - dev = 'cpu' - -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -# Create noisy sinogram. -noises = ['gaussian', 'poisson'] -noise = noises[which_noise] - -if noise == 'poisson': - scale = 5 - eta = 0 - noisy_data = AcquisitionData(np.random.poisson( scale * (eta + sin.as_array()))/scale, ag) -elif noise == 'gaussian': - n1 = np.random.normal(0, 1, size = ag.shape) - noisy_data = AcquisitionData(n1 + sin.as_array(), ag) -else: - raise ValueError('Unsupported Noise ', noise) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,10)) -plt.subplot(1,2,2) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(1,2,1) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Create Operators -op11 = Gradient(ig) -op12 = Identity(op11.range_geometry()) - -op22 = SymmetrizedGradient(op11.domain_geometry()) -op21 = ZeroOperator(ig, op22.range_geometry()) - -op31 = Aop -op32 = ZeroOperator(op22.domain_geometry(), ag) - -operator = BlockOperator(op11, -1*op12, op21, op22, op31, op32, shape=(3,2) ) -normK = operator.norm() - -# Create functions -if noise == 'poisson': - alpha = 3 - beta = 6 - f3 = KullbackLeibler(noisy_data) - g = BlockFunction(IndicatorBox(lower=0), ZeroFunction()) - - # Primal & dual stepsizes - sigma = 1 - tau = 1/(sigma*normK**2) - -elif noise == 'gaussian': - alpha = 20 - beta = 50 - f3 = 0.5 * L2NormSquared(b=noisy_data) - g = BlockFunction(ZeroFunction(), ZeroFunction()) - - # Primal & dual stepsizes - sigma = 10 - tau = 1/(sigma*normK**2) - -f1 = alpha * MixedL21Norm() -f2 = beta * MixedL21Norm() -f = BlockFunction(f1, f2, f3) - -# Compute operator Norm -normK = operator.norm() - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 3000 -pdhg.update_objective_interval = 500 -pdhg.run(3000) -#%% -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output()[0].as_array()) -plt.title('TGV Reconstruction') -plt.colorbar() -plt.show() -plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), data.as_array()[:, int(N/2)], label = 'GTruth') -plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output()[0].as_array()[:, int(N/2)], label = 'TGV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -- cgit v1.2.3 From c429efbf2bc85d42c010961cdf5e8a4c8d8c50b3 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 12 Jun 2019 15:22:37 +0100 Subject: fix demos --- .../Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py | 6 +++--- .../Python/demos/PDHG_examples/GatherAll/PDHG_TV_Tomo2D.py | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py index d848b9f..6937fa0 100755 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Denoising.py @@ -63,6 +63,7 @@ from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ KullbackLeibler from ccpi.framework import TestData import os, sys +#import scipy.io if int(numpy.version.version.split('.')[1]) > 12: from skimage.util import random_noise else: @@ -211,7 +212,7 @@ else: plt.show() -##%% Check with CVX solution +#%% Check with CVX solution from ccpi.optimisation.operators import SparseFiniteDiff @@ -231,8 +232,7 @@ if cvx_not_installable: DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = pnorm( u - noisy_data.as_array(),1) + regulariser = alpha * sum(norm(vstack([Constant(DX.matrix()) * vec(u), Constant(DY.matrix()) * vec(u)]), 2, axis = 0)) # choose solver if 'MOSEK' in installed_solvers(): diff --git a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Tomo2D.py b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Tomo2D.py index f179e95..4f7639e 100644 --- a/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Tomo2D.py +++ b/Wrappers/Python/demos/PDHG_examples/GatherAll/PDHG_TV_Tomo2D.py @@ -52,12 +52,12 @@ from ccpi.astra.ops import AstraProjectorSimple from ccpi.framework import TestData from PIL import Image import os, sys -if int(numpy.version.version.split('.')[1]) > 12: - from skimage.util import random_noise -else: - from demoutil import random_noise +#if int(numpy.version.version.split('.')[1]) > 12: +from skimage.util import random_noise +#else: +# from demoutil import random_noise -import scipy.io +#import scipy.io # user supplied input if len(sys.argv) > 1: -- cgit v1.2.3 From 774f4bc8baf320c9cf8e62ff00d77a2781b28fd7 Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Wed, 12 Jun 2019 15:26:21 +0100 Subject: delete old demos --- .../TV_Denoising/PDHG_TV_Denoising_Gaussian.py | 225 -------------------- .../TV_Denoising/PDHG_TV_Denoising_Poisson.py | 212 ------------------- .../TV_Denoising/PDHG_TV_Denoising_SaltPepper.py | 213 ------------------- .../demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D.py | 173 ---------------- .../PDHG_examples/Tomo/PDHG_TV_Tomo2D_gaussian.py | 212 ------------------- .../PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py | 230 --------------------- .../Python/demos/PDHG_examples/Tomo/phantom.mat | Bin 5583 -> 0 bytes 7 files changed, 1265 deletions(-) delete mode 100644 Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Gaussian.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Poisson.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_SaltPepper.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_gaussian.py delete mode 100644 Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py delete mode 100755 Wrappers/Python/demos/PDHG_examples/Tomo/phantom.mat diff --git a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Gaussian.py b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Gaussian.py deleted file mode 100644 index 9d00ee1..0000000 --- a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Gaussian.py +++ /dev/null @@ -1,225 +0,0 @@ -#======================================================================== -# CCP in Tomographic Imaging (CCPi) Core Imaging Library (CIL). - -# Copyright 2017 UKRI-STFC -# Copyright 2017 University of Manchester - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#========================================================================= -""" - -Total Variation Denoising using PDHG algorithm: - - -Problem: min_{x} \alpha * ||\nabla x||_{2,1} + \frac{1}{2} * || x - g ||_{2}^{2} - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Gaussian Noise - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity] - - - Method = 1 (PDHG - explicit ): K = \nabla - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from ccpi.framework import TestData -import os, sys -loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) - -# Load Data -N = 200 -M = 300 - - -# user can change the size of the input data -# you can choose between -# TestData.PEPPERS 2D + Channel -# TestData.BOAT 2D -# TestData.CAMERA 2D -# TestData.RESOLUTION_CHART 2D -# TestData.SIMPLE_PHANTOM_2D 2D -data = loader.load(TestData.BOAT, size=(N,M), scale=(0,1)) - -ig = data.geometry -ag = ig - -# Create Noisy data. Add Gaussian noise -np.random.seed(10) -noisy_data = ImageData( data.as_array() + np.random.normal(0, 0.1, size=data.shape) ) - -print ("min {} max {}".format(data.as_array().min(), data.as_array().max())) - -# Show Ground Truth and Noisy Data -plt.figure() -plt.subplot(1,3,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(1,3,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(1,3,3) -plt.imshow((data - noisy_data).as_array()) -plt.title('diff') -plt.colorbar() - -plt.show() - -# Regularisation Parameter -alpha = .1 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig, correlation=Gradient.CORRELATION_SPACE) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - f1 = alpha * MixedL21Norm() - f2 = 0.5 * L2NormSquared(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = 0.5 * L2NormSquared(b = noisy_data) - -# Compute Operator Norm -normK = operator.norm() - -# Primal & Dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -# Setup and Run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 10000 -pdhg.update_objective_interval = 100 -pdhg.run(1000, verbose=True) - -# Show Results -plt.figure() -plt.subplot(1,3,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.clim(0,1) -plt.subplot(1,3,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.clim(0,1) -plt.subplot(1,3,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.clim(0,1) -plt.colorbar() -plt.show() - -plt.plot(np.linspace(0,N,M), noisy_data.as_array()[int(N/2),:], label = 'Noisy data') -plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') - -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = 0.5 * sum_squares(u - noisy_data.as_array()) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = MOSEK) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,M), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,M), u.value[int(N/2),:], label = 'CVX') - plt.plot(np.linspace(0,N,M), data.as_array()[int(N/2),:], label = 'Truth') - - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - diff --git a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Poisson.py b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Poisson.py deleted file mode 100644 index 1d887c1..0000000 --- a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_Poisson.py +++ /dev/null @@ -1,212 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation Denoising using PDHG algorithm: - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + \int x - g * log(x) - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Poisson Noise - - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity] - - - Method = 1 (PDHG - explicit ): K = \nabla - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, KullbackLeibler, IndicatorBox, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Poisson denoising -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Apply Poisson noise -n1 = random_noise(data.as_array(), mode = 'poisson', seed = 10) -noisy_data = ImageData(n1) - - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,10)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - - -# Regularisation Parameter -alpha = 2 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - - f1 = alpha * MixedL21Norm() - f2 = KullbackLeibler(noisy_data) - f = BlockFunction(f1, f2) - g = IndicatorBox(lower=0) - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = KullbackLeibler(noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & Dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - -# Setup and Run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 3000 -pdhg.update_objective_interval = 200 -pdhg.run(3000, verbose=True) - - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u1 = Variable(ig.shape) - q = Variable() - w = Variable() - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u1), DY.matrix() * vec(u1)]), 2, axis = 0)) - fidelity = sum(kl_div(noisy_data.as_array(), u1)) - - constraints = [q>=fidelity, u1>=0] - - solver = ECOS - obj = Minimize( regulariser + q) - prob = Problem(obj, constraints) - result = prob.solve(verbose = True, solver = solver) - - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u1.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u1.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u1.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - diff --git a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_SaltPepper.py b/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_SaltPepper.py deleted file mode 100644 index c5709c3..0000000 --- a/Wrappers/Python/demos/PDHG_examples/TV_Denoising/PDHG_TV_Denoising_SaltPepper.py +++ /dev/null @@ -1,213 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation Denoising using PDHG algorithm: - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + ||x-g||_{1} - - \alpha: Regularization parameter - - \nabla: Gradient operator - - g: Noisy Data with Salt & Pepper Noise - - - Method = 0 ( PDHG - split ) : K = [ \nabla, - Identity] - - - Method = 1 (PDHG - explicit ): K = \nabla - - -""" - -from ccpi.framework import ImageData, ImageGeometry - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Identity, Gradient -from ccpi.optimisation.functions import ZeroFunction, L1Norm, \ - MixedL21Norm, BlockFunction - -from skimage.util import random_noise - -# Create phantom for TV Salt & Pepper denoising -N = 100 - -data = np.zeros((N,N)) -data[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -data[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 -data = ImageData(data) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) -ag = ig - -# Create noisy data. Apply Salt & Pepper noise -n1 = random_noise(data.as_array(), mode = 's&p', salt_vs_pepper = 0.9, amount=0.2) -noisy_data = ImageData(n1) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(15,15)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Regularisation Parameter -alpha = 2 - -method = '0' - -if method == '0': - - # Create operators - op1 = Gradient(ig) - op2 = Identity(ig, ag) - - # Create BlockOperator - operator = BlockOperator(op1, op2, shape=(2,1) ) - - # Create functions - f1 = alpha * MixedL21Norm() - f2 = L1Norm(b = noisy_data) - f = BlockFunction(f1, f2) - - g = ZeroFunction() - -else: - - # Without the "Block Framework" - operator = Gradient(ig) - f = alpha * MixedL21Norm() - g = L1Norm(b = noisy_data) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) -opt = {'niter':2000, 'memopt': True} - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma, memopt=True) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 50 -pdhg.run(2000) - - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -##%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - - -if cvx_not_installable: - - ##Construct problem - u = Variable(ig.shape) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - # Define Total Variation as a regulariser - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - fidelity = pnorm( u - noisy_data.as_array(),1) - - # choose solver - if 'MOSEK' in installed_solvers(): - solver = MOSEK - else: - solver = SCS - - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - u.value ) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(u.value) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), u.value[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) - - - - - diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D.py deleted file mode 100644 index f179e95..0000000 --- a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D.py +++ /dev/null @@ -1,173 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation 2D Tomography Reconstruction using PDHG algorithm: - - -Problem: min_u \alpha * ||\nabla u||_{2,1} + \frac{1}{2}||Au - g||^{2} - min_u, u>0 \alpha * ||\nabla u||_{2,1} + \int A u - g log (Au + \eta) - - \nabla: Gradient operator - A: System Matrix - g: Noisy sinogram - \eta: Background noise - - \alpha: Regularization parameter - -""" - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction, KullbackLeibler, IndicatorBox - -from ccpi.astra.ops import AstraProjectorSimple -from ccpi.framework import TestData -from PIL import Image -import os, sys -if int(numpy.version.version.split('.')[1]) > 12: - from skimage.util import random_noise -else: - from demoutil import random_noise - -import scipy.io - -# user supplied input -if len(sys.argv) > 1: - which_noise = int(sys.argv[1]) -else: - which_noise = 1 - -# Load 256 shepp-logan -data256 = scipy.io.loadmat('phantom.mat')['phantom256'] -data = ImageData(numpy.array(Image.fromarray(data256).resize((256,256)))) -N, M = data.shape -ig = ImageGeometry(voxel_num_x=N, voxel_num_y=M) - -# Add it to testdata or use tomophantom -#loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) -#data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(50, 50)) -#ig = data.geometry - -# Create acquisition data and geometry -detectors = N -angles = np.linspace(0, np.pi, 180) -ag = AcquisitionGeometry('parallel','2D',angles, detectors) - -# Select device -device = '0' -#device = input('Available device: GPU==1 / CPU==0 ') -if device=='1': - dev = 'gpu' -else: - dev = 'cpu' - -Aop = AstraProjectorSimple(ig, ag, dev) -sin = Aop.direct(data) - -# Create noisy data. Apply Gaussian noise -noises = ['gaussian', 'poisson'] -noise = noises[which_noise] - -if noise == 'poisson': - scale = 5 - eta = 0 - noisy_data = AcquisitionData(np.random.poisson( scale * (eta + sin.as_array()))/scale, ag) -elif noise == 'gaussian': - n1 = np.random.normal(0, 1, size = ag.shape) - noisy_data = AcquisitionData(n1 + sin.as_array(), ag) -else: - raise ValueError('Unsupported Noise ', noise) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,10)) -plt.subplot(1,2,2) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(1,2,1) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Create BlockOperator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -# Compute operator Norm -normK = operator.norm() - -# Create functions -if noise == 'poisson': - alpha = 3 - f2 = KullbackLeibler(noisy_data) - g = IndicatorBox(lower=0) - sigma = 1 - tau = 1/(sigma*normK**2) - -elif noise == 'gaussian': - alpha = 20 - f2 = 0.5 * L2NormSquared(b=noisy_data) - g = ZeroFunction() - sigma = 10 - tau = 1/(sigma*normK**2) - -f1 = alpha * MixedL21Norm() -f = BlockFunction(f1, f2) - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000) - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,ig.shape[1],ig.shape[1]), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_gaussian.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_gaussian.py deleted file mode 100644 index bd1bb69..0000000 --- a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_gaussian.py +++ /dev/null @@ -1,212 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation 2D Tomography Reconstruction using PDHG algorithm: - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + \frac{1}{2}||Ax - g||^{2} - - \nabla: Gradient operator - - A: Projection Matrix - g: Noisy sinogram corrupted with Gaussian Noise - - \alpha: Regularization parameter - -""" - -from ccpi.framework import ImageData, ImageGeometry, AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import ZeroFunction, L2NormSquared, \ - MixedL21Norm, BlockFunction - -from ccpi.astra.ops import AstraProjectorSimple - - - -# Create phantom for TV 2D tomography -N = 100 -x = np.zeros((N,N)) -x[round(N/4):round(3*N/4),round(N/4):round(3*N/4)] = 0.5 -x[round(N/8):round(7*N/8),round(3*N/8):round(5*N/8)] = 1 - -data = ImageData(x) -ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N) - -detectors = N -angles = np.linspace(0, np.pi, N) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) - -device = input('Available device: GPU==1 / CPU==0 ') - -if device=='1': - dev = 'gpu' -else: - dev = 'cpu' - -Aop = AstraProjectorSimple(ig, ag, dev) -sin = Aop.direct(data) - -# Create noisy data. Apply Poisson noise -n1 = np.random.normal(0, 3, size=ag.shape) -noisy_data = AcquisitionData(n1 + sin.as_array(), ag) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,10)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - -# Regularisation Parameter -alpha = 50 - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Create BlockOperator -operator = BlockOperator(op1, op2, shape=(2,1) ) - -# Create functions - -f1 = alpha * MixedL21Norm() -f2 = 0.5 * L2NormSquared(b=noisy_data) -f = BlockFunction(f1, f2) - -g = ZeroFunction() - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 10 -tau = 1/(sigma*normK**2) - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 2000 -pdhg.update_objective_interval = 200 -pdhg.run(2000) - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution - -from ccpi.optimisation.operators import SparseFiniteDiff -import astra -import numpy - -try: - from cvxpy import * - cvx_not_installable = True -except ImportError: - cvx_not_installable = False - -if cvx_not_installable: - - ##Construct problem - u = Variable(N*N) - - DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') - DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') - - regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) - - # create matrix representation for Astra operator - vol_geom = astra.create_vol_geom(N, N) - proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) - - proj_id = astra.create_projector('strip', proj_geom, vol_geom) - - matrix_id = astra.projector.matrix(proj_id) - - ProjMat = astra.matrix.get(matrix_id) - - tmp = noisy_data.as_array().ravel() - - fidelity = 0.5 * sum_squares(ProjMat * u - tmp) - - solver = MOSEK - obj = Minimize( regulariser + fidelity) - prob = Problem(obj) - result = prob.solve(verbose = True, solver = solver) - - diff_cvx = numpy.abs( pdhg.get_output().as_array() - np.reshape(u.value, (N,N) )) - - plt.figure(figsize=(15,15)) - plt.subplot(3,1,1) - plt.imshow(pdhg.get_output().as_array()) - plt.title('PDHG solution') - plt.colorbar() - plt.subplot(3,1,2) - plt.imshow(np.reshape(u.value, (N, N))) - plt.title('CVX solution') - plt.colorbar() - plt.subplot(3,1,3) - plt.imshow(diff_cvx) - plt.title('Difference') - plt.colorbar() - plt.show() - - plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') - plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N,N) )[int(N/2),:], label = 'CVX') - plt.legend() - plt.title('Middle Line Profiles') - plt.show() - - print('Primal Objective (CVX) {} '.format(obj.value)) - print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py b/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py deleted file mode 100644 index e7e33cb..0000000 --- a/Wrappers/Python/demos/PDHG_examples/Tomo/PDHG_TV_Tomo2D_poisson.py +++ /dev/null @@ -1,230 +0,0 @@ -#======================================================================== -# Copyright 2019 Science Technology Facilities Council -# Copyright 2019 University of Manchester -# -# This work is part of the Core Imaging Library developed by Science Technology -# Facilities Council and University of Manchester -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0.txt -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -#========================================================================= - -""" - -Total Variation Denoising using PDHG algorithm: - - -Problem: min_x, x>0 \alpha * ||\nabla x||_{2,1} + int A x -g log(Ax + \eta) - - \nabla: Gradient operator - - A: Projection Matrix - g: Noisy sinogram corrupted with Poisson Noise - - \eta: Background Noise - \alpha: Regularization parameter - -""" - -from ccpi.framework import AcquisitionGeometry, AcquisitionData - -import numpy as np -import numpy -import matplotlib.pyplot as plt - -from ccpi.optimisation.algorithms import PDHG - -from ccpi.optimisation.operators import BlockOperator, Gradient -from ccpi.optimisation.functions import KullbackLeibler, \ - MixedL21Norm, BlockFunction, IndicatorBox - -from ccpi.astra.ops import AstraProjectorSimple -from ccpi.framework import TestData -import os, sys - - - -loader = TestData(data_dir=os.path.join(sys.prefix, 'share','ccpi')) - -# Load Data -N = 50 -M = 50 -data = loader.load(TestData.SIMPLE_PHANTOM_2D, size=(N,M), scale=(0,1)) - -ig = data.geometry -ag = ig - -#Create Acquisition Data and apply poisson noise - -detectors = N -angles = np.linspace(0, np.pi, N) - -ag = AcquisitionGeometry('parallel','2D',angles, detectors) - -device = input('Available device: GPU==1 / CPU==0 ') - -if device=='1': - dev = 'gpu' -else: - dev = 'cpu' - -Aop = AstraProjectorSimple(ig, ag, 'cpu') -sin = Aop.direct(data) - -# Create noisy data. Apply Poisson noise -scale = 0.5 -eta = 0 -n1 = np.random.poisson(eta + sin.as_array()) - -noisy_data = AcquisitionData(n1, ag) - -# Show Ground Truth and Noisy Data -plt.figure(figsize=(10,10)) -plt.subplot(2,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(2,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.show() - - -#%% -# Regularisation Parameter -alpha = 2 - -# Create operators -op1 = Gradient(ig) -op2 = Aop - -# Create BlockOperator -operator = BlockOperator(op1,op2, shape=(2,1) ) - -# Create functions - -f1 = alpha * MixedL21Norm() -f2 = KullbackLeibler(noisy_data) -f = BlockFunction(f1, f2) - -g = IndicatorBox(lower=0) - - -# Compute operator Norm -normK = operator.norm() - -# Primal & dual stepsizes -sigma = 1 -tau = 1/(sigma*normK**2) - - -# Setup and run the PDHG algorithm -pdhg = PDHG(f=f,g=g,operator=operator, tau=tau, sigma=sigma) -pdhg.max_iteration = 3000 -pdhg.update_objective_interval = 500 -pdhg.run(3000, verbose = True) - -plt.figure(figsize=(15,15)) -plt.subplot(3,1,1) -plt.imshow(data.as_array()) -plt.title('Ground Truth') -plt.colorbar() -plt.subplot(3,1,2) -plt.imshow(noisy_data.as_array()) -plt.title('Noisy Data') -plt.colorbar() -plt.subplot(3,1,3) -plt.imshow(pdhg.get_output().as_array()) -plt.title('TV Reconstruction') -plt.colorbar() -plt.show() -## -plt.plot(np.linspace(0,N,N), data.as_array()[int(N/2),:], label = 'GTruth') -plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'TV reconstruction') -plt.legend() -plt.title('Middle Line Profiles') -plt.show() - - -#%% Check with CVX solution -# -#from ccpi.optimisation.operators import SparseFiniteDiff -#import astra -#import numpy -# -#try: -# from cvxpy import * -# cvx_not_installable = True -#except ImportError: -# cvx_not_installable = False -# -# -#if cvx_not_installable: -# -# -# ##Construct problem -# u = Variable(N*N) -# q = Variable() -# -# DY = SparseFiniteDiff(ig, direction=0, bnd_cond='Neumann') -# DX = SparseFiniteDiff(ig, direction=1, bnd_cond='Neumann') -# -# regulariser = alpha * sum(norm(vstack([DX.matrix() * vec(u), DY.matrix() * vec(u)]), 2, axis = 0)) -# -# # create matrix representation for Astra operator -# -# vol_geom = astra.create_vol_geom(N, N) -# proj_geom = astra.create_proj_geom('parallel', 1.0, detectors, angles) -# -# proj_id = astra.create_projector('strip', proj_geom, vol_geom) -# -# matrix_id = astra.projector.matrix(proj_id) -# -# ProjMat = astra.matrix.get(matrix_id) -# -# tmp = noisy_data.as_array().ravel() -# -# fidelity = sum(kl_div(tmp, ProjMat * u)) -# -# constraints = [q>=fidelity, u>=0] -# solver = SCS -# obj = Minimize( regulariser + q) -# prob = Problem(obj, constraints) -# result = prob.solve(verbose = True, solver = solver) -# -# diff_cvx = np.abs(pdhg.get_output().as_array() - np.reshape(u.value, (N, N))) -# -# plt.figure(figsize=(15,15)) -# plt.subplot(3,1,1) -# plt.imshow(pdhg.get_output().as_array()) -# plt.title('PDHG solution') -# plt.colorbar() -# plt.subplot(3,1,2) -# plt.imshow(np.reshape(u.value, (N, N))) -# plt.title('CVX solution') -# plt.colorbar() -# plt.subplot(3,1,3) -# plt.imshow(diff_cvx) -# plt.title('Difference') -# plt.colorbar() -# plt.show() -# -# plt.plot(np.linspace(0,N,N), pdhg.get_output().as_array()[int(N/2),:], label = 'PDHG') -# plt.plot(np.linspace(0,N,N), np.reshape(u.value, (N, N))[int(N/2),:], label = 'CVX') -# plt.legend() -# plt.title('Middle Line Profiles') -# plt.show() -# -# print('Primal Objective (CVX) {} '.format(obj.value)) -# print('Primal Objective (PDHG) {} '.format(pdhg.objective[-1][0])) \ No newline at end of file diff --git a/Wrappers/Python/demos/PDHG_examples/Tomo/phantom.mat b/Wrappers/Python/demos/PDHG_examples/Tomo/phantom.mat deleted file mode 100755 index c465bbe..0000000 Binary files a/Wrappers/Python/demos/PDHG_examples/Tomo/phantom.mat and /dev/null differ -- cgit v1.2.3 From 76080f077bdc29af9d0509dca606407936cdc8cd Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Wed, 12 Jun 2019 16:01:36 +0100 Subject: moved import --- Wrappers/Python/ccpi/framework/VectorData.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/framework/VectorData.py b/Wrappers/Python/ccpi/framework/VectorData.py index d0fed10..47ac0dd 100755 --- a/Wrappers/Python/ccpi/framework/VectorData.py +++ b/Wrappers/Python/ccpi/framework/VectorData.py @@ -28,7 +28,8 @@ from datetime import timedelta, datetime import warnings from functools import reduce from numbers import Number -from ccpi.framework import DataContainer, VectorGeometry +from ccpi.framework import DataContainer +from ccpi.framework import VectorGeometry class VectorData(DataContainer): def __init__(self, array=None, **kwargs): -- cgit v1.2.3 From f254767380dfe69d5280ef09ba8916dba3347d24 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 13 Jun 2019 04:48:54 -0400 Subject: trying to fix python2.7 import --- Wrappers/Python/ccpi/framework/Vector.py | 96 ++++++++++++++++++++++++++++++ Wrappers/Python/ccpi/framework/__init__.py | 6 +- 2 files changed, 100 insertions(+), 2 deletions(-) create mode 100755 Wrappers/Python/ccpi/framework/Vector.py diff --git a/Wrappers/Python/ccpi/framework/Vector.py b/Wrappers/Python/ccpi/framework/Vector.py new file mode 100755 index 0000000..b9ce486 --- /dev/null +++ b/Wrappers/Python/ccpi/framework/Vector.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# This work is part of the Core Imaging Library developed by +# Visual Analytics and Imaging System Group of the Science Technology +# Facilities Council, STFC + +# Copyright 2018-2019 Edoardo Pasca + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy +import sys +from datetime import timedelta, datetime +import warnings +from functools import reduce +from numbers import Number +from ccpi.framework import DataContainer + +class VectorData(DataContainer): + def __init__(self, array=None, **kwargs): + self.geometry = kwargs.get('geometry', None) + self.dtype = kwargs.get('dtype', numpy.float32) + + if self.geometry is None: + if array is None: + raise ValueError('Please specify either a geometry or an array') + else: + if len(array.shape) > 1: + raise ValueError('Incompatible size: expected 1D got {}'.format(array.shape)) + out = array + self.geometry = VectorGeometry.VectorGeometry(array.shape[0]) + self.length = self.geometry.length + else: + self.length = self.geometry.length + + if array is None: + out = numpy.zeros((self.length,), dtype=self.dtype) + else: + if self.length == array.shape[0]: + out = array + else: + raise ValueError('Incompatible size: expecting {} got {}'.format((self.length,), array.shape)) + deep_copy = True + super(VectorData, self).__init__(out, deep_copy, None) + +class VectorGeometry(object): + RANDOM = 'random' + RANDOM_INT = 'random_int' + + def __init__(self, + length): + + self.length = length + self.shape = (length, ) + + + def clone(self): + '''returns a copy of VectorGeometry''' + return VectorGeometry(self.length) + + def allocate(self, value=0, **kwargs): + '''allocates an VectorData according to the size expressed in the instance''' + self.dtype = kwargs.get('dtype', numpy.float32) + out = VectorData(geometry=self, dtype=self.dtype) + if isinstance(value, Number): + if value != 0: + out += value + else: + if value == VectorGeometry.RANDOM: + seed = kwargs.get('seed', None) + if seed is not None: + numpy.random.seed(seed) + out.fill(numpy.random.random_sample(self.shape)) + elif value == VectorGeometry.RANDOM_INT: + seed = kwargs.get('seed', None) + if seed is not None: + numpy.random.seed(seed) + max_value = kwargs.get('max_value', 100) + out.fill(numpy.random.randint(max_value,size=self.shape)) + else: + raise ValueError('Value {} unknown'.format(value)) + return out diff --git a/Wrappers/Python/ccpi/framework/__init__.py b/Wrappers/Python/ccpi/framework/__init__.py index 072ac84..66eb94d 100755 --- a/Wrappers/Python/ccpi/framework/__init__.py +++ b/Wrappers/Python/ccpi/framework/__init__.py @@ -25,5 +25,7 @@ from .framework import AX, PixelByPixelDataProcessor, CastDataContainer from .BlockDataContainer import BlockDataContainer from .BlockGeometry import BlockGeometry from .TestData import TestData -from .VectorGeometry import VectorGeometry -from .VectorData import VectorData +#from .VectorGeometry import VectorGeometry +#from .VectorData import VectorData +#from .pippo import pippo, pupi +from .Vector import VectorGeometry, VectorData -- cgit v1.2.3 From 23795ca3b019873b2fd295e03ae69db15edf69d5 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 13 Jun 2019 10:35:28 +0100 Subject: Delete FiniteDifferenceOperator_old.py --- .../operators/FiniteDifferenceOperator_old.py | 374 --------------------- 1 file changed, 374 deletions(-) delete mode 100644 Wrappers/Python/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py diff --git a/Wrappers/Python/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py b/Wrappers/Python/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py deleted file mode 100644 index 387fb4b..0000000 --- a/Wrappers/Python/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py +++ /dev/null @@ -1,374 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 1 22:51:17 2019 - -@author: evangelos -""" - -from ccpi.optimisation.operators import LinearOperator -from ccpi.optimisation.ops import PowerMethodNonsquare -from ccpi.framework import ImageData, BlockDataContainer -import numpy as np - -class FiniteDiff(LinearOperator): - - # Works for Neum/Symmetric & periodic boundary conditions - # TODO add central differences??? - # TODO not very well optimised, too many conditions - # TODO add discretisation step, should get that from imageGeometry - - # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] - # Grad_order = ['channels', 'direction_y', 'direction_x'] - # Grad_order = ['direction_z', 'direction_y', 'direction_x'] - # Grad_order = ['channels', 'direction_z', 'direction_y', 'direction_x'] - - def __init__(self, gm_domain, gm_range=None, direction=0, bnd_cond = 'Neumann'): - '''''' - super(FiniteDiff, self).__init__() - '''FIXME: domain and range should be geometries''' - self.gm_domain = gm_domain - self.gm_range = gm_range - - self.direction = direction - self.bnd_cond = bnd_cond - - # Domain Geometry = Range Geometry if not stated - if self.gm_range is None: - self.gm_range = self.gm_domain - # check direction and "length" of geometry - if self.direction + 1 > len(self.gm_domain.shape): - raise ValueError('Gradient directions more than geometry domain') - - #self.voxel_size = kwargs.get('voxel_size',1) - # this wrongly assumes a homogeneous voxel size - self.voxel_size = self.gm_domain.voxel_size_x - - - def direct(self, x, out=None): - - x_asarr = x.as_array() - x_sz = len(x.shape) - - if out is None: - out = np.zeros_like(x_asarr) - fd_arr = out - else: - fd_arr = out.as_array() -# fd_arr[:]=0 - -# if out is None: -# out = self.gm_domain.allocate().as_array() -# -# fd_arr = out.as_array() -# fd_arr = self.gm_domain.allocate().as_array() - - ######################## Direct for 2D ############################### - if x_sz == 2: - - if self.direction == 1: - - np.subtract( x_asarr[:,1:], x_asarr[:,0:-1], out = fd_arr[:,0:-1] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0], x_asarr[:,-1], out = fd_arr[:,-1] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 0: - - np.subtract( x_asarr[1:], x_asarr[0:-1], out = fd_arr[0:-1,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:], x_asarr[-1,:], out = fd_arr[-1,:] ) - else: - raise ValueError('No valid boundary conditions') - - ######################## Direct for 3D ############################### - elif x_sz == 3: - - if self.direction == 0: - - np.subtract( x_asarr[1:,:,:], x_asarr[0:-1,:,:], out = fd_arr[0:-1,:,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:,:], x_asarr[-1,:,:], out = fd_arr[-1,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 1: - - np.subtract( x_asarr[:,1:,:], x_asarr[:,0:-1,:], out = fd_arr[:,0:-1,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0,:], x_asarr[:,-1,:], out = fd_arr[:,-1,:] ) - else: - raise ValueError('No valid boundary conditions') - - - if self.direction == 2: - - np.subtract( x_asarr[:,:,1:], x_asarr[:,:,0:-1], out = fd_arr[:,:,0:-1] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,0], x_asarr[:,:,-1], out = fd_arr[:,:,-1] ) - else: - raise ValueError('No valid boundary conditions') - - ######################## Direct for 4D ############################### - elif x_sz == 4: - - if self.direction == 0: - np.subtract( x_asarr[1:,:,:,:], x_asarr[0:-1,:,:,:], out = fd_arr[0:-1,:,:,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:,:,:], x_asarr[-1,:,:,:], out = fd_arr[-1,:,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 1: - np.subtract( x_asarr[:,1:,:,:], x_asarr[:,0:-1,:,:], out = fd_arr[:,0:-1,:,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0,:,:], x_asarr[:,-1,:,:], out = fd_arr[:,-1,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 2: - np.subtract( x_asarr[:,:,1:,:], x_asarr[:,:,0:-1,:], out = fd_arr[:,:,0:-1,:] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,0,:], x_asarr[:,:,-1,:], out = fd_arr[:,:,-1,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 3: - np.subtract( x_asarr[:,:,:,1:], x_asarr[:,:,:,0:-1], out = fd_arr[:,:,:,0:-1] ) - - if self.bnd_cond == 'Neumann': - pass - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,:,0], x_asarr[:,:,:,-1], out = fd_arr[:,:,:,-1] ) - else: - raise ValueError('No valid boundary conditions') - - else: - raise NotImplementedError - -# res = out #/self.voxel_size - return type(x)(out) - - - def adjoint(self, x, out=None): - - x_asarr = x.as_array() - #x_asarr = x - x_sz = len(x.shape) - - if out is None: - out = np.zeros_like(x_asarr) - fd_arr = out - else: - fd_arr = out.as_array() - -# if out is None: -# out = self.gm_domain.allocate().as_array() -# fd_arr = out -# else: -# fd_arr = out.as_array() -## fd_arr = self.gm_domain.allocate().as_array() - - ######################## Adjoint for 2D ############################### - if x_sz == 2: - - if self.direction == 1: - - np.subtract( x_asarr[:,1:], x_asarr[:,0:-1], out = fd_arr[:,1:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,0], 0, out = fd_arr[:,0] ) - np.subtract( -x_asarr[:,-2], 0, out = fd_arr[:,-1] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0], x_asarr[:,-1], out = fd_arr[:,0] ) - - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 0: - - np.subtract( x_asarr[1:,:], x_asarr[0:-1,:], out = fd_arr[1:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[0,:], 0, out = fd_arr[0,:] ) - np.subtract( -x_asarr[-2,:], 0, out = fd_arr[-1,:] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:], x_asarr[-1,:], out = fd_arr[0,:] ) - - else: - raise ValueError('No valid boundary conditions') - - ######################## Adjoint for 3D ############################### - elif x_sz == 3: - - if self.direction == 0: - - np.subtract( x_asarr[1:,:,:], x_asarr[0:-1,:,:], out = fd_arr[1:,:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[0,:,:], 0, out = fd_arr[0,:,:] ) - np.subtract( -x_asarr[-2,:,:], 0, out = fd_arr[-1,:,:] ) - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:,:], x_asarr[-1,:,:], out = fd_arr[0,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 1: - np.subtract( x_asarr[:,1:,:], x_asarr[:,0:-1,:], out = fd_arr[:,1:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,0,:], 0, out = fd_arr[:,0,:] ) - np.subtract( -x_asarr[:,-2,:], 0, out = fd_arr[:,-1,:] ) - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0,:], x_asarr[:,-1,:], out = fd_arr[:,0,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 2: - np.subtract( x_asarr[:,:,1:], x_asarr[:,:,0:-1], out = fd_arr[:,:,1:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,:,0], 0, out = fd_arr[:,:,0] ) - np.subtract( -x_asarr[:,:,-2], 0, out = fd_arr[:,:,-1] ) - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,0], x_asarr[:,:,-1], out = fd_arr[:,:,0] ) - else: - raise ValueError('No valid boundary conditions') - - ######################## Adjoint for 4D ############################### - elif x_sz == 4: - - if self.direction == 0: - np.subtract( x_asarr[1:,:,:,:], x_asarr[0:-1,:,:,:], out = fd_arr[1:,:,:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[0,:,:,:], 0, out = fd_arr[0,:,:,:] ) - np.subtract( -x_asarr[-2,:,:,:], 0, out = fd_arr[-1,:,:,:] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[0,:,:,:], x_asarr[-1,:,:,:], out = fd_arr[0,:,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 1: - np.subtract( x_asarr[:,1:,:,:], x_asarr[:,0:-1,:,:], out = fd_arr[:,1:,:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,0,:,:], 0, out = fd_arr[:,0,:,:] ) - np.subtract( -x_asarr[:,-2,:,:], 0, out = fd_arr[:,-1,:,:] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,0,:,:], x_asarr[:,-1,:,:], out = fd_arr[:,0,:,:] ) - else: - raise ValueError('No valid boundary conditions') - - - if self.direction == 2: - np.subtract( x_asarr[:,:,1:,:], x_asarr[:,:,0:-1,:], out = fd_arr[:,:,1:,:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,:,0,:], 0, out = fd_arr[:,:,0,:] ) - np.subtract( -x_asarr[:,:,-2,:], 0, out = fd_arr[:,:,-1,:] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,0,:], x_asarr[:,:,-1,:], out = fd_arr[:,:,0,:] ) - else: - raise ValueError('No valid boundary conditions') - - if self.direction == 3: - np.subtract( x_asarr[:,:,:,1:], x_asarr[:,:,:,0:-1], out = fd_arr[:,:,:,1:] ) - - if self.bnd_cond == 'Neumann': - np.subtract( x_asarr[:,:,:,0], 0, out = fd_arr[:,:,:,0] ) - np.subtract( -x_asarr[:,:,:,-2], 0, out = fd_arr[:,:,:,-1] ) - - elif self.bnd_cond == 'Periodic': - np.subtract( x_asarr[:,:,:,0], x_asarr[:,:,:,-1], out = fd_arr[:,:,:,0] ) - else: - raise ValueError('No valid boundary conditions') - - else: - raise NotImplementedError - - out *= -1 #/self.voxel_size - return type(x)(out) - - def range_geometry(self): - '''Returns the range geometry''' - return self.gm_range - - def domain_geometry(self): - '''Returns the domain geometry''' - return self.gm_domain - - def norm(self): - x0 = self.gm_domain.allocate() - x0.fill( np.random.random_sample(x0.shape) ) - self.s1, sall, svec = PowerMethodNonsquare(self, 25, x0) - return self.s1 - - -if __name__ == '__main__': - - from ccpi.framework import ImageGeometry - import numpy - - N, M = 2, 3 - - ig = ImageGeometry(N, M) - - - FD = FiniteDiff(ig, direction = 0, bnd_cond = 'Neumann') - u = FD.domain_geometry().allocate('random_int') - - - res = FD.domain_geometry().allocate() - FD.direct(u, out=res) - - z = FD.direct(u) - print(z.as_array(), res.as_array()) - - for i in range(10): - - z1 = FD.direct(u) - FD.direct(u, out=res) - numpy.testing.assert_array_almost_equal(z1.as_array(), \ - res.as_array(), decimal=4) - - - - - - -# w = G.range_geometry().allocate('random_int') - - - - \ No newline at end of file -- cgit v1.2.3 From 3352510df0967c79eb9d06f9e024c8fdcdacd677 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 13 Jun 2019 11:06:47 +0100 Subject: test binary divide speedup is tested on 10 consecutive divisions --- Wrappers/Python/test/test_DataContainer.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Wrappers/Python/test/test_DataContainer.py b/Wrappers/Python/test/test_DataContainer.py index 4c53df8..7234167 100755 --- a/Wrappers/Python/test/test_DataContainer.py +++ b/Wrappers/Python/test/test_DataContainer.py @@ -324,16 +324,19 @@ class TestDataContainer(unittest.TestCase): ds = DataContainer(a, False, ['X', 'Y', 'Z']) ds1 = ds.copy() - steps.append(timer()) - ds.divide(ds1, out=ds) - steps.append(timer()) - t1 = dt(steps) - print("ds.divide(ds1, out=ds)", dt(steps)) - steps.append(timer()) - ds2 = ds.divide(ds1) - steps.append(timer()) - t2 = dt(steps) - print("ds2 = ds.divide(ds1)", dt(steps)) + t1 = 0 + t2 = 0 + for i in range(10): + steps.append(timer()) + ds.divide(ds1, out=ds) + steps.append(timer()) + t1 += dt(steps)/10. + print("ds.divide(ds1, out=ds)", dt(steps)) + steps.append(timer()) + ds2 = ds.divide(ds1) + steps.append(timer()) + t2 += dt(steps)/10. + print("ds2 = ds.divide(ds1)", dt(steps)) self.assertLess(t1, t2) self.assertEqual(ds.as_array()[0][0][0], 1.) -- cgit v1.2.3 From 118ac94a828e8b0f0404a54bb158fbc78b0f8016 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 13 Jun 2019 11:10:29 +0100 Subject: removed unused files --- Wrappers/Python/ccpi/framework/VectorData.py | 59 -------------------- Wrappers/Python/ccpi/framework/VectorGeometry.py | 69 ------------------------ Wrappers/Python/ccpi/framework/__init__.py | 3 -- 3 files changed, 131 deletions(-) delete mode 100755 Wrappers/Python/ccpi/framework/VectorData.py delete mode 100755 Wrappers/Python/ccpi/framework/VectorGeometry.py diff --git a/Wrappers/Python/ccpi/framework/VectorData.py b/Wrappers/Python/ccpi/framework/VectorData.py deleted file mode 100755 index 47ac0dd..0000000 --- a/Wrappers/Python/ccpi/framework/VectorData.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy -import sys -from datetime import timedelta, datetime -import warnings -from functools import reduce -from numbers import Number -from ccpi.framework import DataContainer -from ccpi.framework import VectorGeometry - -class VectorData(DataContainer): - def __init__(self, array=None, **kwargs): - self.geometry = kwargs.get('geometry', None) - self.dtype = kwargs.get('dtype', numpy.float32) - - if self.geometry is None: - if array is None: - raise ValueError('Please specify either a geometry or an array') - else: - if len(array.shape) > 1: - raise ValueError('Incompatible size: expected 1D got {}'.format(array.shape)) - out = array - self.geometry = VectorGeometry.VectorGeometry(array.shape[0]) - self.length = self.geometry.length - else: - self.length = self.geometry.length - - if array is None: - out = numpy.zeros((self.length,), dtype=self.dtype) - else: - if self.length == array.shape[0]: - out = array - else: - raise ValueError('Incompatible size: expecting {} got {}'.format((self.length,), array.shape)) - deep_copy = True - super(VectorData, self).__init__(out, deep_copy, None) diff --git a/Wrappers/Python/ccpi/framework/VectorGeometry.py b/Wrappers/Python/ccpi/framework/VectorGeometry.py deleted file mode 100755 index 255d2a0..0000000 --- a/Wrappers/Python/ccpi/framework/VectorGeometry.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -# This work is part of the Core Imaging Library developed by -# Visual Analytics and Imaging System Group of the Science Technology -# Facilities Council, STFC - -# Copyright 2018-2019 Edoardo Pasca - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy -import sys -from datetime import timedelta, datetime -import warnings -from functools import reduce -from numbers import Number -from ccpi.framework.VectorData import VectorData - -class VectorGeometry(object): - RANDOM = 'random' - RANDOM_INT = 'random_int' - - def __init__(self, - length): - - self.length = length - self.shape = (length, ) - - - def clone(self): - '''returns a copy of VectorGeometry''' - return VectorGeometry(self.length) - - def allocate(self, value=0, **kwargs): - '''allocates an VectorData according to the size expressed in the instance''' - self.dtype = kwargs.get('dtype', numpy.float32) - out = VectorData(geometry=self, dtype=self.dtype) - if isinstance(value, Number): - if value != 0: - out += value - else: - if value == VectorGeometry.RANDOM: - seed = kwargs.get('seed', None) - if seed is not None: - numpy.random.seed(seed) - out.fill(numpy.random.random_sample(self.shape)) - elif value == VectorGeometry.RANDOM_INT: - seed = kwargs.get('seed', None) - if seed is not None: - numpy.random.seed(seed) - max_value = kwargs.get('max_value', 100) - out.fill(numpy.random.randint(max_value,size=self.shape)) - else: - raise ValueError('Value {} unknown'.format(value)) - return out \ No newline at end of file diff --git a/Wrappers/Python/ccpi/framework/__init__.py b/Wrappers/Python/ccpi/framework/__init__.py index 66eb94d..3de27ed 100755 --- a/Wrappers/Python/ccpi/framework/__init__.py +++ b/Wrappers/Python/ccpi/framework/__init__.py @@ -25,7 +25,4 @@ from .framework import AX, PixelByPixelDataProcessor, CastDataContainer from .BlockDataContainer import BlockDataContainer from .BlockGeometry import BlockGeometry from .TestData import TestData -#from .VectorGeometry import VectorGeometry -#from .VectorData import VectorData -#from .pippo import pippo, pupi from .Vector import VectorGeometry, VectorData -- cgit v1.2.3 From 516ac57569a76e4d41a2abdd3cd786641f1aea7f Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 13 Jun 2019 13:28:02 +0100 Subject: fix Vector import --- Wrappers/Python/ccpi/framework/Vector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wrappers/Python/ccpi/framework/Vector.py b/Wrappers/Python/ccpi/framework/Vector.py index b9ce486..542cb7a 100755 --- a/Wrappers/Python/ccpi/framework/Vector.py +++ b/Wrappers/Python/ccpi/framework/Vector.py @@ -42,7 +42,7 @@ class VectorData(DataContainer): if len(array.shape) > 1: raise ValueError('Incompatible size: expected 1D got {}'.format(array.shape)) out = array - self.geometry = VectorGeometry.VectorGeometry(array.shape[0]) + self.geometry = VectorGeometry(array.shape[0]) self.length = self.geometry.length else: self.length = self.geometry.length -- cgit v1.2.3 From 4ad11441b6042de7518148d0fa59492313ee5e2e Mon Sep 17 00:00:00 2001 From: epapoutsellis Date: Thu, 13 Jun 2019 15:15:46 +0100 Subject: demo cmp fista, cgls, cvx, grdesc --- Wrappers/Python/wip/fix_test.py | 49 +++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/Wrappers/Python/wip/fix_test.py b/Wrappers/Python/wip/fix_test.py index 316606e..7f0124f 100755 --- a/Wrappers/Python/wip/fix_test.py +++ b/Wrappers/Python/wip/fix_test.py @@ -61,15 +61,15 @@ class Norm1(Function): opt = {'memopt': True} # Problem data. -m = 4 +m = 10 n = 10 np.random.seed(1) Amat = np.asarray( np.random.randn(m, n), dtype=numpy.float32) #Amat = np.asarray(np.eye(m), dtype=np.float32) * 2 A = LinearOperatorMatrix(Amat) bmat = np.asarray( np.random.randn(m), dtype=numpy.float32) -bmat *= 0 -bmat += 2 +#bmat *= 0 +#bmat += 2 print ("bmat", bmat.shape) print ("A", A.A) #bmat.shape = (bmat.shape[0], 1) @@ -78,8 +78,8 @@ print ("A", A.A) # Change n to equal to m. vgb = VectorGeometry(m) vgx = VectorGeometry(n) -b = vgb.allocate(2, dtype=numpy.float32) -# b.fill(bmat) +b = vgb.allocate(0, dtype=numpy.float32) +b.fill(bmat) #b = DataContainer(bmat) # Regularization parameter @@ -98,11 +98,11 @@ a = VectorData(x_init.as_array(), deep_copy=True) assert id(x_init.as_array()) != id(a.as_array()) -#%% -f.L = LinearOperator.PowerMethod(A, 25, x_init)[0] -print ('f.L', f.L) + +#f.L = LinearOperator.PowerMethod(A, 25, x_init)[0] +#print ('f.L', f.L) rate = (1 / f.L) / 6 -f.L *= 12 +#f.L *= 12 # Initial guess #x_init = DataContainer(np.zeros((n, 1))) @@ -145,16 +145,16 @@ fa.update_objective_interval = int( fa.max_iteration / 10 ) fa.run(fa.max_iteration, callback = None, verbose=True) gd = GradientDescent(x_init=x_init, objective_function=f, rate = rate ) -gd.max_iteration = 100 +gd.max_iteration = 5000 gd.update_objective_interval = int( gd.max_iteration / 10 ) gd.run(gd.max_iteration, callback = None, verbose=True) cgls = CGLS(x_init= x_initcgls, operator=A, data=b) cgls.max_iteration = 1000 -cgls.update_objective_interval = 2 +cgls.update_objective_interval = 100 #cgls.should_stop = stop_criterion(cgls) -cgls.run(10, callback = callback, verbose=True) +cgls.run(1000, callback = callback, verbose=True) # Print for comparison print("FISTA least squares plus 1-norm solution and objective value:") @@ -165,3 +165,28 @@ print ("data ", b.as_array()) print ('FISTA ', A.direct(fa.get_output()).as_array()) print ('GradientDescent', A.direct(gd.get_output()).as_array()) print ('CGLS ', A.direct(cgls.get_output()).as_array()) + + +#%% + +import cvxpy as cp +# Construct the problem. +x = cp.Variable(n) +objective = cp.Minimize(cp.sum_squares(A.A*x - bmat)) +prob = cp.Problem(objective) +# The optimal objective is returned by prob.solve(). +result = prob.solve(solver = cp.MOSEK) + +print ('CGLS ', cgls.get_output().as_array()) +print ('CVX ', x.value) + +print ('FISTA ', fa.get_output().as_array()) +print ('GD ', gd.get_output().as_array()) + + +#%% + + + + + -- cgit v1.2.3 From 3869559b14500fa4d730f084c4645b6c485c647f Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 13 Jun 2019 15:16:21 +0100 Subject: play around with test --- .../Python/ccpi/optimisation/algorithms/CGLS.py | 7 ++-- Wrappers/Python/wip/fix_test.py | 45 +++++++++++++++------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py b/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py index 8474d89..4faffad 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py @@ -50,7 +50,7 @@ class CGLS(Algorithm): def set_up(self, x_init, operator , data ): self.r = data.copy() - self.x = x_init.copy() + self.x = x_init * 0 self.operator = operator self.d = operator.adjoint(self.r) @@ -96,11 +96,12 @@ class CGLS(Algorithm): Ad = self.operator.direct(self.d) norm = Ad.squared_norm() if norm == 0.: - print ('cannot update solution') + print ('norm = 0, cannot update solution') + print ("self.d norm", self.d.squared_norm(), self.d.as_array()) raise StopIteration() alpha = self.normr2/norm if alpha == 0.: - print ('cannot update solution') + print ('alpha = 0, cannot update solution') raise StopIteration() self.d *= alpha Ad *= alpha diff --git a/Wrappers/Python/wip/fix_test.py b/Wrappers/Python/wip/fix_test.py index 316606e..9eb0a4e 100755 --- a/Wrappers/Python/wip/fix_test.py +++ b/Wrappers/Python/wip/fix_test.py @@ -61,15 +61,20 @@ class Norm1(Function): opt = {'memopt': True} # Problem data. -m = 4 -n = 10 -np.random.seed(1) +m = 500 +n = 200 + +# if m < n then the problem is under-determined and algorithms will struggle to find a solution. +# One approach is to add regularisation + +#np.random.seed(1) Amat = np.asarray( np.random.randn(m, n), dtype=numpy.float32) +Amat = np.asarray( np.random.random_integers(1,10, (m, n)), dtype=numpy.float32) #Amat = np.asarray(np.eye(m), dtype=np.float32) * 2 A = LinearOperatorMatrix(Amat) bmat = np.asarray( np.random.randn(m), dtype=numpy.float32) -bmat *= 0 -bmat += 2 +#bmat *= 0 +#bmat += 2 print ("bmat", bmat.shape) print ("A", A.A) #bmat.shape = (bmat.shape[0], 1) @@ -78,7 +83,7 @@ print ("A", A.A) # Change n to equal to m. vgb = VectorGeometry(m) vgx = VectorGeometry(n) -b = vgb.allocate(2, dtype=numpy.float32) +b = vgb.allocate(VectorGeometry.RANDOM_INT, dtype=numpy.float32) # b.fill(bmat) #b = DataContainer(bmat) @@ -99,11 +104,12 @@ a = VectorData(x_init.as_array(), deep_copy=True) assert id(x_init.as_array()) != id(a.as_array()) #%% -f.L = LinearOperator.PowerMethod(A, 25, x_init)[0] -print ('f.L', f.L) +# f.L = LinearOperator.PowerMethod(A, 25, x_init)[0] +# print ('f.L', f.L) rate = (1 / f.L) / 6 f.L *= 12 - +print (f.L) +# rate = f.L / 1000 # Initial guess #x_init = DataContainer(np.zeros((n, 1))) print ('x_init', x_init.as_array()) @@ -133,28 +139,35 @@ print ("pippo", pippo.as_array()) print ("x_init", x_init.as_array()) print ("x1", x1.as_array()) +y = A.direct(x_init) +y *= 0 +A.direct(x_init, out=y) # Combine with least squares and solve using generic FISTA implementation #x_fista1, it1, timing1, criter1 = FISTA(x_init, f, g1, opt=opt) def callback(it, objective, solution): - print (objective, f(solution)) + print ("callback " , it , objective, f(solution)) fa = FISTA(x_init=x_init, f=f, g=g1) -fa.max_iteration = 100 +fa.max_iteration = 1000 fa.update_objective_interval = int( fa.max_iteration / 10 ) fa.run(fa.max_iteration, callback = None, verbose=True) gd = GradientDescent(x_init=x_init, objective_function=f, rate = rate ) -gd.max_iteration = 100 +gd.max_iteration = 10000 gd.update_objective_interval = int( gd.max_iteration / 10 ) gd.run(gd.max_iteration, callback = None, verbose=True) + + cgls = CGLS(x_init= x_initcgls, operator=A, data=b) cgls.max_iteration = 1000 -cgls.update_objective_interval = 2 +cgls.update_objective_interval = int( cgls.max_iteration / 10 ) #cgls.should_stop = stop_criterion(cgls) -cgls.run(10, callback = callback, verbose=True) +cgls.run(cgls.max_iteration, callback = callback, verbose=True) + + # Print for comparison print("FISTA least squares plus 1-norm solution and objective value:") @@ -165,3 +178,7 @@ print ("data ", b.as_array()) print ('FISTA ', A.direct(fa.get_output()).as_array()) print ('GradientDescent', A.direct(gd.get_output()).as_array()) print ('CGLS ', A.direct(cgls.get_output()).as_array()) + +cond = numpy.linalg.cond(A.A) + +print ("cond" , cond) \ No newline at end of file -- cgit v1.2.3 From 24baca3a99254b0b1de13ffb9a5129de1e36355e Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 13 Jun 2019 15:36:43 +0100 Subject: fix test import --- Wrappers/Python/test/test_DataContainer.py | 28 ++++++++++++++++------------ Wrappers/Python/test/test_run_test.py | 1 - 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Wrappers/Python/test/test_DataContainer.py b/Wrappers/Python/test/test_DataContainer.py index 7234167..16f7b86 100755 --- a/Wrappers/Python/test/test_DataContainer.py +++ b/Wrappers/Python/test/test_DataContainer.py @@ -201,21 +201,25 @@ class TestDataContainer(unittest.TestCase): self.assertNumpyArrayEqual(out.as_array(), ds2.as_array()) ds0 = ds - steps.append(timer()) - ds0.add(2, out=out) - steps.append(timer()) - print("ds0.add(2,out=out)", dt(steps), 3, ds0.as_array()[0][0][0]) - self.assertEqual(3., out.as_array()[0][0][0]) + dt1 = 0 + dt2 = 0 + for i in range(10): + steps.append(timer()) + ds0.add(2, out=out) + steps.append(timer()) + print("ds0.add(2,out=out)", dt(steps), 3, ds0.as_array()[0][0][0]) + self.assertEqual(3., out.as_array()[0][0][0]) - dt1 = dt(steps) - steps.append(timer()) - ds3 = ds0.add(2) - steps.append(timer()) - print("ds3 = ds0.add(2)", dt(steps), 5, ds3.as_array()[0][0][0]) - dt2 = dt(steps) - self.assertLess(dt1, dt2) + dt1 += dt(steps)/10 + steps.append(timer()) + ds3 = ds0.add(2) + steps.append(timer()) + print("ds3 = ds0.add(2)", dt(steps), 5, ds3.as_array()[0][0][0]) + dt2 += dt(steps)/10 self.assertNumpyArrayEqual(out.as_array(), ds3.as_array()) + self.assertLess(dt1, dt2) + def binary_subtract(self): print("Test binary subtract") diff --git a/Wrappers/Python/test/test_run_test.py b/Wrappers/Python/test/test_run_test.py index a0db9cb..81ee738 100755 --- a/Wrappers/Python/test/test_run_test.py +++ b/Wrappers/Python/test/test_run_test.py @@ -12,7 +12,6 @@ from ccpi.optimisation.functions import Norm2Sq from ccpi.optimisation.functions import ZeroFunction # from ccpi.optimisation.funcs import Norm1 from ccpi.optimisation.functions import L1Norm -from ccpi.optimisation.funcs import Norm2 from ccpi.optimisation.operators import LinearOperatorMatrix from ccpi.optimisation.operators import Identity -- cgit v1.2.3 From 8573fb599ff506a9ebf3f2d5d07deb9b5c671609 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 13 Jun 2019 15:47:51 +0100 Subject: remove check isnan which fails with BlockDataContainers --- Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py b/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py index 4faffad..15acc31 100755 --- a/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py +++ b/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py @@ -106,9 +106,7 @@ class CGLS(Algorithm): self.d *= alpha Ad *= alpha self.r -= Ad - if numpy.isnan(self.r.as_array()).any(): - print ("some nan") - raise StopIteration() + self.x += self.d self.operator.adjoint(self.r, out=self.s) -- cgit v1.2.3 From 00a9428244fabe9d69f19470aae92b3b3ebdbb03 Mon Sep 17 00:00:00 2001 From: Edoardo Pasca Date: Thu, 13 Jun 2019 22:23:47 +0100 Subject: add numpy numerical types to allowed types for inline algebra --- Wrappers/Python/ccpi/framework/framework.py | 6 +++++- Wrappers/Python/test/test_algorithms.py | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Wrappers/Python/ccpi/framework/framework.py b/Wrappers/Python/ccpi/framework/framework.py index acb59ae..caea1e1 100755 --- a/Wrappers/Python/ccpi/framework/framework.py +++ b/Wrappers/Python/ccpi/framework/framework.py @@ -708,7 +708,11 @@ class DataContainer(object): return out else: raise ValueError(message(type(self),"Wrong size for data memory: out {} x2 {} expected {}".format( out.shape,x2.shape ,self.shape))) - elif issubclass(type(out), DataContainer) and isinstance(x2, (int,float,complex)): + elif issubclass(type(out), DataContainer) and \ + isinstance(x2, (int,float,complex, numpy.int, numpy.int8, \ + numpy.int16, numpy.int32, numpy.int64,\ + numpy.float, numpy.float16, numpy.float32,\ + numpy.float64, numpy.complex)): if self.check_dimensions(out): kwargs['out']=out.as_array() pwop(self.as_array(), x2, *args, **kwargs ) diff --git a/Wrappers/Python/test/test_algorithms.py b/Wrappers/Python/test/test_algorithms.py index 8c398f4..3bb3d57 100755 --- a/Wrappers/Python/test/test_algorithms.py +++ b/Wrappers/Python/test/test_algorithms.py @@ -64,10 +64,11 @@ class TestAlgorithms(unittest.TestCase): print ("Test CGLS") ig = ImageGeometry(124,153,154) x_init = ImageData(geometry=ig) + x_init = ig.allocate() b = x_init.copy() # fill with random numbers b.fill(numpy.random.random(x_init.shape)) - + b = ig.allocate('random') identity = Identity(ig) alg = CGLS(x_init=x_init, operator=identity, data=b) -- cgit v1.2.3