summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xWrappers/Python/ccpi/framework/BlockDataContainer.py479
-rwxr-xr-xWrappers/Python/ccpi/framework/BlockGeometry.py37
-rwxr-xr-xWrappers/Python/ccpi/framework/__init__.py26
-rwxr-xr-x[-rw-r--r--]Wrappers/Python/ccpi/framework/framework.py (renamed from Wrappers/Python/ccpi/framework.py)648
-rw-r--r--Wrappers/Python/ccpi/io/reader.py21
-rwxr-xr-xWrappers/Python/ccpi/optimisation/algorithms/Algorithm.py9
-rwxr-xr-xWrappers/Python/ccpi/optimisation/algorithms/CGLS.py3
-rw-r--r--Wrappers/Python/ccpi/optimisation/algorithms/FBPD.py2
-rwxr-xr-xWrappers/Python/ccpi/optimisation/algorithms/FISTA.py10
-rwxr-xr-xWrappers/Python/ccpi/optimisation/algorithms/GradientDescent.py14
-rw-r--r--Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py214
-rw-r--r--Wrappers/Python/ccpi/optimisation/algorithms/__init__.py3
-rwxr-xr-xWrappers/Python/ccpi/optimisation/algs.py18
-rwxr-xr-xWrappers/Python/ccpi/optimisation/funcs.py152
-rw-r--r--Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py209
-rw-r--r--Wrappers/Python/ccpi/optimisation/functions/Function.py69
-rw-r--r--Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py85
-rwxr-xr-xWrappers/Python/ccpi/optimisation/functions/IndicatorBox.py65
-rw-r--r--Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py123
-rw-r--r--Wrappers/Python/ccpi/optimisation/functions/L1Norm.py234
-rw-r--r--Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py303
-rwxr-xr-xWrappers/Python/ccpi/optimisation/functions/MixedL21Norm.py175
-rwxr-xr-xWrappers/Python/ccpi/optimisation/functions/Norm2Sq.py98
-rwxr-xr-xWrappers/Python/ccpi/optimisation/functions/ScaledFunction.py149
-rw-r--r--Wrappers/Python/ccpi/optimisation/functions/ZeroFunction.py61
-rw-r--r--Wrappers/Python/ccpi/optimisation/functions/__init__.py13
-rwxr-xr-xWrappers/Python/ccpi/optimisation/operators/BlockOperator.py366
-rw-r--r--Wrappers/Python/ccpi/optimisation/operators/BlockScaledOperator.py67
-rw-r--r--Wrappers/Python/ccpi/optimisation/operators/FiniteDifferenceOperator.py373
-rw-r--r--Wrappers/Python/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py374
-rw-r--r--Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py243
-rw-r--r--Wrappers/Python/ccpi/optimisation/operators/IdentityOperator.py79
-rwxr-xr-xWrappers/Python/ccpi/optimisation/operators/LinearOperator.py22
-rwxr-xr-xWrappers/Python/ccpi/optimisation/operators/Operator.py30
-rw-r--r--Wrappers/Python/ccpi/optimisation/operators/ScaledOperator.py51
-rw-r--r--Wrappers/Python/ccpi/optimisation/operators/ShrinkageOperator.py19
-rw-r--r--Wrappers/Python/ccpi/optimisation/operators/SparseFiniteDiff.py144
-rw-r--r--Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py186
-rw-r--r--Wrappers/Python/ccpi/optimisation/operators/ZeroOperator.py39
-rwxr-xr-xWrappers/Python/ccpi/optimisation/operators/__init__.py23
-rwxr-xr-xWrappers/Python/ccpi/optimisation/ops.py15
-rwxr-xr-xWrappers/Python/ccpi/processors.py1026
-rw-r--r--Wrappers/Python/conda-recipe/conda_build_config.yaml2
-rw-r--r--Wrappers/Python/conda-recipe/meta.yaml7
-rw-r--r--Wrappers/Python/setup.py7
-rwxr-xr-xWrappers/Python/test/test_BlockDataContainer.py474
-rw-r--r--Wrappers/Python/test/test_BlockOperator.py365
-rwxr-xr-xWrappers/Python/test/test_DataContainer.py35
-rwxr-xr-xWrappers/Python/test/test_Gradient.py90
-rw-r--r--Wrappers/Python/test/test_Operator.py412
-rwxr-xr-xWrappers/Python/test/test_algorithms.py1
-rw-r--r--Wrappers/Python/test/test_functions.py377
-rwxr-xr-xWrappers/Python/test/test_run_test.py31
-rw-r--r--Wrappers/Python/wip/CGLS_tikhonov.py196
-rw-r--r--Wrappers/Python/wip/CreatePhantom.py242
-rw-r--r--Wrappers/Python/wip/fista_TV_denoising.py72
-rwxr-xr-xWrappers/Python/wip/pdhg_TV_denoising.py124
-rw-r--r--Wrappers/Python/wip/pdhg_TV_denoising3D.py360
-rw-r--r--Wrappers/Python/wip/pdhg_TV_denoising_precond.py156
-rw-r--r--Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py180
-rw-r--r--Wrappers/Python/wip/pdhg_TV_tomography2D.py156
-rw-r--r--Wrappers/Python/wip/pdhg_TV_tomography2D_time.py152
-rw-r--r--Wrappers/Python/wip/pdhg_tv_denoising_poisson.py191
-rw-r--r--Wrappers/Python/wip/test_pdhg_gap.py140
-rw-r--r--Wrappers/Python/wip/test_pdhg_profile/profile_pdhg_TV_denoising.py273
-rw-r--r--Wrappers/Python/wip/test_profile.py84
66 files changed, 9445 insertions, 959 deletions
diff --git a/Wrappers/Python/ccpi/framework/BlockDataContainer.py b/Wrappers/Python/ccpi/framework/BlockDataContainer.py
new file mode 100755
index 0000000..386cd87
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/framework/BlockGeometry.py b/Wrappers/Python/ccpi/framework/BlockGeometry.py
new file mode 100755
index 0000000..5dd6750
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/framework/__init__.py b/Wrappers/Python/ccpi/framework/__init__.py
new file mode 100755
index 0000000..229edb5
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/framework.py b/Wrappers/Python/ccpi/framework/framework.py
index 24f4ca6..7516447 100644..100755
--- a/Wrappers/Python/ccpi/framework.py
+++ b/Wrappers/Python/ccpi/framework/framework.py
@@ -27,6 +27,8 @@ 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"""
@@ -43,6 +45,13 @@ def message(cls, msg, *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,
@@ -67,6 +76,30 @@ class ImageGeometry(object):
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
@@ -111,14 +144,51 @@ class ImageGeometry(object):
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):
+ def allocate(self, value=0, dimension_labels=None, **kwargs):
'''allocates an ImageData according to the size expressed in the instance'''
- out = ImageData(geometry=self, dimension_labels=dimension_labels)
- if value != 0:
- out += value
+ 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,
@@ -130,7 +200,7 @@ class AcquisitionGeometry(object):
dist_source_center=None,
dist_center_detector=None,
channels=1,
- angle_unit='degree'
+ **kwargs
):
"""
General inputs for standard type projection geometries
@@ -161,6 +231,7 @@ class AcquisitionGeometry(object):
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
@@ -171,6 +242,29 @@ class AcquisitionGeometry(object):
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'''
@@ -198,15 +292,35 @@ 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, dimension_labels=dimension_labels)
- if value != 0:
- out += value
+ 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'''
@@ -382,116 +496,21 @@ class DataContainer(object):
return self.shape == other.shape
## algebra
- def __add__(self, other , out=None, *args, **kwargs):
- if issubclass(type(other), DataContainer):
- if self.check_dimensions(other):
- out = self.as_array() + other.as_array()
- return type(self)(out,
- deep_copy=True,
- dimension_labels=self.dimension_labels,
- geometry=self.geometry)
- else:
- raise ValueError('Wrong shape: {0} and {1}'.format(self.shape,
- other.shape))
- elif isinstance(other, (int, float, complex)):
- return type(self)(
- self.as_array() + other,
- deep_copy=True,
- dimension_labels=self.dimension_labels,
- geometry=self.geometry)
- else:
- raise TypeError('Cannot {0} DataContainer with {1}'.format("add" ,
- type(other)))
- # __add__
+ def __add__(self, other):
+ return self.add(other)
+ def __mul__(self, other):
+ return self.multiply(other)
def __sub__(self, other):
- if issubclass(type(other), DataContainer):
- if self.check_dimensions(other):
- out = self.as_array() - other.as_array()
- return type(self)(out,
- deep_copy=True,
- dimension_labels=self.dimension_labels,
- geometry=self.geometry)
- else:
- raise ValueError('__sub__ Wrong shape: {0} and {1}'.format(self.shape,
- other.shape))
- elif isinstance(other, (int, float, complex)):
- return type(self)(self.as_array() - other,
- deep_copy=True,
- dimension_labels=self.dimension_labels,
- geometry=self.geometry)
- else:
- raise TypeError('Cannot {0} DataContainer with {1}'.format("subtract" ,
- type(other)))
- # __sub__
- def __truediv__(self,other):
- return self.__div__(other)
-
+ return self.subtract(other)
def __div__(self, other):
- if issubclass(type(other), DataContainer):
- if self.check_dimensions(other):
- out = self.as_array() / other.as_array()
- return type(self)(out,
- deep_copy=True,
- dimension_labels=self.dimension_labels,
- geometry=self.geometry)
- else:
- raise ValueError('__div__ Wrong shape: {0} and {1}'.format(self.shape,
- other.shape))
- elif isinstance(other, (int, float, complex)):
- return type(self)(self.as_array() / other,
- deep_copy=True,
- dimension_labels=self.dimension_labels,
- geometry=self.geometry)
- else:
- raise TypeError('Cannot {0} DataContainer with {1}'.format("divide" ,
- type(other)))
- # __div__
-
+ return self.divide(other)
+ def __truediv__(self, other):
+ return self.divide(other)
def __pow__(self, other):
- if issubclass(type(other), DataContainer):
- if self.check_dimensions(other):
- out = self.as_array() ** other.as_array()
- return type(self)(out,
- deep_copy=True,
- dimension_labels=self.dimension_labels,
- geometry=self.geometry)
- else:
- raise ValueError('__pow__ Wrong shape: {0} and {1}'.format(self.shape,
- other.shape))
- elif isinstance(other, (int, float, complex)):
- return type(self)(self.as_array() ** other,
- deep_copy=True,
- dimension_labels=self.dimension_labels,
- geometry=self.geometry)
- else:
- raise TypeError('pow: Cannot {0} DataContainer with {1}'.format("power" ,
- type(other)))
- # __pow__
+ return self.power(other)
+
- def __mul__(self, other):
- if issubclass(type(other), DataContainer):
- if self.check_dimensions(other):
- out = self.as_array() * other.as_array()
- return type(self)(out,
- deep_copy=True,
- dimension_labels=self.dimension_labels,
- geometry=self.geometry)
- else:
- raise ValueError('*:Wrong shape: {0} and {1}'.format(self.shape,
- other.shape))
- elif isinstance(other, (int, float, complex,\
- numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.int64,\
- numpy.float, numpy.float16, numpy.float32, numpy.float64, \
- numpy.complex)):
- return type(self)(self.as_array() * other,
- deep_copy=True,
- dimension_labels=self.dimension_labels,
- geometry=self.geometry)
- else:
- raise TypeError('Cannot {0} DataContainer with {1}'.format("multiply" ,
- type(other)))
- # __mul__
# reverse operand
def __radd__(self, other):
@@ -532,54 +551,27 @@ class DataContainer(object):
# (+=, -=, *=, /= , //=,
# must return self
-
-
def __iadd__(self, other):
- if isinstance(other, (int, float)) :
- numpy.add(self.array, other, out=self.array)
- elif issubclass(type(other), DataContainer):
- if self.check_dimensions(other):
- numpy.add(self.array, other.array, out=self.array)
- else:
- raise ValueError('Dimensions do not match')
- return self
- # __iadd__
+ kw = {'out':self}
+ return self.add(other, **kw)
def __imul__(self, other):
- if isinstance(other, (int, float)) :
- arr = self.as_array()
- numpy.multiply(arr, other, out=arr)
- elif issubclass(type(other), DataContainer):
- if self.check_dimensions(other):
- numpy.multiply(self.array, other.array, out=self.array)
- else:
- raise ValueError('Dimensions do not match')
- return self
- # __imul__
+ kw = {'out':self}
+ return self.multiply(other, **kw)
def __isub__(self, other):
- if isinstance(other, (int, float)) :
- numpy.subtract(self.array, other, out=self.array)
- elif issubclass(type(other), DataContainer):
- if self.check_dimensions(other):
- numpy.subtract(self.array, other.array, out=self.array)
- else:
- raise ValueError('Dimensions do not match')
- return self
- # __isub__
+ kw = {'out':self}
+ return self.subtract(other, **kw)
def __idiv__(self, other):
- return self.__itruediv__(other)
+ kw = {'out':self}
+ return self.divide(other, **kw)
+
def __itruediv__(self, other):
- if isinstance(other, (int, float)) :
- numpy.divide(self.array, other, out=self.array)
- elif issubclass(type(other), DataContainer):
- if self.check_dimensions(other):
- numpy.divide(self.array, other.array, out=self.array)
- else:
- raise ValueError('Dimensions do not match')
- return self
- # __idiv__
+ kw = {'out':self}
+ return self.divide(other, **kw)
+
+
def __str__ (self, representation=False):
repres = ""
@@ -639,8 +631,8 @@ class DataContainer(object):
## binary operations
- def pixel_wise_binary(self,pwop, x2 , out=None, *args, **kwargs):
-
+ 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 )
@@ -658,7 +650,8 @@ class DataContainer(object):
elif issubclass(type(out), DataContainer) and issubclass(type(x2), DataContainer):
if self.check_dimensions(out) and self.check_dimensions(x2):
- pwop(self.as_array(), x2.as_array(), out=out.as_array(), *args, **kwargs )
+ 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,
@@ -668,14 +661,15 @@ class DataContainer(object):
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):
-
- pwop(self.as_array(), x2, out=out.as_array(), *args, **kwargs )
+ 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:
- pwop(self.as_array(), x2 , out=out, *args, **kwargs)
+ kwargs['out'] = out
+ pwop(self.as_array(), x2, *args, **kwargs)
#return type(self)(out,
# deep_copy=False,
# dimension_labels=self.dimension_labels,
@@ -683,26 +677,39 @@ class DataContainer(object):
else:
raise ValueError (message(type(self), "incompatible class:" , pwop.__name__, type(out)))
- def add(self, other , out=None, *args, **kwargs):
- return self.pixel_wise_binary(numpy.add, other, out=out, *args, **kwargs)
-
- def subtract(self, other, out=None , *args, **kwargs):
- return self.pixel_wise_binary(numpy.subtract, other, out=out, *args, **kwargs)
+ 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 , out=None, *args, **kwargs):
- return self.pixel_wise_binary(numpy.multiply, other, out=out, *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 , out=None ,*args, **kwargs):
- return self.pixel_wise_binary(numpy.divide, other, out=out, *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 , out=None, *args, **kwargs):
- return self.pixel_wise_binary(numpy.power, other, out=out, *args, **kwargs)
+ def power(self, other, *args, **kwargs):
+ return self.pixel_wise_binary(numpy.power, other, *args, **kwargs)
- def maximum(self,x2, out=None, *args, **kwargs):
- return self.pixel_wise_binary(numpy.maximum, x2=x2, out=out, *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, out=None, *args, **kwargs):
+ 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,
@@ -711,38 +718,43 @@ class DataContainer(object):
geometry=self.geometry)
elif issubclass(type(out), DataContainer):
if self.check_dimensions(out):
- pwop(self.as_array(), out=out.as_array(), *args, **kwargs )
+ 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:
- pwop(self.as_array(), out=out, *args, **kwargs)
+ kwargs['out'] = out
+ pwop(self.as_array(), *args, **kwargs)
else:
raise ValueError (message(type(self), "incompatible class:" , pwop.__name__, type(out)))
- def abs(self, out=None, *args, **kwargs):
- return self.pixel_wise_unary(numpy.abs, out=out, *args, **kwargs)
-
- def sign(self, out=None, *args, **kwargs):
- return self.pixel_wise_unary(numpy.sign , out=out, *args, **kwargs)
+ def abs(self, *args, **kwargs):
+ return self.pixel_wise_unary(numpy.abs, *args, **kwargs)
- def sqrt(self, out=None, *args, **kwargs):
- return self.pixel_wise_unary(numpy.sqrt, out=out, *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, out=None, *args, **kwargs):
+ 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 numpy.dot(y, y.conjugate())
+ #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())
@@ -756,59 +768,22 @@ class DataContainer(object):
+
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 = None
+ self.geometry = kwargs.get('geometry', None)
if array is None:
- if 'geometry' in kwargs.keys():
- geometry = kwargs['geometry']
- self.geometry = geometry
- 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 = ['channel' ,'vertical' , 'horizontal_y' ,
- 'horizontal_x']
- else:
- shape = (channels , horiz_y, horiz_x)
- dim_labels = ['channel' , 'horizontal_y' ,
- 'horizontal_x']
- else:
- if vert > 1:
- shape = (vert, horiz_y, horiz_x)
- dim_labels = ['vertical' , 'horizontal_y' ,
- 'horizontal_x']
- else:
- shape = (horiz_y, horiz_x)
- dim_labels = ['horizontal_y' ,
- 'horizontal_x']
- dimension_labels = dim_labels
- else:
- shape = []
- for dim in dimension_labels:
- if dim == 'channel':
- shape.append(channels)
- elif dim == 'horizontal_y':
- shape.append(horiz_y)
- elif dim == 'vertical':
- shape.append(vert)
- elif dim == 'horizontal_x':
- shape.append(horiz_x)
- if len(shape) != len(dimension_labels):
- raise ValueError('Missing {0} axes'.format(
- len(dimension_labels) - len(shape)))
- shape = tuple(shape)
+ 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,
@@ -818,6 +793,11 @@ class ImageData(DataContainer):
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 \
@@ -838,14 +818,17 @@ class ImageData(DataContainer):
if dimension_labels is None:
if array.ndim == 4:
- dimension_labels = ['channel' ,'vertical' , 'horizontal_y' ,
- 'horizontal_x']
+ dimension_labels = [ImageGeometry.CHANNEL,
+ ImageGeometry.VERTICAL,
+ ImageGeometry.HORIZONTAL_Y,
+ ImageGeometry.HORIZONTAL_X]
elif array.ndim == 3:
- dimension_labels = ['vertical' , 'horizontal_y' ,
- 'horizontal_x']
+ dimension_labels = [ImageGeometry.VERTICAL,
+ ImageGeometry.HORIZONTAL_Y,
+ ImageGeometry.HORIZONTAL_X]
else:
- dimension_labels = ['horizontal_y' ,
- 'horizontal_x']
+ dimension_labels = [ ImageGeometry.HORIZONTAL_Y,
+ ImageGeometry.HORIZONTAL_X]
#DataContainer.__init__(self, array, deep_copy, dimension_labels, **kwargs)
super(ImageData, self).__init__(array, deep_copy,
@@ -861,75 +844,93 @@ class ImageData(DataContainer):
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 = None
+ self.geometry = kwargs.get('geometry', None)
if array is None:
if 'geometry' in kwargs.keys():
geometry = kwargs['geometry']
self.geometry = geometry
- 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 = ['channel' , ' angle' ,
- 'vertical' , 'horizontal']
- else:
- shape = (channels , num_of_angles, horiz)
- dim_labels = ['channel' , 'angle' ,
- 'horizontal']
- else:
- if vert > 1:
- shape = (num_of_angles, vert, horiz)
- dim_labels = ['angle' , 'vertical' ,
- 'horizontal']
- else:
- shape = (num_of_angles, horiz)
- dim_labels = ['angle' ,
- 'horizontal']
-
- dimension_labels = dim_labels
- else:
- shape = []
- for dim in dimension_labels:
- if dim == 'channel':
- shape.append(channels)
- elif dim == 'angle':
- shape.append(num_of_angles)
- elif dim == 'vertical':
- shape.append(vert)
- elif dim == '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)
+ 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 \
@@ -950,21 +951,81 @@ class AcquisitionData(DataContainer):
if dimension_labels is None:
if array.ndim == 4:
- dimension_labels = ['channel' ,'angle' , 'vertical' ,
- 'horizontal']
+ dimension_labels = [AcquisitionGeometry.CHANNEL,
+ AcquisitionGeometry.ANGLE,
+ AcquisitionGeometry.VERTICAL,
+ AcquisitionGeometry.HORIZONTAL]
elif array.ndim == 3:
- dimension_labels = ['angle' , 'vertical' ,
- 'horizontal']
+ dimension_labels = [AcquisitionGeometry.ANGLE,
+ AcquisitionGeometry.VERTICAL,
+ AcquisitionGeometry.HORIZONTAL]
else:
- dimension_labels = ['angle' ,
- 'horizontal']
-
- #DataContainer.__init__(self, array, deep_copy, dimension_labels, **kwargs)
+ 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
@@ -1010,6 +1071,7 @@ class DataProcessor(object):
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))
diff --git a/Wrappers/Python/ccpi/io/reader.py b/Wrappers/Python/ccpi/io/reader.py
index 856f5e0..07e3bf9 100644
--- a/Wrappers/Python/ccpi/io/reader.py
+++ b/Wrappers/Python/ccpi/io/reader.py
@@ -241,26 +241,37 @@ class NexusReader(object):
pass
dims = file[self.data_path].shape
if ymin is None and ymax is None:
- data = np.array(file[self.data_path])
+
+ 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 = np.array(file[self.data_path][:,:ymax,:])
+ data = projections[:,:ymax,:]
elif ymax is None:
ymax = dims[1]
if ymin < 0:
raise ValueError('ymin out of range')
- data = np.array(file[self.data_path][:,ymin:,:])
+ data = projections[:,ymin:,:]
else:
if ymax > dims[1]:
raise ValueError('ymax out of range')
if ymin < 0:
raise ValueError('ymin out of range')
- data = np.array(file[self.data_path]
- [: , ymin:ymax , :] )
+ data = projections[: , ymin:ymax , :]
except:
print("Error reading nexus file")
diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py
index 680b268..ed95c3f 100755
--- a/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py
+++ b/Wrappers/Python/ccpi/optimisation/algorithms/Algorithm.py
@@ -140,17 +140,18 @@ class Algorithm(object):
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=False, callback=None):
+ 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:
+ if verbose and self.iteration % self.update_objective_interval == 0:
print ("Iteration {}/{}, objective {}".format(self.iteration,
self.max_iteration, self.get_last_objective()) )
- if callback is not None:
- callback(self.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/ccpi/optimisation/algorithms/CGLS.py b/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py
index 7194eb8..e65bc89 100755
--- a/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py
+++ b/Wrappers/Python/ccpi/optimisation/algorithms/CGLS.py
@@ -23,7 +23,6 @@ Created on Thu Feb 21 11:11:23 2019
"""
from ccpi.optimisation.algorithms import Algorithm
-#from collections.abc import Iterable
class CGLS(Algorithm):
'''Conjugate Gradient Least Squares algorithm
@@ -84,4 +83,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 798fb61..aa07359 100644
--- a/Wrappers/Python/ccpi/optimisation/algorithms/FBPD.py
+++ b/Wrappers/Python/ccpi/optimisation/algorithms/FBPD.py
@@ -23,7 +23,7 @@ Created on Thu Feb 21 11:09:03 2019
"""
from ccpi.optimisation.algorithms import Algorithm
-from ccpi.optimisation.funcs import ZeroFun
+from ccpi.optimisation.functions import ZeroFunction
class FBPD(Algorithm):
'''FBPD Algorithm
diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py
index bc4489e..8ea2b6c 100755
--- a/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py
+++ b/Wrappers/Python/ccpi/optimisation/algorithms/FISTA.py
@@ -6,7 +6,7 @@ Created on Thu Feb 21 11:07:30 2019
"""
from ccpi.optimisation.algorithms import Algorithm
-from ccpi.optimisation.funcs import ZeroFun
+from ccpi.optimisation.functions import ZeroFunction
import numpy
class FISTA(Algorithm):
@@ -46,11 +46,11 @@ class FISTA(Algorithm):
# default inputs
if f is None:
- self.f = ZeroFun()
+ self.f = ZeroFunction()
else:
self.f = f
if g is None:
- g = ZeroFun()
+ g = ZeroFunction()
self.g = g
else:
self.g = g
@@ -106,9 +106,9 @@ class FISTA(Algorithm):
else:
- u = self.y - self.invL*self.f.grad(self.y)
+ u = self.y - self.invL*self.f.gradient(self.y)
- self.x = self.g.prox(u,self.invL)
+ self.x = self.g.proximal(u,self.invL)
self.t = 0.5*(1 + numpy.sqrt(1 + 4*(self.t_old**2)))
diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/GradientDescent.py b/Wrappers/Python/ccpi/optimisation/algorithms/GradientDescent.py
index 7794b4d..14763c5 100755
--- a/Wrappers/Python/ccpi/optimisation/algorithms/GradientDescent.py
+++ b/Wrappers/Python/ccpi/optimisation/algorithms/GradientDescent.py
@@ -51,13 +51,17 @@ class GradientDescent(Algorithm):
def set_up(self, x_init, objective_function, rate):
'''initialisation of the algorithm'''
self.x = x_init.copy()
- if self.memopt:
- self.x_update = 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:
@@ -65,8 +69,8 @@ class GradientDescent(Algorithm):
self.x_update *= -self.rate
self.x += self.x_update
else:
- self.x += -self.rate * self.objective_function.grad(self.x)
+ self.x += -self.rate * self.objective_function.gradient(self.x)
def update_objective(self):
self.loss.append(self.objective_function(self.x))
- \ No newline at end of file
+
diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py
new file mode 100644
index 0000000..a165e55
--- /dev/null
+++ b/Wrappers/Python/ccpi/optimisation/algorithms/PDHG.py
@@ -0,0 +1,214 @@
+#!/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%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:
+
+ 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%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()
+
+ return x, t_end - t, primal, dual, pdgap
+
+
+
diff --git a/Wrappers/Python/ccpi/optimisation/algorithms/__init__.py b/Wrappers/Python/ccpi/optimisation/algorithms/__init__.py
index 903bc30..f562973 100644
--- a/Wrappers/Python/ccpi/optimisation/algorithms/__init__.py
+++ b/Wrappers/Python/ccpi/optimisation/algorithms/__init__.py
@@ -27,3 +27,6 @@ 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/ccpi/optimisation/algs.py b/Wrappers/Python/ccpi/optimisation/algs.py
index 15638a9..2f819d3 100755
--- a/Wrappers/Python/ccpi/optimisation/algs.py
+++ b/Wrappers/Python/ccpi/optimisation/algs.py
@@ -20,8 +20,8 @@
import numpy
import time
-from ccpi.optimisation.funcs import Function
-from ccpi.optimisation.funcs import ZeroFun
+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
@@ -72,7 +72,7 @@ def FISTA(x_init, f=None, g=None, opt=None):
t_old = 1
- c = f(x_init) + g(x_init)
+# c = f(x_init) + g(x_init)
# algorithm loop
for it in range(0, max_iter):
@@ -99,9 +99,9 @@ def FISTA(x_init, f=None, g=None, opt=None):
else:
- u = y - invL*f.grad(y)
+ u = y - invL*f.gradient(y)
- x = g.prox(u,invL)
+ x = g.proximal(u,invL)
t = 0.5*(1 + numpy.sqrt(1 + 4*(t_old**2)))
@@ -111,8 +111,8 @@ def FISTA(x_init, f=None, g=None, opt=None):
t_old = t
# time and criterion
- timing[it] = time.time() - time0
- criter[it] = f(x) + g(x);
+# 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:
@@ -121,9 +121,9 @@ def FISTA(x_init, f=None, g=None, opt=None):
#print(it, 'out of', 10, 'iterations', end='\r');
#criter = criter[0:it+1];
- timing = numpy.cumsum(timing[0:it+1]);
+# timing = numpy.cumsum(timing[0:it+1]);
- return x, it, timing, criter
+ return x #, it, timing, criter
def FBPD(x_init, operator=None, constraint=None, data_fidelity=None,\
regulariser=None, opt=None):
diff --git a/Wrappers/Python/ccpi/optimisation/funcs.py b/Wrappers/Python/ccpi/optimisation/funcs.py
index 47ee810..efc465c 100755
--- a/Wrappers/Python/ccpi/optimisation/funcs.py
+++ b/Wrappers/Python/ccpi/optimisation/funcs.py
@@ -20,8 +20,8 @@
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):
@@ -35,17 +35,6 @@ def isSizeCorrect(data1 ,data2):
raise ValueError("{0}: getting two incompatible types: {1} {2}"\
.format('Function', type(data1), type(data2)))
return False
-
-class Function(object):
- def __init__(self):
- self.L = None
- def __call__(self,x, out=None): raise NotImplementedError
- def grad(self, x): raise NotImplementedError
- def prox(self, x, tau): raise NotImplementedError
- def gradient(self, x, out=None): raise NotImplementedError
- def proximal(self, x, tau, out=None): raise NotImplementedError
-
-
class Norm2(Function):
def __init__(self,
@@ -141,24 +130,33 @@ class Norm2sq(Function):
self.A = A # Should be an operator, default identity
self.b = b # Default zero DataSet?
self.c = c # Default 1.
- self.memopt = memopt
if memopt:
- #self.direct_placehold = A.adjoint(b)
- self.direct_placehold = A.allocate_direct()
- self.adjoint_placehold = A.allocate_adjoint()
-
+ 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.get_max_sing_val()**2)
+ 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 2*self.c*self.A.adjoint( self.A.direct(x) - self.b )
- return (2.0*self.c)*self.A.adjoint( self.A.direct(x) - self.b )
-
+ #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:
@@ -178,44 +176,58 @@ class Norm2sq(Function):
if self.memopt:
#return 2.0*self.c*self.A.adjoint( self.A.direct(x) - self.b )
- self.A.direct(x, out=self.adjoint_placehold)
- self.adjoint_placehold.__isub__( self.b )
- self.A.adjoint(self.adjoint_placehold, out=self.direct_placehold)
- self.direct_placehold.__imul__(2.0 * self.c)
- # can this be avoided?
- out.fill(self.direct_placehold)
+ 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 self.grad(x)
-
+ return (2.0*self.c)*self.A.adjoint( self.A.direct(x) - self.b )
+
-class ZeroFun(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.
+class IndicatorBox(Function):
- def __init__(self,gamma=0,L=1):
- self.gamma = gamma
- self.L = L
- super(ZeroFun, self).__init__()
+ 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):
- return 0
+
+ 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):
- return x.copy()
+ 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 isSizeCorrect(out, x):
- # check dimensionality
- if issubclass(type(out), DataContainer):
- out.fill(x)
-
- elif issubclass(type(out) , numpy.ndarray):
- out[:] = x
- else:
- raise ValueError ('Wrong size: x{0} out{1}'
- .format(x.shape,out.shape) )
+ 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
@@ -258,45 +270,3 @@ class Norm1(Function):
#out[:] = self.prox(x,tau)
else:
raise ValueError ('Wrong size: x{0} out{1}'.format(x.shape,out.shape) )
-
-# 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
- self.lower = lower
- self.upper = upper
- super(IndicatorBox, self).__init__()
-
- 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/ccpi/optimisation/functions/BlockFunction.py b/Wrappers/Python/ccpi/optimisation/functions/BlockFunction.py
new file mode 100644
index 0000000..bf627a5
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/functions/Function.py b/Wrappers/Python/ccpi/optimisation/functions/Function.py
new file mode 100644
index 0000000..ba33666
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/functions/FunctionOperatorComposition.py b/Wrappers/Python/ccpi/optimisation/functions/FunctionOperatorComposition.py
new file mode 100644
index 0000000..70511bb
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/functions/IndicatorBox.py b/Wrappers/Python/ccpi/optimisation/functions/IndicatorBox.py
new file mode 100755
index 0000000..df8dc89
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/functions/KullbackLeibler.py b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py
new file mode 100644
index 0000000..40dddd7
--- /dev/null
+++ b/Wrappers/Python/ccpi/optimisation/functions/KullbackLeibler.py
@@ -0,0 +1,123 @@
+# -*- 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
+
+ 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()))
+
+
+ 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.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):
+
+ 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__':
+
+ 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)))
+
+ f = KullbackLeibler(data, bnoise=bnoise)
+ print(f.sum_value)
+
+ print(f(x))
+
+
+
+ \ No newline at end of file
diff --git a/Wrappers/Python/ccpi/optimisation/functions/L1Norm.py b/Wrappers/Python/ccpi/optimisation/functions/L1Norm.py
new file mode 100644
index 0000000..4e53f2c
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/functions/L2NormSquared.py b/Wrappers/Python/ccpi/optimisation/functions/L2NormSquared.py
new file mode 100644
index 0000000..6d3bf86
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/functions/MixedL21Norm.py b/Wrappers/Python/ccpi/optimisation/functions/MixedL21Norm.py
new file mode 100755
index 0000000..2004e5f
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/functions/Norm2Sq.py b/Wrappers/Python/ccpi/optimisation/functions/Norm2Sq.py
new file mode 100755
index 0000000..b553d7c
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/functions/ScaledFunction.py b/Wrappers/Python/ccpi/optimisation/functions/ScaledFunction.py
new file mode 100755
index 0000000..7caeab2
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/functions/ZeroFunction.py b/Wrappers/Python/ccpi/optimisation/functions/ZeroFunction.py
new file mode 100644
index 0000000..cce519a
--- /dev/null
+++ b/Wrappers/Python/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 <x, x^{*}> 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/ccpi/optimisation/functions/__init__.py b/Wrappers/Python/ccpi/optimisation/functions/__init__.py
new file mode 100644
index 0000000..a82ee3e
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/BlockOperator.py b/Wrappers/Python/ccpi/optimisation/operators/BlockOperator.py
new file mode 100755
index 0000000..1d77510
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/BlockScaledOperator.py b/Wrappers/Python/ccpi/optimisation/operators/BlockScaledOperator.py
new file mode 100644
index 0000000..aeb6c53
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/FiniteDifferenceOperator.py b/Wrappers/Python/ccpi/optimisation/operators/FiniteDifferenceOperator.py
new file mode 100644
index 0000000..835f96d
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py b/Wrappers/Python/ccpi/optimisation/operators/FiniteDifferenceOperator_old.py
new file mode 100644
index 0000000..387fb4b
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/GradientOperator.py b/Wrappers/Python/ccpi/optimisation/operators/GradientOperator.py
new file mode 100644
index 0000000..4935435
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/IdentityOperator.py b/Wrappers/Python/ccpi/optimisation/operators/IdentityOperator.py
new file mode 100644
index 0000000..a58a296
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/LinearOperator.py b/Wrappers/Python/ccpi/optimisation/operators/LinearOperator.py
new file mode 100755
index 0000000..e19304f
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/Operator.py b/Wrappers/Python/ccpi/optimisation/operators/Operator.py
new file mode 100755
index 0000000..2d2089b
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/ScaledOperator.py b/Wrappers/Python/ccpi/optimisation/operators/ScaledOperator.py
new file mode 100644
index 0000000..ba0049e
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/ShrinkageOperator.py b/Wrappers/Python/ccpi/optimisation/operators/ShrinkageOperator.py
new file mode 100644
index 0000000..f47c655
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/SparseFiniteDiff.py b/Wrappers/Python/ccpi/optimisation/operators/SparseFiniteDiff.py
new file mode 100644
index 0000000..5e318ff
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/SymmetrizedGradientOperator.py b/Wrappers/Python/ccpi/optimisation/operators/SymmetrizedGradientOperator.py
new file mode 100644
index 0000000..c38458d
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/ZeroOperator.py b/Wrappers/Python/ccpi/optimisation/operators/ZeroOperator.py
new file mode 100644
index 0000000..a7c5f09
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/operators/__init__.py b/Wrappers/Python/ccpi/optimisation/operators/__init__.py
new file mode 100755
index 0000000..7040d3a
--- /dev/null
+++ b/Wrappers/Python/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/ccpi/optimisation/ops.py b/Wrappers/Python/ccpi/optimisation/ops.py
index e9e7f44..fcd0d9e 100755
--- a/Wrappers/Python/ccpi/optimisation/ops.py
+++ b/Wrappers/Python/ccpi/optimisation/ops.py
@@ -49,9 +49,9 @@ class Operator(object):
def allocate_adjoint(self):
'''Allocates memory on the X space'''
raise NotImplementedError
- def range_dim(self):
+ def range_geometry(self):
raise NotImplementedError
- def domain_dim(self):
+ def domain_geometry(self):
raise NotImplementedError
def __rmul__(self, other):
'''reverse multiplication of Operator with number sets the variable scalar in the Operator'''
@@ -97,7 +97,8 @@ class TomoIdentity(Operator):
self.s1 = 1.0
self.geometry = geometry
-
+ def is_linear(self):
+ return True
def direct(self,x,out=None):
if out is None:
@@ -114,8 +115,8 @@ class TomoIdentity(Operator):
def adjoint(self,x, out=None):
return self.direct(x, out)
- def size(self):
- return NotImplemented
+ def norm(self):
+ return self.s1
def get_max_sing_val(self):
return self.s1
@@ -128,6 +129,10 @@ class TomoIdentity(Operator):
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
diff --git a/Wrappers/Python/ccpi/processors.py b/Wrappers/Python/ccpi/processors.py
index 3a3671a..ccef410 100755
--- a/Wrappers/Python/ccpi/processors.py
+++ b/Wrappers/Python/ccpi/processors.py
@@ -1,514 +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)
+# -*- 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/conda-recipe/conda_build_config.yaml b/Wrappers/Python/conda-recipe/conda_build_config.yaml
index 96a211f..30c8e9d 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.12
+ - 1.12
- 1.15
diff --git a/Wrappers/Python/conda-recipe/meta.yaml b/Wrappers/Python/conda-recipe/meta.yaml
index 8ded429..dd3238e 100644
--- a/Wrappers/Python/conda-recipe/meta.yaml
+++ b/Wrappers/Python/conda-recipe/meta.yaml
@@ -11,7 +11,7 @@ build:
test:
requires:
- python-wget
- - cvxpy # [not win]
+ - cvxpy # [ unix and py36 and np115 ]
source_files:
- ./test # [win]
@@ -24,8 +24,9 @@ test:
requirements:
build:
+ - {{ pin_compatible('numpy', max_pin='x.x') }}
- python
- - numpy {{ numpy }}
+ - numpy
- setuptools
run:
@@ -33,7 +34,7 @@ requirements:
- python
- numpy
- scipy
- - matplotlib
+ #- matplotlib
- h5py
about:
diff --git a/Wrappers/Python/setup.py b/Wrappers/Python/setup.py
index eaf124b..87930b5 100644
--- a/Wrappers/Python/setup.py
+++ b/Wrappers/Python/setup.py
@@ -31,8 +31,11 @@ if cil_version == '':
setup(
name="ccpi-framework",
version=cil_version,
- packages=['ccpi' , 'ccpi.io', 'ccpi.optimisation',
- 'ccpi.optimisation.algorithms'],
+ packages=['ccpi' , 'ccpi.io',
+ 'ccpi.framework', 'ccpi.optimisation',
+ 'ccpi.optimisation.operators',
+ 'ccpi.optimisation.algorithms',
+ 'ccpi.optimisation.functions'],
# Project uses reStructuredText, so ensure that the docutils get
# installed or upgraded on the target machine
diff --git a/Wrappers/Python/test/test_BlockDataContainer.py b/Wrappers/Python/test/test_BlockDataContainer.py
new file mode 100755
index 0000000..a20f289
--- /dev/null
+++ b/Wrappers/Python/test/test_BlockDataContainer.py
@@ -0,0 +1,474 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Tue Mar 5 16:08:23 2019
+
+@author: ofn77899
+"""
+
+import unittest
+import numpy
+#from ccpi.plugins.ops import CCPiProjectorSimple
+from ccpi.optimisation.ops import PowerMethodNonsquare
+from ccpi.optimisation.ops import TomoIdentity
+from ccpi.optimisation.funcs import Norm2sq, Norm1
+from ccpi.framework import ImageGeometry, AcquisitionGeometry
+from ccpi.framework import ImageData, AcquisitionData
+#from ccpi.optimisation.algorithms import GradientDescent
+from ccpi.framework import BlockDataContainer, DataContainer
+#from ccpi.optimisation.Algorithms import CGLS
+import functools
+
+from ccpi.optimisation.operators import Gradient, Identity, BlockOperator
+
+class TestBlockDataContainer(unittest.TestCase):
+ def skiptest_BlockDataContainerShape(self):
+ print ("test block data container")
+ ig0 = ImageGeometry(12,42,55,32)
+ ig1 = ImageGeometry(12,42,55,32)
+
+ data0 = ImageData(geometry=ig0)
+ data1 = ImageData(geometry=ig1) + 1
+
+ data2 = ImageData(geometry=ig0) + 2
+ data3 = ImageData(geometry=ig1) + 3
+
+ cp0 = BlockDataContainer(data0,data1)
+ cp1 = BlockDataContainer(data2,data3)
+ transpose_shape = (cp0.shape[1], cp0.shape[0])
+ self.assertTrue(cp0.T.shape == transpose_shape)
+ def skiptest_BlockDataContainerShapeArithmetic(self):
+ print ("test block data container")
+ ig0 = ImageGeometry(2,3,4)
+ ig1 = ImageGeometry(2,3,4)
+
+ data0 = ImageData(geometry=ig0)
+ data1 = ImageData(geometry=ig1) + 1
+
+ data2 = ImageData(geometry=ig0) + 2
+ data3 = ImageData(geometry=ig1) + 3
+
+ cp0 = BlockDataContainer(data0,data1)
+ #cp1 = BlockDataContainer(data2,data3)
+ cp1 = cp0 + 1
+ self.assertTrue(cp1.shape == cp0.shape)
+ cp1 = cp0.T + 1
+
+ transpose_shape = (cp0.shape[1], cp0.shape[0])
+ self.assertTrue(cp1.shape == transpose_shape)
+
+ cp1 = cp0.T - 1
+ transpose_shape = (cp0.shape[1], cp0.shape[0])
+ self.assertTrue(cp1.shape == transpose_shape)
+
+ cp1 = (cp0.T + 1)*2
+ transpose_shape = (cp0.shape[1], cp0.shape[0])
+ self.assertTrue(cp1.shape == transpose_shape)
+
+ cp1 = (cp0.T + 1)/2
+ transpose_shape = (cp0.shape[1], cp0.shape[0])
+ self.assertTrue(cp1.shape == transpose_shape)
+
+ cp1 = cp0.T.power(2.2)
+ transpose_shape = (cp0.shape[1], cp0.shape[0])
+ self.assertTrue(cp1.shape == transpose_shape)
+
+ cp1 = cp0.T.maximum(3)
+ transpose_shape = (cp0.shape[1], cp0.shape[0])
+ self.assertTrue(cp1.shape == transpose_shape)
+
+ cp1 = cp0.T.abs()
+ transpose_shape = (cp0.shape[1], cp0.shape[0])
+ self.assertTrue(cp1.shape == transpose_shape)
+
+ cp1 = cp0.T.sign()
+ transpose_shape = (cp0.shape[1], cp0.shape[0])
+ self.assertTrue(cp1.shape == transpose_shape)
+
+ cp1 = cp0.T.sqrt()
+ transpose_shape = (cp0.shape[1], cp0.shape[0])
+ self.assertTrue(cp1.shape == transpose_shape)
+
+ cp1 = cp0.T.conjugate()
+ transpose_shape = (cp0.shape[1], cp0.shape[0])
+ self.assertTrue(cp1.shape == transpose_shape)
+
+ def test_BlockDataContainer(self):
+ print ("test block data container")
+ ig0 = ImageGeometry(2,3,4)
+ ig1 = ImageGeometry(2,3,5)
+
+ data0 = ImageData(geometry=ig0)
+ data1 = ImageData(geometry=ig1) + 1
+
+ data2 = ImageData(geometry=ig0) + 2
+ data3 = ImageData(geometry=ig1) + 3
+
+ cp0 = BlockDataContainer(data0,data1)
+ cp1 = BlockDataContainer(data2,data3)
+
+ cp2 = BlockDataContainer(data0+1, data2+1)
+ d = cp2 + data0
+ self.assertEqual(d.get_item(0).as_array()[0][0][0], 1)
+ try:
+ d = cp2 + data1
+ self.assertTrue(False)
+ except ValueError as ve:
+ print (ve)
+ self.assertTrue(True)
+ d = cp2 - data0
+ self.assertEqual(d.get_item(0).as_array()[0][0][0], 1)
+ try:
+ d = cp2 - data1
+ self.assertTrue(False)
+ except ValueError as ve:
+ print (ve)
+ self.assertTrue(True)
+ d = cp2 * data2
+ self.assertEqual(d.get_item(0).as_array()[0][0][0], 2)
+ try:
+ d = cp2 * data1
+ self.assertTrue(False)
+ except ValueError as ve:
+ print (ve)
+ self.assertTrue(True)
+
+ a = [ (el, ot) for el,ot in zip(cp0.containers,cp1.containers)]
+ print (a[0][0].shape)
+ #cp2 = BlockDataContainer(*a)
+ cp2 = cp0.add(cp1)
+ self.assertEqual (cp2.get_item(0).as_array()[0][0][0] , 2.)
+ self.assertEqual (cp2.get_item(1).as_array()[0][0][0] , 4.)
+
+ cp2 = cp0 + cp1
+ self.assertTrue (cp2.get_item(0).as_array()[0][0][0] == 2.)
+ self.assertTrue (cp2.get_item(1).as_array()[0][0][0] == 4.)
+ cp2 = cp0 + 1
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 1. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 2., decimal = 5)
+ cp2 = cp0 + [1 ,2]
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 1. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 3., decimal = 5)
+ cp2 += cp1
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , +3. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , +6., decimal = 5)
+
+ cp2 += 1
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , +4. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , +7., decimal = 5)
+
+ cp2 += [-2,-1]
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 2. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 6., decimal = 5)
+
+
+ cp2 = cp0.subtract(cp1)
+ assert (cp2.get_item(0).as_array()[0][0][0] == -2.)
+ assert (cp2.get_item(1).as_array()[0][0][0] == -2.)
+ cp2 = cp0 - cp1
+ assert (cp2.get_item(0).as_array()[0][0][0] == -2.)
+ assert (cp2.get_item(1).as_array()[0][0][0] == -2.)
+
+ cp2 = cp0 - 1
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , -1. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 0, decimal = 5)
+ cp2 = cp0 - [1 ,2]
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , -1. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , -1., decimal = 5)
+
+ cp2 -= cp1
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , -3. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , -4., decimal = 5)
+
+ cp2 -= 1
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , -4. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , -5., decimal = 5)
+
+ cp2 -= [-2,-1]
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , -2. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , -4., decimal = 5)
+
+
+ cp2 = cp0.multiply(cp1)
+ assert (cp2.get_item(0).as_array()[0][0][0] == 0.)
+ assert (cp2.get_item(1).as_array()[0][0][0] == 3.)
+ cp2 = cp0 * cp1
+ assert (cp2.get_item(0).as_array()[0][0][0] == 0.)
+ assert (cp2.get_item(1).as_array()[0][0][0] == 3.)
+
+ cp2 = cp0 * 2
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 2, decimal = 5)
+ cp2 = 2 * cp0
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 2, decimal = 5)
+ cp2 = cp0 * [3 ,2]
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 2., decimal = 5)
+ cp2 = cp0 * numpy.asarray([3 ,2])
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 2., decimal = 5)
+
+ cp2 = [3,2] * cp0
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 2., decimal = 5)
+ cp2 = numpy.asarray([3,2]) * cp0
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 2., decimal = 5)
+
+ try:
+ cp2 = [3,2,3] * cp0
+ #numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ #numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 2., decimal = 5)
+ self.assertTrue(False)
+ except ValueError as ve:
+ print (ve)
+ self.assertTrue(True)
+ cp2 *= cp1
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0 , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , +6., decimal = 5)
+
+ cp2 *= 1
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , +6., decimal = 5)
+
+ cp2 *= [-2,-1]
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , -6., decimal = 5)
+
+ try:
+ cp2 *= [2,3,5]
+ self.assertTrue(False)
+ except ValueError as ve:
+ print (ve)
+ self.assertTrue(True)
+
+ cp2 = cp0.divide(cp1)
+ assert (cp2.get_item(0).as_array()[0][0][0] == 0.)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0], 1./3., decimal=4)
+ cp2 = cp0/cp1
+ assert (cp2.get_item(0).as_array()[0][0][0] == 0.)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0], 1./3., decimal=4)
+
+ cp2 = cp0 / 2
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 0.5, decimal = 5)
+ cp2 = cp0 / [3 ,2]
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 0.5, decimal = 5)
+ cp2 = cp0 / numpy.asarray([3 ,2])
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 0.5, decimal = 5)
+ cp3 = numpy.asarray([3 ,2]) / (cp0+1)
+ numpy.testing.assert_almost_equal(cp3.get_item(0).as_array()[0][0][0] , 3. , decimal=5)
+ numpy.testing.assert_almost_equal(cp3.get_item(1).as_array()[0][0][0] , 1, decimal = 5)
+
+ cp2 += 1
+ cp2 /= cp1
+ # TODO fix inplace division
+
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 1./2 , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 1.5/3., decimal = 5)
+
+ cp2 /= 1
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0.5 , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 0.5, decimal = 5)
+
+ cp2 /= [-2,-1]
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , -0.5/2. , decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , -0.5, decimal = 5)
+ ####
+
+ cp2 = cp0.power(cp1)
+ assert (cp2.get_item(0).as_array()[0][0][0] == 0.)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0], 1., decimal=4)
+ cp2 = cp0**cp1
+ assert (cp2.get_item(0).as_array()[0][0][0] == 0.)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0], 1., decimal=4)
+
+ cp2 = cp0 ** 2
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0] , 0., decimal=5)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0] , 1., decimal = 5)
+
+ cp2 = cp0.maximum(cp1)
+ assert (cp2.get_item(0).as_array()[0][0][0] == cp1.get_item(0).as_array()[0][0][0])
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0], cp2.get_item(1).as_array()[0][0][0], decimal=4)
+
+
+ cp2 = cp0.abs()
+ numpy.testing.assert_almost_equal(cp2.get_item(0).as_array()[0][0][0], 0., decimal=4)
+ numpy.testing.assert_almost_equal(cp2.get_item(1).as_array()[0][0][0], 1., decimal=4)
+
+ cp2 = cp0.subtract(cp1)
+ s = cp2.sign()
+ numpy.testing.assert_almost_equal(s.get_item(0).as_array()[0][0][0], -1., decimal=4)
+ numpy.testing.assert_almost_equal(s.get_item(1).as_array()[0][0][0], -1., decimal=4)
+
+ cp2 = cp0.add(cp1)
+ s = cp2.sqrt()
+ numpy.testing.assert_almost_equal(s.get_item(0).as_array()[0][0][0], numpy.sqrt(2), decimal=4)
+ numpy.testing.assert_almost_equal(s.get_item(1).as_array()[0][0][0], numpy.sqrt(4), decimal=4)
+
+ s = cp0.sum()
+ size = functools.reduce(lambda x,y: x*y, data1.shape, 1)
+ print ("size" , size)
+ numpy.testing.assert_almost_equal(s, 0 + size, decimal=4)
+ s0 = 1
+ s1 = 1
+ for i in cp0.get_item(0).shape:
+ s0 *= i
+ for i in cp0.get_item(1).shape:
+ s1 *= i
+
+ #numpy.testing.assert_almost_equal(s[1], cp0.get_item(0,0).as_array()[0][0][0]*s0 +cp0.get_item(1,0).as_array()[0][0][0]*s1, decimal=4)
+ def test_Nested_BlockDataContainer(self):
+ print ("test_Nested_BlockDataContainer")
+ ig0 = ImageGeometry(2,3,4)
+ ig1 = ImageGeometry(2,3,4)
+
+ data0 = ImageData(geometry=ig0)
+ data1 = ImageData(geometry=ig1) + 1
+
+ data2 = ImageData(geometry=ig0) + 2
+ data3 = ImageData(geometry=ig1) + 3
+
+ cp0 = BlockDataContainer(data0,data1)
+ cp1 = BlockDataContainer(data2,data3)
+
+ nbdc = BlockDataContainer(cp0, cp1)
+ nbdc2 = nbdc + 2
+ numpy.testing.assert_almost_equal(nbdc2.get_item(0).get_item(0).as_array()[0][0][0] , 2. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(0).get_item(1).as_array()[0][0][0] , 3. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(1).get_item(0).as_array()[0][0][0] , 4. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(1).get_item(1).as_array()[0][0][0] , 5. , decimal=5)
+
+ nbdc2 = 2 + nbdc
+ numpy.testing.assert_almost_equal(nbdc2.get_item(0).get_item(0).as_array()[0][0][0] , 2. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(0).get_item(1).as_array()[0][0][0] , 3. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(1).get_item(0).as_array()[0][0][0] , 4. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(1).get_item(1).as_array()[0][0][0] , 5. , decimal=5)
+
+
+ nbdc2 = nbdc * 2
+ numpy.testing.assert_almost_equal(nbdc2.get_item(0).get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(0).get_item(1).as_array()[0][0][0] , 2. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(1).get_item(0).as_array()[0][0][0] , 4. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(1).get_item(1).as_array()[0][0][0] , 6. , decimal=5)
+
+ nbdc2 = 2 * nbdc
+ numpy.testing.assert_almost_equal(nbdc2.get_item(0).get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(0).get_item(1).as_array()[0][0][0] , 2. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(1).get_item(0).as_array()[0][0][0] , 4. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(1).get_item(1).as_array()[0][0][0] , 6. , decimal=5)
+
+ nbdc2 = nbdc / 2
+ numpy.testing.assert_almost_equal(nbdc2.get_item(0).get_item(0).as_array()[0][0][0] , 0. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(0).get_item(1).as_array()[0][0][0] , .5 , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(1).get_item(0).as_array()[0][0][0] , 1. , decimal=5)
+ numpy.testing.assert_almost_equal(nbdc2.get_item(1).get_item(1).as_array()[0][0][0] , 3./2 , decimal=5)
+
+ c5 = nbdc.get_item(0).power(2).sum()
+ c5a = nbdc.power(2).sum()
+ print ("sum", c5a, c5)
+
+ cp0 = BlockDataContainer(data0,data2)
+ a = cp0 * data2
+ b = data2 * cp0
+ self.assertBlockDataContainerEqual(a,b)
+
+
+ print ("test_Nested_BlockDataContainer OK")
+ def stest_NestedBlockDataContainer2(self):
+ M, N = 2, 3
+ ig = ImageGeometry(voxel_num_x = M, voxel_num_y = N)
+ ag = ig
+ u = ig.allocate(1)
+ op1 = Gradient(ig)
+ op2 = Identity(ig, ag)
+
+ operator = BlockOperator(op1, op2, shape=(2,1))
+
+ d1 = op1.direct(u)
+ d2 = op2.direct(u)
+
+ d = operator.direct(u)
+
+ dd = operator.domain_geometry()
+ ww = operator.range_geometry()
+
+ print(d.get_item(0).get_item(0).as_array())
+ print(d.get_item(0).get_item(1).as_array())
+ print(d.get_item(1).as_array())
+
+ c1 = d + d
+
+ c2 = 2*d
+
+ c3 = d / (d+0.0001)
+
+
+ c5 = d.get_item(0).power(2).sum()
+
+ def test_BlockDataContainer_fill(self):
+ print ("test block data container")
+ ig0 = ImageGeometry(2,3,4)
+ ig1 = ImageGeometry(2,3,5)
+
+ data0 = ImageData(geometry=ig0)
+ data1 = ImageData(geometry=ig1) + 1
+
+ data2 = ImageData(geometry=ig0) + 2
+ data3 = ImageData(geometry=ig1) + 3
+
+ cp0 = BlockDataContainer(data0,data1)
+ #cp1 = BlockDataContainer(data2,data3)
+
+ cp2 = BlockDataContainer(data0+1, data1+1)
+
+ data0.fill(data2)
+ self.assertNumpyArrayEqual(data0.as_array(), data2.as_array())
+ data0 = ImageData(geometry=ig0)
+
+ for el,ot in zip(cp0, cp2):
+ print (el.shape, ot.shape)
+ cp0.fill(cp2)
+ self.assertBlockDataContainerEqual(cp0, cp2)
+
+ def test_NestedBlockDataContainer(self):
+ ig0 = ImageGeometry(2,3,4)
+ ig1 = ImageGeometry(2,3,5)
+
+ data0 = ig0.allocate(0)
+ data2 = ig0.allocate(1)
+
+ cp0 = BlockDataContainer(data0,data2)
+ #cp1 = BlockDataContainer(data2,data3)
+
+ nested = BlockDataContainer(cp0, data2, data2)
+ out = BlockDataContainer(BlockDataContainer(data0 , data0), data0, data0)
+ nested.divide(data2,out=out)
+ self.assertBlockDataContainerEqual(out, nested)
+
+
+ def assertBlockDataContainerEqual(self, container1, container2):
+ print ("assert Block Data Container Equal")
+ self.assertTrue(issubclass(container1.__class__, container2.__class__))
+ for col in range(container1.shape[0]):
+ if issubclass(container1.get_item(col).__class__, DataContainer):
+ print ("Checking col ", col)
+ self.assertNumpyArrayEqual(
+ container1.get_item(col).as_array(),
+ container2.get_item(col).as_array()
+ )
+ else:
+ self.assertBlockDataContainerEqual(container1.get_item(col),container2.get_item(col))
+
+ def assertNumpyArrayEqual(self, first, second):
+ res = True
+ try:
+ numpy.testing.assert_array_equal(first, second)
+ except AssertionError as err:
+ res = False
+ print(err)
+ self.assertTrue(res)
+
+
diff --git a/Wrappers/Python/test/test_BlockOperator.py b/Wrappers/Python/test/test_BlockOperator.py
new file mode 100644
index 0000000..e1c05fb
--- /dev/null
+++ b/Wrappers/Python/test/test_BlockOperator.py
@@ -0,0 +1,365 @@
+import unittest
+from ccpi.optimisation.operators import BlockOperator
+from ccpi.framework import BlockDataContainer
+from ccpi.optimisation.ops import TomoIdentity
+from ccpi.framework import ImageGeometry, ImageData
+import numpy
+from ccpi.optimisation.operators import FiniteDiff
+
+class TestOperator(TomoIdentity):
+ def __init__(self, *args, **kwargs):
+ super(TestOperator, self).__init__(*args, **kwargs)
+ self.range = kwargs.get('range', self.geometry)
+ def range_geometry(self):
+ return self.range
+
+class TestBlockOperator(unittest.TestCase):
+
+ def test_BlockOperator(self):
+ print ("test_BlockOperator")
+ ig = [ ImageGeometry(10,20,30) , \
+ ImageGeometry(10,20,30) , \
+ ImageGeometry(10,20,30) ]
+ x = [ g.allocate() for g in ig ]
+ ops = [ TomoIdentity(g) for g in ig ]
+
+ K = BlockOperator(*ops)
+ X = BlockDataContainer(x[0])
+ Y = K.direct(X)
+ self.assertTrue(Y.shape == K.shape)
+
+ numpy.testing.assert_array_equal(Y.get_item(0).as_array(),X.get_item(0).as_array())
+ numpy.testing.assert_array_equal(Y.get_item(1).as_array(),X.get_item(0).as_array())
+ #numpy.testing.assert_array_equal(Y.get_item(2).as_array(),X.get_item(2).as_array())
+
+ X = BlockDataContainer(*x) + 1
+ Y = K.T.direct(X)
+ # K.T (1,3) X (3,1) => output shape (1,1)
+ self.assertTrue(Y.shape == (1,1))
+ zero = numpy.zeros(X.get_item(0).shape)
+ numpy.testing.assert_array_equal(Y.get_item(0).as_array(),len(x)+zero)
+
+ K2 = BlockOperator(*(ops+ops), shape=(3,2))
+ Y = K2.T.direct(X)
+ # K.T (2,3) X (3,1) => output shape (2,1)
+ self.assertTrue(Y.shape == (2,1))
+
+ try:
+ # this should fail as the domain is not compatible
+ ig = [ ImageGeometry(10,20,31) , \
+ ImageGeometry(10,20,30) , \
+ ImageGeometry(10,20,30) ]
+ x = [ g.allocate() for g in ig ]
+ ops = [ TomoIdentity(g) for g in ig ]
+
+ K = BlockOperator(*ops)
+ self.assertTrue(False)
+ except ValueError as ve:
+ print (ve)
+ self.assertTrue(True)
+
+ try:
+ # this should fail as the range is not compatible
+ ig = [ ImageGeometry(10,20,30) , \
+ ImageGeometry(10,20,30) , \
+ ImageGeometry(10,20,30) ]
+ rg0 = [ ImageGeometry(10,20,31) , \
+ ImageGeometry(10,20,31) , \
+ ImageGeometry(10,20,31) ]
+ rg1 = [ ImageGeometry(10,22,31) , \
+ ImageGeometry(10,22,31) , \
+ ImageGeometry(10,20,31) ]
+ x = [ g.allocate() for g in ig ]
+ ops = [ TestOperator(g, range=r) for g,r in zip(ig, rg0) ]
+ ops += [ TestOperator(g, range=r) for g,r in zip(ig, rg1) ]
+
+ K = BlockOperator(*ops, shape=(2,3))
+ print ("K col comp? " , K.column_wise_compatible())
+ print ("K row comp? " , K.row_wise_compatible())
+ for op in ops:
+ print ("range" , op.range_geometry().shape)
+ for op in ops:
+ print ("domain" , op.domain_geometry().shape)
+ self.assertTrue(False)
+ except ValueError as ve:
+ print (ve)
+ self.assertTrue(True)
+
+ def test_ScaledBlockOperatorSingleScalar(self):
+ ig = [ ImageGeometry(10,20,30) , \
+ ImageGeometry(10,20,30) , \
+ ImageGeometry(10,20,30) ]
+ x = [ g.allocate() for g in ig ]
+ ops = [ TomoIdentity(g) for g in ig ]
+
+ val = 1
+ # test limit as non Scaled
+ scalar = 1
+ k = BlockOperator(*ops)
+ K = scalar * k
+ X = BlockDataContainer(*x) + val
+
+ Y = K.T.direct(X)
+ self.assertTrue(Y.shape == (1,1))
+ zero = numpy.zeros(X.get_item(0).shape)
+ xx = numpy.asarray([val for _ in x])
+ numpy.testing.assert_array_equal(Y.get_item(0).as_array(),((scalar*xx).sum()+zero))
+
+ scalar = 0.5
+ k = BlockOperator(*ops)
+ K = scalar * k
+ X = BlockDataContainer(*x) + 1
+
+ Y = K.T.direct(X)
+ self.assertTrue(Y.shape == (1,1))
+ zero = numpy.zeros(X.get_item(0).shape)
+ numpy.testing.assert_array_equal(Y.get_item(0).as_array(),scalar*(len(x)+zero))
+
+
+ def test_ScaledBlockOperatorScalarList(self):
+ ig = [ ImageGeometry(2,3) , \
+ #ImageGeometry(10,20,30) , \
+ ImageGeometry(2,3 ) ]
+ x = [ g.allocate() for g in ig ]
+ ops = [ TomoIdentity(g) for g in ig ]
+
+
+ # test limit as non Scaled
+ scalar = numpy.asarray([1 for _ in x])
+ k = BlockOperator(*ops)
+ K = scalar * k
+ val = 1
+ X = BlockDataContainer(*x) + val
+
+ Y = K.T.direct(X)
+ self.assertTrue(Y.shape == (1,1))
+ zero = numpy.zeros(X.get_item(0).shape)
+ xx = numpy.asarray([val for _ in x])
+ numpy.testing.assert_array_equal(Y.get_item(0).as_array(),(scalar*xx).sum()+zero)
+
+ scalar = numpy.asarray([i+1 for i,el in enumerate(x)])
+ #scalar = numpy.asarray([6,0])
+ k = BlockOperator(*ops)
+ K = scalar * k
+ X = BlockDataContainer(*x) + val
+ Y = K.T.direct(X)
+ self.assertTrue(Y.shape == (1,1))
+ zero = numpy.zeros(X.get_item(0).shape)
+ xx = numpy.asarray([val for _ in x])
+
+
+ numpy.testing.assert_array_equal(Y.get_item(0).as_array(),
+ (scalar*xx).sum()+zero)
+
+
+ def test_TomoIdentity(self):
+ ig = ImageGeometry(10,20,30)
+ img = ig.allocate()
+ print (img.shape, ig.shape)
+ self.assertTrue(img.shape == (30,20,10))
+ self.assertEqual(img.sum(), 0)
+ Id = TomoIdentity(ig)
+ y = Id.direct(img)
+ numpy.testing.assert_array_equal(y.as_array(), img.as_array())
+
+ def skiptest_CGLS_tikhonov(self):
+ from ccpi.optimisation.algorithms import CGLS
+
+ from ccpi.plugins.ops import CCPiProjectorSimple
+ from ccpi.optimisation.ops import PowerMethodNonsquare
+ from ccpi.optimisation.ops import TomoIdentity
+ from ccpi.optimisation.funcs import Norm2sq, Norm1
+ from ccpi.framework import ImageGeometry, AcquisitionGeometry
+ from ccpi.optimisation.Algorithms import GradientDescent
+ #from ccpi.optimisation.Algorithms import CGLS
+ import matplotlib.pyplot as plt
+
+
+ # Set up phantom size N x N x vert by creating ImageGeometry, initialising the
+ # ImageData object with this geometry and empty array and finally put some
+ # data into its array, and display one slice as image.
+
+ # Image parameters
+ N = 128
+ vert = 4
+
+ # Set up image geometry
+ ig = ImageGeometry(voxel_num_x=N,
+ voxel_num_y=N,
+ voxel_num_z=vert)
+
+ # Set up empty image data
+ Phantom = ImageData(geometry=ig,
+ dimension_labels=['horizontal_x',
+ 'horizontal_y',
+ 'vertical'])
+ Phantom += 0.05
+ # 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.94
+ if vert > 1 :
+ Phantom.fill(x, vertical=i)
+ i += 1
+
+
+ perc = 0.02
+ # Set up empty image data
+ noise = ImageData(numpy.random.normal(loc = 0.04 ,
+ scale = perc ,
+ size = Phantom.shape), geometry=ig,
+ dimension_labels=['horizontal_x',
+ 'horizontal_y',
+ 'vertical'])
+ Phantom += noise
+
+ # 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, set the width of a detector
+ # pixel relative to an object pixe and the number of detector pixels.
+ angles_num = 20
+ det_w = 1.0
+ det_num = N
+
+ angles = numpy.linspace(0,numpy.pi,angles_num,endpoint=False,dtype=numpy.float32)*\
+ 180/numpy.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)
+
+ # Set up Operator object combining the ImageGeometry and AcquisitionGeometry
+ # wrapping calls to CCPi projector.
+ A = CCPiProjectorSimple(ig, ag)
+
+ # Forward and backprojection are available as methods direct and adjoint. Here
+ # generate test data b and some noise
+
+ b = A.direct(Phantom)
+
+
+ #z = A.adjoint(b)
+
+
+ # 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. Note that 100 iterations for
+ # some of the methods is a very low number and 1000 or 10000 iterations may be
+ # needed if one wants to obtain a converged solution.
+ x_init = ImageData(geometry=ig,
+ dimension_labels=['horizontal_x','horizontal_y','vertical'])
+ X_init = BlockDataContainer(x_init)
+ B = BlockDataContainer(b,
+ ImageData(geometry=ig, dimension_labels=['horizontal_x','horizontal_y','vertical']))
+
+ # setup a tomo identity
+ Ibig = 1e5 * TomoIdentity(geometry=ig)
+ Ismall = 1e-5 * TomoIdentity(geometry=ig)
+
+ # composite operator
+ Kbig = BlockOperator(A, Ibig, shape=(2,1))
+ Ksmall = BlockOperator(A, Ismall, shape=(2,1))
+
+ #out = K.direct(X_init)
+
+ f = Norm2sq(Kbig,B)
+ f.L = 0.00003
+
+ fsmall = Norm2sq(Ksmall,B)
+ f.L = 0.00003
+
+ simplef = Norm2sq(A, b)
+ simplef.L = 0.00003
+
+ gd = GradientDescent( x_init=x_init, objective_function=simplef,
+ rate=simplef.L)
+ gd.max_iteration = 10
+
+ cg = CGLS()
+ cg.set_up(X_init, Kbig, B )
+ cg.max_iteration = 1
+
+ cgsmall = CGLS()
+ cgsmall.set_up(X_init, Ksmall, B )
+ cgsmall.max_iteration = 1
+
+
+ cgs = CGLS()
+ cgs.set_up(x_init, A, b )
+ cgs.max_iteration = 6
+ #
+ #out.__isub__(B)
+ #out2 = K.adjoint(out)
+
+ #(2.0*self.c)*self.A.adjoint( self.A.direct(x) - self.b )
+
+ #for _ in gd:
+ # print ("iteration {} {}".format(gd.iteration, gd.get_current_loss()))
+
+ #cg.run(10, lambda it,val: print ("iteration {} objective {}".format(it,val)) )
+
+ #cgs.run(10, lambda it,val: print ("iteration {} objective {}".format(it,val)))
+
+ #cgsmall.run(10, lambda it,val: print ("iteration {} objective {}".format(it,val)))
+ #cgsmall.run(10, lambda it,val: print ("iteration {} objective {}".format(it,val)))
+ # for _ in cg:
+ # print ("iteration {} {}".format(cg.iteration, cg.get_current_loss()))
+ #
+ # fig = plt.figure()
+ # plt.imshow(cg.get_output().get_item(0,0).subset(vertical=0).as_array())
+ # plt.title('Composite CGLS')
+ # plt.show()
+ #
+ # for _ in cgs:
+ # print ("iteration {} {}".format(cgs.iteration, cgs.get_current_loss()))
+ #
+ fig = plt.figure()
+ plt.subplot(1,5,1)
+ plt.imshow(Phantom.subset(vertical=0).as_array())
+ plt.title('Simulated Phantom')
+ plt.subplot(1,5,2)
+ plt.imshow(gd.get_output().subset(vertical=0).as_array())
+ plt.title('Simple Gradient Descent')
+ plt.subplot(1,5,3)
+ plt.imshow(cgs.get_output().subset(vertical=0).as_array())
+ plt.title('Simple CGLS')
+ plt.subplot(1,5,4)
+ plt.imshow(cg.get_output().get_item(0,0).subset(vertical=0).as_array())
+ plt.title('Composite CGLS\nbig lambda')
+ plt.subplot(1,5,5)
+ plt.imshow(cgsmall.get_output().get_item(0,0).subset(vertical=0).as_array())
+ plt.title('Composite CGLS\nsmall lambda')
+ plt.show()
+
+ def test_FiniteDiffOperator(self):
+ N, M = 200, 300
+
+
+ ig = ImageGeometry(voxel_num_x = M, voxel_num_y = N)
+ u = ig.allocate('random_int')
+ G = FiniteDiff(ig, direction=0, bnd_cond = 'Neumann')
+ print(type(u), u.as_array())
+ print(G.direct(u).as_array())
+
+ # Gradient Operator norm, for one direction should be close to 2
+ numpy.testing.assert_allclose(G.norm(), numpy.sqrt(4), atol=0.1)
+
+ M1, N1, K1 = 200, 300, 2
+ ig1 = ImageGeometry(voxel_num_x = M1, voxel_num_y = N1, channels = K1)
+ u1 = ig1.allocate('random_int')
+ G1 = FiniteDiff(ig1, direction=2, bnd_cond = 'Periodic')
+ print(ig1.shape==u1.shape)
+ print (G1.norm())
+ numpy.testing.assert_allclose(G1.norm(), numpy.sqrt(4), atol=0.1) \ No newline at end of file
diff --git a/Wrappers/Python/test/test_DataContainer.py b/Wrappers/Python/test/test_DataContainer.py
index f23179c..40cd244 100755
--- a/Wrappers/Python/test/test_DataContainer.py
+++ b/Wrappers/Python/test/test_DataContainer.py
@@ -174,7 +174,7 @@ class TestDataContainer(unittest.TestCase):
def binary_add(self):
print("Test binary add")
X, Y, Z = 512, 512, 512
- X, Y, Z = 256, 512, 512
+ X, Y, Z = 1024, 512, 512
steps = [timer()]
a = numpy.ones((X, Y, Z), dtype='float32')
steps.append(timer())
@@ -494,10 +494,21 @@ class TestDataContainer(unittest.TestCase):
self.assertEqual(order[0], image.dimension_labels[0])
self.assertEqual(order[1], image.dimension_labels[1])
self.assertEqual(order[2], image.dimension_labels[2])
+
+ ig = ImageGeometry(2,3,2)
+ try:
+ z = ImageData(numpy.random.randint(10, size=(2,3)), geometry=ig)
+ self.assertTrue(False)
+ except ValueError as ve:
+ print (ve)
+ self.assertTrue(True)
+
+ #vgeometry.allocate('')
def test_AcquisitionGeometry_allocate(self):
- ageometry = AcquisitionGeometry(dimension=2, angles=numpy.linspace(0, 180, num=10),
- geom_type='parallel', pixel_num_v=3,
- pixel_num_h=5, channels=2)
+ ageometry = AcquisitionGeometry(dimension=2,
+ angles=numpy.linspace(0, 180, num=10),
+ geom_type='parallel', pixel_num_v=3,
+ pixel_num_h=5, channels=2)
sino = ageometry.allocate()
shape = sino.shape
print ("shape", shape)
@@ -509,8 +520,8 @@ class TestDataContainer(unittest.TestCase):
self.assertEqual(1,sino.as_array()[shape[0]-1][shape[1]-1][shape[2]-1][shape[3]-1])
print (sino.dimension_labels, sino.shape, ageometry)
- default_order = ['channel' , ' angle' ,
- 'vertical' , 'horizontal']
+ default_order = ['channel' , 'angle' ,
+ 'vertical' , 'horizontal']
self.assertEqual(default_order[0], sino.dimension_labels[0])
self.assertEqual(default_order[1], sino.dimension_labels[1])
self.assertEqual(default_order[2], sino.dimension_labels[2])
@@ -522,7 +533,15 @@ class TestDataContainer(unittest.TestCase):
self.assertEqual(order[1], sino.dimension_labels[1])
self.assertEqual(order[2], sino.dimension_labels[2])
self.assertEqual(order[2], sino.dimension_labels[2])
-
+
+
+ try:
+ z = AcquisitionData(numpy.random.randint(10, size=(2,3)), geometry=ageometry)
+ self.assertTrue(False)
+ except ValueError as ve:
+ print (ve)
+ self.assertTrue(True)
+
def assertNumpyArrayEqual(self, first, second):
res = True
try:
@@ -561,4 +580,4 @@ class TestDataContainer(unittest.TestCase):
if __name__ == '__main__':
unittest.main()
- \ No newline at end of file
+
diff --git a/Wrappers/Python/test/test_Gradient.py b/Wrappers/Python/test/test_Gradient.py
new file mode 100755
index 0000000..1d6485c
--- /dev/null
+++ b/Wrappers/Python/test/test_Gradient.py
@@ -0,0 +1,90 @@
+import unittest
+import numpy
+#from ccpi.plugins.ops import CCPiProjectorSimple
+from ccpi.optimisation.ops import PowerMethodNonsquare
+from ccpi.optimisation.ops import TomoIdentity
+from ccpi.optimisation.funcs import Norm2sq, Norm1
+from ccpi.framework import ImageGeometry, AcquisitionGeometry
+from ccpi.framework import ImageData, AcquisitionData
+#from ccpi.optimisation.algorithms import GradientDescent
+from ccpi.framework import BlockDataContainer
+#from ccpi.optimisation.Algorithms import CGLS
+import functools
+
+from ccpi.optimisation.operators import Gradient, Identity, BlockOperator
+
+class TestGradient(unittest.TestCase):
+ def test_Gradient(self):
+ N, M, K = 20, 30, 40
+ channels = 10
+
+ # check range geometry, examples
+
+ ig1 = ImageGeometry(voxel_num_x = M, voxel_num_y = N)
+ ig2 = ImageGeometry(voxel_num_x = M, voxel_num_y = N, voxel_num_z = K)
+ ig3 = ImageGeometry(voxel_num_x = M, voxel_num_y = N, channels = channels)
+ ig4 = ImageGeometry(voxel_num_x = M, voxel_num_y = N, channels = channels, voxel_num_z= K)
+
+ G1 = Gradient(ig1, correlation = 'Space')
+ print(G1.range_geometry().shape, '2D no channels')
+
+ G4 = Gradient(ig3, correlation = 'SpaceChannels')
+ print(G4.range_geometry().shape, '2D with channels corr')
+ G5 = Gradient(ig3, correlation = 'Space')
+ print(G5.range_geometry().shape, '2D with channels no corr')
+
+ G6 = Gradient(ig4, correlation = 'Space')
+ print(G6.range_geometry().shape, '3D with channels no corr')
+ G7 = Gradient(ig4, correlation = 'SpaceChannels')
+ print(G7.range_geometry().shape, '3D with channels with corr')
+
+
+ u = ig1.allocate(ImageGeometry.RANDOM)
+ w = G1.range_geometry().allocate(ImageGeometry.RANDOM_INT)
+
+ LHS = (G1.direct(u)*w).sum()
+ RHS = (u * G1.adjoint(w)).sum()
+ numpy.testing.assert_approx_equal(LHS, RHS, significant = 1)
+ numpy.testing.assert_approx_equal(G1.norm(), numpy.sqrt(2*4), significant = 1)
+
+
+ u1 = ig3.allocate('random')
+ w1 = G4.range_geometry().allocate('random')
+ LHS1 = (G4.direct(u1) * w1).sum()
+ RHS1 = (u1 * G4.adjoint(w1)).sum()
+ numpy.testing.assert_approx_equal(LHS1, RHS1, significant=1)
+ numpy.testing.assert_almost_equal(G4.norm(), numpy.sqrt(3*4), decimal = 0)
+
+ u2 = ig4.allocate('random')
+ w2 = G7.range_geometry().allocate('random')
+ LHS2 = (G7.direct(u2) * w2).sum()
+ RHS2 = (u2 * G7.adjoint(w2)).sum()
+ numpy.testing.assert_approx_equal(LHS2, RHS2, significant = 3)
+ numpy.testing.assert_approx_equal(G7.norm(), numpy.sqrt(3*4), significant = 1)
+
+
+ #check direct/adjoint for space/channels correlation
+
+ ig_channel = ImageGeometry(voxel_num_x = 2, voxel_num_y = 3, channels = 2)
+ G_no_channel = Gradient(ig_channel, correlation = 'Space')
+ G_channel = Gradient(ig_channel, correlation = 'SpaceChannels')
+
+ u3 = ig_channel.allocate('random_int')
+ res_no_channel = G_no_channel.direct(u3)
+ res_channel = G_channel.direct(u3)
+
+ print(" Derivative for 3 directions, first is wrt Channel direction\n")
+ print(res_channel[0].as_array())
+ print(res_channel[1].as_array())
+ print(res_channel[2].as_array())
+
+ print(" Derivative for 2 directions, no Channel direction\n")
+ print(res_no_channel[0].as_array())
+ print(res_no_channel[1].as_array())
+
+ ig2D = ImageGeometry(voxel_num_x = 2, voxel_num_y = 3)
+ u4 = ig2D.allocate('random_int')
+ G2D = Gradient(ig2D)
+ res = G2D.direct(u4)
+ print(res[0].as_array())
+ print(res[1].as_array())
diff --git a/Wrappers/Python/test/test_Operator.py b/Wrappers/Python/test/test_Operator.py
new file mode 100644
index 0000000..293fb43
--- /dev/null
+++ b/Wrappers/Python/test/test_Operator.py
@@ -0,0 +1,412 @@
+import unittest
+#from ccpi.optimisation.operators import Operator
+from ccpi.optimisation.ops import TomoIdentity
+from ccpi.framework import ImageGeometry, ImageData, BlockDataContainer, DataContainer
+from ccpi.optimisation.operators import BlockOperator, BlockScaledOperator,\
+ FiniteDiff
+import numpy
+from timeit import default_timer as timer
+from ccpi.framework import ImageGeometry
+from ccpi.optimisation.operators import Gradient, Identity, SparseFiniteDiff
+
+def dt(steps):
+ return steps[-1] - steps[-2]
+
+class CCPiTestClass(unittest.TestCase):
+ def assertBlockDataContainerEqual(self, container1, container2):
+ print ("assert Block Data Container Equal")
+ self.assertTrue(issubclass(container1.__class__, container2.__class__))
+ for col in range(container1.shape[0]):
+ if issubclass(container1.get_item(col).__class__, DataContainer):
+ print ("Checking col ", col)
+ self.assertNumpyArrayEqual(
+ container1.get_item(col).as_array(),
+ container2.get_item(col).as_array()
+ )
+ else:
+ self.assertBlockDataContainerEqual(container1.get_item(col),container2.get_item(col))
+
+ def assertNumpyArrayEqual(self, first, second):
+ res = True
+ try:
+ numpy.testing.assert_array_equal(first, second)
+ except AssertionError as err:
+ res = False
+ print(err)
+ self.assertTrue(res)
+
+ def assertNumpyArrayAlmostEqual(self, first, second, decimal=6):
+ res = True
+ try:
+ numpy.testing.assert_array_almost_equal(first, second, decimal)
+ except AssertionError as err:
+ res = False
+ print(err)
+ print("expected " , second)
+ print("actual " , first)
+
+ self.assertTrue(res)
+
+
+class TestOperator(CCPiTestClass):
+ def test_ScaledOperator(self):
+ ig = ImageGeometry(10,20,30)
+ img = ig.allocate()
+ scalar = 0.5
+ sid = scalar * TomoIdentity(ig)
+ numpy.testing.assert_array_equal(scalar * img.as_array(), sid.direct(img).as_array())
+
+
+ def test_TomoIdentity(self):
+ ig = ImageGeometry(10,20,30)
+ img = ig.allocate()
+ self.assertTrue(img.shape == (30,20,10))
+ self.assertEqual(img.sum(), 0)
+ Id = TomoIdentity(ig)
+ y = Id.direct(img)
+ numpy.testing.assert_array_equal(y.as_array(), img.as_array())
+
+ def test_FiniteDifference(self):
+ ##
+ N, M = 2, 3
+
+ ig = ImageGeometry(N, M)
+ Id = Identity(ig)
+
+ FD = FiniteDiff(ig, direction = 0, bnd_cond = 'Neumann')
+ u = FD.domain_geometry().allocate('random_int')
+
+
+ res = FD.domain_geometry().allocate(ImageGeometry.RANDOM_INT)
+ FD.adjoint(u, out=res)
+ w = FD.adjoint(u)
+
+ self.assertNumpyArrayEqual(res.as_array(), w.as_array())
+
+ res = Id.domain_geometry().allocate(ImageGeometry.RANDOM_INT)
+ Id.adjoint(u, out=res)
+ w = Id.adjoint(u)
+
+ self.assertNumpyArrayEqual(res.as_array(), w.as_array())
+ self.assertNumpyArrayEqual(u.as_array(), w.as_array())
+
+ G = Gradient(ig)
+
+ u = G.range_geometry().allocate(ImageGeometry.RANDOM_INT)
+ res = G.domain_geometry().allocate(ImageGeometry.RANDOM_INT)
+ G.adjoint(u, out=res)
+ w = G.adjoint(u)
+ self.assertNumpyArrayEqual(res.as_array(), w.as_array())
+
+
+
+
+
+class TestBlockOperator(unittest.TestCase):
+ def assertBlockDataContainerEqual(self, container1, container2):
+ print ("assert Block Data Container Equal")
+ self.assertTrue(issubclass(container1.__class__, container2.__class__))
+ for col in range(container1.shape[0]):
+ if issubclass(container1.get_item(col).__class__, DataContainer):
+ print ("Checking col ", col)
+ self.assertNumpyArrayEqual(
+ container1.get_item(col).as_array(),
+ container2.get_item(col).as_array()
+ )
+ else:
+ self.assertBlockDataContainerEqual(container1.get_item(col),container2.get_item(col))
+
+ def assertNumpyArrayEqual(self, first, second):
+ res = True
+ try:
+ numpy.testing.assert_array_equal(first, second)
+ except AssertionError as err:
+ res = False
+ print(err)
+ self.assertTrue(res)
+
+ def assertNumpyArrayAlmostEqual(self, first, second, decimal=6):
+ res = True
+ try:
+ numpy.testing.assert_array_almost_equal(first, second, decimal)
+ except AssertionError as err:
+ res = False
+ print(err)
+ print("expected " , second)
+ print("actual " , first)
+
+ self.assertTrue(res)
+
+ def test_BlockOperator(self):
+
+
+ M, N = 3, 4
+ ig = ImageGeometry(M, N)
+ arr = ig.allocate('random_int')
+
+ G = Gradient(ig)
+ Id = Identity(ig)
+
+ B = BlockOperator(G, Id)
+ # Nx1 case
+ u = ig.allocate('random_int')
+ z1 = B.direct(u)
+
+ res = B.range_geometry().allocate()
+ #res = z1.copy()
+ B.direct(u, out=res)
+
+
+ print (type(z1), type(res))
+ print (z1.shape)
+ print(z1[0][0].as_array())
+ print(res[0][0].as_array())
+ self.assertBlockDataContainerEqual(z1, res)
+ # for col in range(z1.shape[0]):
+ # a = z1.get_item(col)
+ # b = res.get_item(col)
+ # if isinstance(a, BlockDataContainer):
+ # for col2 in range(a.shape[0]):
+ # self.assertNumpyArrayEqual(
+ # a.get_item(col2).as_array(),
+ # b.get_item(col2).as_array()
+ # )
+ # else:
+ # self.assertNumpyArrayEqual(
+ # a.as_array(),
+ # b.as_array()
+ # )
+ z1 = B.range_geometry().allocate(ImageGeometry.RANDOM_INT)
+
+ res1 = B.adjoint(z1)
+ res2 = B.domain_geometry().allocate()
+ B.adjoint(z1, out=res2)
+
+ self.assertNumpyArrayEqual(res1.as_array(), res2.as_array())
+
+ BB = BlockOperator( Id, 2 * Id)
+ B = BlockOperator( BB, Id )
+ v = B.domain_geometry().allocate()
+ B.adjoint(res,out=v)
+ vv = B.adjoint(res)
+ el1 = B.get_item(0,0).adjoint(z1.get_item(0)) +\
+ B.get_item(1,0).adjoint(z1.get_item(1))
+ print ("el1" , el1.as_array())
+ print ("vv" , vv.as_array())
+ print ("v" , v.as_array())
+
+ self.assertNumpyArrayEqual(v.as_array(),vv.as_array())
+ # test adjoint
+ print ("############ 2x1 #############")
+
+ BB = BlockOperator( Id, 2 * Id)
+ u = ig.allocate(1)
+ z1 = BB.direct(u)
+ print ("z1 shape {} one\n{} two\n{}".format(z1.shape,
+ z1.get_item(0).as_array(),
+ z1.get_item(1).as_array()))
+ res = BB.range_geometry().allocate(0)
+ BB.direct(u, out=res)
+ print ("res shape {} one\n{} two\n{}".format(res.shape,
+ res.get_item(0).as_array(),
+ res.get_item(1).as_array()))
+
+
+ self.assertNumpyArrayEqual(z1.get_item(0).as_array(),
+ u.as_array())
+ self.assertNumpyArrayEqual(z1.get_item(1).as_array(),
+ 2 * u.as_array())
+ self.assertNumpyArrayEqual(res.get_item(0).as_array(),
+ u.as_array())
+ self.assertNumpyArrayEqual(res.get_item(1).as_array(),
+ 2 * u.as_array())
+
+ x1 = BB.adjoint(z1)
+ print("adjoint x1\n",x1.as_array())
+
+ res1 = BB.domain_geometry().allocate()
+ BB.adjoint(z1, out=res1)
+ print("res1\n",res1.as_array())
+ self.assertNumpyArrayEqual(x1.as_array(),
+ res1.as_array())
+
+ self.assertNumpyArrayEqual(x1.as_array(),
+ 5 * u.as_array())
+ self.assertNumpyArrayEqual(res1.as_array(),
+ 5 * u.as_array())
+ #################################################
+
+ print ("############ 2x2 #############")
+ BB = BlockOperator( Id, 2 * Id, 3 * Id, Id, shape=(2,2))
+ B = BB
+ u = ig.allocate(1)
+ U = BlockDataContainer(u,u)
+ z1 = B.direct(U)
+
+
+ print ("z1 shape {} one\n{} two\n{}".format(z1.shape,
+ z1.get_item(0).as_array(),
+ z1.get_item(1).as_array()))
+ self.assertNumpyArrayEqual(z1.get_item(0).as_array(),
+ 3 * u.as_array())
+ self.assertNumpyArrayEqual(z1.get_item(1).as_array(),
+ 4 * u.as_array())
+ res = B.range_geometry().allocate()
+ B.direct(U, out=res)
+ self.assertNumpyArrayEqual(res.get_item(0).as_array(),
+ 3 * u.as_array())
+ self.assertNumpyArrayEqual(res.get_item(1).as_array(),
+ 4 * u.as_array())
+
+
+ x1 = B.adjoint(z1)
+ # this should be [15 u, 10 u]
+ el1 = B.get_item(0,0).adjoint(z1.get_item(0)) + B.get_item(1,0).adjoint(z1.get_item(1))
+ el2 = B.get_item(0,1).adjoint(z1.get_item(0)) + B.get_item(1,1).adjoint(z1.get_item(1))
+
+ shape = B.get_output_shape(z1.shape, adjoint=True)
+ print ("shape ", shape)
+ out = B.domain_geometry().allocate()
+
+ for col in range(B.shape[1]):
+ for row in range(B.shape[0]):
+ if row == 0:
+ el = B.get_item(row,col).adjoint(z1.get_item(row))
+ else:
+ el += B.get_item(row,col).adjoint(z1.get_item(row))
+ out.get_item(col).fill(el)
+
+ print ("el1 " , el1.as_array())
+ print ("el2 " , el2.as_array())
+ print ("out shape {} one\n{} two\n{}".format(out.shape,
+ out.get_item(0).as_array(),
+ out.get_item(1).as_array()))
+
+ self.assertNumpyArrayEqual(out.get_item(0).as_array(),
+ 15 * u.as_array())
+ self.assertNumpyArrayEqual(out.get_item(1).as_array(),
+ 10 * u.as_array())
+
+ res2 = B.domain_geometry().allocate()
+ #print (res2, res2.as_array())
+ B.adjoint(z1, out = res2)
+
+ #print ("adjoint",x1.as_array(),"\n",res2.as_array())
+ self.assertNumpyArrayEqual(
+ out.get_item(0).as_array(),
+ res2.get_item(0).as_array()
+ )
+ self.assertNumpyArrayEqual(
+ out.get_item(1).as_array(),
+ res2.get_item(1).as_array()
+ )
+
+ if True:
+ #B1 = BlockOperator(Id, Id, Id, Id, shape=(2,2))
+ B1 = BlockOperator(G, Id)
+ U = ig.allocate(ImageGeometry.RANDOM_INT)
+ #U = BlockDataContainer(u,u)
+ RES1 = B1.range_geometry().allocate()
+
+ Z1 = B1.direct(U)
+ B1.direct(U, out = RES1)
+
+ self.assertBlockDataContainerEqual(Z1,RES1)
+
+
+
+ print("U", U.as_array())
+ print("Z1", Z1[0][0].as_array())
+ print("RES1", RES1[0][0].as_array())
+ print("Z1", Z1[0][1].as_array())
+ print("RES1", RES1[0][1].as_array())
+ def test_timedifference(self):
+
+ M, N ,W = 100, 512, 512
+ ig = ImageGeometry(M, N, W)
+ arr = ig.allocate('random_int')
+
+ G = Gradient(ig)
+ Id = Identity(ig)
+
+ B = BlockOperator(G, Id)
+
+
+ # Nx1 case
+ u = ig.allocate('random_int')
+ steps = [timer()]
+ i = 0
+ n = 2.
+ t1 = t2 = 0
+ res = B.range_geometry().allocate()
+
+ while (i < n):
+ print ("i ", i)
+ steps.append(timer())
+ z1 = B.direct(u)
+ steps.append(timer())
+ t = dt(steps)
+ #print ("B.direct(u) " ,t)
+ t1 += t/n
+
+ steps.append(timer())
+ B.direct(u, out = res)
+ steps.append(timer())
+ t = dt(steps)
+ #print ("B.direct(u, out=res) " ,t)
+ t2 += t/n
+ i += 1
+
+ print ("Time difference ", t1,t2)
+ self.assertGreater(t1,t2)
+
+ steps = [timer()]
+ i = 0
+ #n = 50.
+ t1 = t2 = 0
+ resd = B.domain_geometry().allocate()
+ z1 = B.direct(u)
+ #B.adjoint(z1, out=resd)
+
+ print (type(res))
+ while (i < n):
+ print ("i ", i)
+ steps.append(timer())
+ w1 = B.adjoint(z1)
+ steps.append(timer())
+ t = dt(steps)
+ #print ("B.adjoint(z1) " ,t)
+ t1 += t/n
+
+ steps.append(timer())
+ B.adjoint(z1, out=resd)
+ steps.append(timer())
+ t = dt(steps)
+ #print ("B.adjoint(z1, out=res) " ,t)
+ t2 += t/n
+ i += 1
+
+ print ("Time difference ", t1,t2)
+ self.assertGreater(t1,t2)
+
+ def test_BlockOperatorLinearValidity(self):
+
+
+ M, N = 3, 4
+ ig = ImageGeometry(M, N)
+ arr = ig.allocate('random_int')
+
+ G = Gradient(ig)
+ Id = Identity(ig)
+
+ B = BlockOperator(G, Id)
+ # Nx1 case
+ u = ig.allocate('random_int')
+ w = B.range_geometry().allocate(ImageGeometry.RANDOM_INT)
+ w1 = B.direct(u)
+ u1 = B.adjoint(w)
+ self.assertEqual((w * w1).sum() , (u1*u).sum())
+
+
+
+
diff --git a/Wrappers/Python/test/test_algorithms.py b/Wrappers/Python/test/test_algorithms.py
index b5959b5..a35ffc1 100755
--- a/Wrappers/Python/test/test_algorithms.py
+++ b/Wrappers/Python/test/test_algorithms.py
@@ -86,6 +86,7 @@ class TestAlgorithms(unittest.TestCase):
identity = TomoIdentity(geometry=ig)
norm2sq = Norm2sq(identity, b)
+ norm2sq.L = 2 * norm2sq.c * identity.norm()**2
opt = {'tol': 1e-4, 'memopt':False}
alg = FISTA(x_init=x_init, f=norm2sq, g=None, opt=opt)
alg.max_iteration = 2
diff --git a/Wrappers/Python/test/test_functions.py b/Wrappers/Python/test/test_functions.py
new file mode 100644
index 0000000..05bdd7a
--- /dev/null
+++ b/Wrappers/Python/test/test_functions.py
@@ -0,0 +1,377 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Sat Mar 2 19:24:37 2019
+
+@author: evangelos
+"""
+
+
+import numpy as np
+#from ccpi.optimisation.funcs import Function
+from ccpi.optimisation.functions import Function
+from ccpi.framework import DataContainer, ImageData, ImageGeometry
+from ccpi.optimisation.operators import Identity
+from ccpi.optimisation.operators import BlockOperator
+from ccpi.framework import BlockDataContainer
+from numbers import Number
+from ccpi.optimisation.operators import Gradient
+
+#from ccpi.optimisation.functions import SimpleL2NormSq
+from ccpi.optimisation.functions import L2NormSquared
+#from ccpi.optimisation.functions import SimpleL1Norm
+from ccpi.optimisation.functions import L1Norm, MixedL21Norm
+
+from ccpi.optimisation.funcs import Norm2sq
+# from ccpi.optimisation.functions.L2NormSquared import SimpleL2NormSq, L2NormSq
+# from ccpi.optimisation.functions.L1Norm import SimpleL1Norm, L1Norm
+#from ccpi.optimisation.functions import mixed_L12Norm
+from ccpi.optimisation.functions import ZeroFunction
+
+from ccpi.optimisation.functions import FunctionOperatorComposition
+import unittest
+import numpy
+
+#
+
+
+class TestFunction(unittest.TestCase):
+ def assertBlockDataContainerEqual(self, container1, container2):
+ print ("assert Block Data Container Equal")
+ self.assertTrue(issubclass(container1.__class__, container2.__class__))
+ for col in range(container1.shape[0]):
+ if issubclass(container1.get_item(col).__class__, DataContainer):
+ print ("Checking col ", col)
+ self.assertNumpyArrayEqual(
+ container1.get_item(col).as_array(),
+ container2.get_item(col).as_array()
+ )
+ else:
+ self.assertBlockDataContainerEqual(container1.get_item(col),container2.get_item(col))
+
+ def assertNumpyArrayEqual(self, first, second):
+ res = True
+ try:
+ numpy.testing.assert_array_equal(first, second)
+ except AssertionError as err:
+ res = False
+ print(err)
+ self.assertTrue(res)
+
+ def assertNumpyArrayAlmostEqual(self, first, second, decimal=6):
+ res = True
+ try:
+ numpy.testing.assert_array_almost_equal(first, second, decimal)
+ except AssertionError as err:
+ res = False
+ print(err)
+ print("expected " , second)
+ print("actual " , first)
+
+ self.assertTrue(res)
+ def test_Function(self):
+
+
+ N = 3
+ ig = ImageGeometry(N,N)
+ ag = ig
+ op1 = Gradient(ig)
+ op2 = Identity(ig, ag)
+
+ # Form Composite Operator
+ operator = BlockOperator(op1, op2 , shape=(2,1) )
+
+ # Create functions
+ noisy_data = ag.allocate(ImageGeometry.RANDOM_INT)
+
+ d = ag.allocate(ImageGeometry.RANDOM_INT)
+ alpha = 0.5
+ # scaled function
+ g = alpha * L2NormSquared(b=noisy_data)
+
+ # Compare call of g
+ a2 = alpha*(d - noisy_data).power(2).sum()
+ #print(a2, g(d))
+ self.assertEqual(a2, g(d))
+
+ # Compare convex conjugate of g
+ a3 = 0.5 * d.squared_norm() + d.dot(noisy_data)
+ self.assertEqual(a3, g.convex_conjugate(d))
+ #print( a3, g.convex_conjugate(d))
+
+ #test proximal conjugate
+
+
+ def test_L2NormSquared(self):
+ # 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(ImageGeometry.RANDOM_INT)
+ b = ig.allocate(ImageGeometry.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 conjugate 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)
+
+ def test_L2NormSquaredOut(self):
+ # 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(ImageGeometry.RANDOM_INT)
+ b = ig.allocate(ImageGeometry.RANDOM_INT)
+
+ # check grad/call no data
+ f = L2NormSquared()
+ a1 = f.gradient(u)
+ a2 = a1 * 0.
+ f.gradient(u, out=a2)
+ 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 = b1 * 0.
+ f1.gradient(u, out=b2)
+
+ 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 proximal no data
+ tau = 5
+ e1 = f.proximal(u, tau)
+ e2 = e1 * 0.
+ f.proximal(u, tau, out=e2)
+ 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 = h1 * 0.
+ f1.proximal(u, tau, out=h2)
+ 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 = k1 * 0.
+ f.proximal_conjugate(u, tau, out=k2)
+
+ 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 = l1 * 0.
+ f1.proximal_conjugate(u, tau, out=l2)
+ 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)
+
+ # grad
+ w = f_scaled_no_data.gradient(u)
+ ww = w * 0
+ f_scaled_no_data.gradient(u, out=ww)
+
+ numpy.testing.assert_array_almost_equal(w.as_array(),
+ ww.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
+ w = f_scaled_no_data.proximal(u, tau)
+ ww = w * 0
+ f_scaled_no_data.proximal(u, tau, out=ww)
+ numpy.testing.assert_array_almost_equal(w.as_array(), \
+ ww.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
+ w = f_scaled_no_data.proximal_conjugate(u, tau)
+ ww = w * 0
+ f_scaled_no_data.proximal_conjugate(u, tau, out=ww)
+ numpy.testing.assert_array_almost_equal(w.as_array(), \
+ ww.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)
+
+ def test_Norm2sq_as_FunctionOperatorComposition(self):
+ M, N, K = 2,3,5
+ ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N, voxel_num_z = K)
+ u = ig.allocate(ImageGeometry.RANDOM_INT)
+ b = ig.allocate(ImageGeometry.RANDOM_INT)
+
+ A = 0.5 * Identity(ig)
+ old_chisq = Norm2sq(A, b, 1.0)
+ new_chisq = FunctionOperatorComposition(A, L2NormSquared(b=b))
+
+ yold = old_chisq(u)
+ ynew = new_chisq(u)
+ self.assertEqual(yold, ynew)
+
+ yold = old_chisq.gradient(u)
+ ynew = new_chisq.gradient(u)
+ numpy.testing.assert_array_equal(yold.as_array(), ynew.as_array())
+
+ def test_mixedL12Norm(self):
+ M, N, K = 2,3,5
+ ig = ImageGeometry(voxel_num_x=M, voxel_num_y = N)
+ u1 = ig.allocate('random_int')
+ u2 = ig.allocate('random_int')
+
+ U = BlockDataContainer(u1, u2, shape=(2,1))
+
+ # Define no scale and scaled
+ f_no_scaled = MixedL21Norm()
+ f_scaled = 1 * MixedL21Norm()
+
+ # call
+
+ a1 = f_no_scaled(U)
+ a2 = f_scaled(U)
+ self.assertNumpyArrayAlmostEqual(a1.as_array(),a2.as_array())
+
+
+ tmp = [ el**2 for el in U.containers ]
+ self.assertBlockDataContainerEqual(BlockDataContainer(*tmp),
+ U.power(2))
+
+ z1 = f_no_scaled.proximal_conjugate(U, 1)
+ u3 = ig.allocate('random_int')
+ u4 = ig.allocate('random_int')
+
+ z3 = BlockDataContainer(u3, u4, shape=(2,1))
+
+
+ f_no_scaled.proximal_conjugate(U, 1, out=z3)
+ self.assertBlockDataContainerEqual(z3,z1)
+
+#
+# f1 = L2NormSq(alpha=1, b=noisy_data)
+# print(f1(noisy_data))
+#
+# f2 = L2NormSq(alpha=5, b=noisy_data).composition_with(op2)
+# print(f2(noisy_data))
+#
+# print(f1.gradient(noisy_data).as_array())
+# print(f2.gradient(noisy_data).as_array())
+##
+# print(f1.proximal(noisy_data,1).as_array())
+# print(f2.proximal(noisy_data,1).as_array())
+#
+#
+# f3 = mixed_L12Norm(alpha = 1).composition_with(op1)
+# print(f3(noisy_data))
+#
+# print(ImageData(op1.direct(noisy_data).power(2).sum(axis=0)).sqrt().sum())
+#
+# print( 5*(op2.direct(d) - noisy_data).power(2).sum(), f2(d))
+#
+# from functions import mixed_L12Norm as mixed_L12Norm_old
+#
+# print(mixed_L12Norm_old(op1,None,alpha)(noisy_data))
+
+
+ #
+
+
diff --git a/Wrappers/Python/test/test_run_test.py b/Wrappers/Python/test/test_run_test.py
index 3c7d9ab..c698032 100755
--- a/Wrappers/Python/test/test_run_test.py
+++ b/Wrappers/Python/test/test_run_test.py
@@ -6,10 +6,10 @@ from ccpi.framework import ImageData
from ccpi.framework import AcquisitionData
from ccpi.framework import ImageGeometry
from ccpi.framework import AcquisitionGeometry
-from ccpi.optimisation.algs import FISTA
-from ccpi.optimisation.algs import FBPD
+from ccpi.optimisation.algorithms import FISTA
+#from ccpi.optimisation.algs import FBPD
from ccpi.optimisation.funcs import Norm2sq
-from ccpi.optimisation.funcs import ZeroFun
+from ccpi.optimisation.functions import ZeroFunction
from ccpi.optimisation.funcs import Norm1
from ccpi.optimisation.funcs import TV2D
from ccpi.optimisation.funcs import Norm2
@@ -82,7 +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)
- g0 = ZeroFun()
+ g0 = ZeroFunction()
# Initial guess
x_init = DataContainer(np.zeros((n, 1)))
@@ -90,12 +90,15 @@ class TestAlgorithms(unittest.TestCase):
f.grad(x_init)
# Run FISTA for least squares plus zero function.
- x_fista0, it0, timing0, criter0 = FISTA(x_init, f, g0, opt=opt)
-
+ #x_fista0, it0, timing0, criter0 = FISTA(x_init, f, g0, opt=opt)
+ fa = FISTA(x_init=x_init, f=f, g=g0)
+ fa.max_iteration = 10
+ fa.run(10)
+
# 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])
+ print(fa.get_output())
+ print(fa.get_last_objective())
# Compare to CVXPY
@@ -143,7 +146,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)
- g0 = ZeroFun()
+ g0 = ZeroFunction()
# Initial guess
x_init = DataContainer(np.zeros((n, 1)))
@@ -155,12 +158,16 @@ class TestAlgorithms(unittest.TestCase):
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, opt=opt)
+ #x_fista1, it1, timing1, criter1 = FISTA(x_init, f, g1, opt=opt)
+ fa = FISTA(x_init=x_init, f=f, g=g1)
+ fa.max_iteration = 10
+ fa.run(10)
+
# Print for comparison
print("FISTA least squares plus 1-norm solution and objective value:")
- print(x_fista1.as_array().squeeze())
- print(criter1[-1])
+ print(fa.get_output())
+ print(fa.get_last_objective())
# Compare to CVXPY
diff --git a/Wrappers/Python/wip/CGLS_tikhonov.py b/Wrappers/Python/wip/CGLS_tikhonov.py
new file mode 100644
index 0000000..e9bbcd9
--- /dev/null
+++ b/Wrappers/Python/wip/CGLS_tikhonov.py
@@ -0,0 +1,196 @@
+from ccpi.optimisation.algorithms import CGLS
+
+from ccpi.plugins.ops import CCPiProjectorSimple
+from ccpi.optimisation.ops import PowerMethodNonsquare
+from ccpi.optimisation.ops import TomoIdentity
+from ccpi.optimisation.funcs import Norm2sq, Norm1
+from ccpi.framework import ImageGeometry, AcquisitionGeometry, ImageData, AcquisitionData
+from ccpi.optimisation.algorithms import GradientDescent
+#from ccpi.optimisation.algorithms import CGLS
+import matplotlib.pyplot as plt
+import numpy
+from ccpi.framework import BlockDataContainer
+from ccpi.optimisation.operators import BlockOperator
+
+# Set up phantom size N x N x vert by creating ImageGeometry, initialising the
+# ImageData object with this geometry and empty array and finally put some
+# data into its array, and display one slice as image.
+
+# Image parameters
+N = 128
+vert = 4
+
+# Set up image geometry
+ig = ImageGeometry(voxel_num_x=N,
+ voxel_num_y=N,
+ voxel_num_z=vert)
+
+# Set up empty image data
+Phantom = ImageData(geometry=ig,
+ dimension_labels=['horizontal_x',
+ 'horizontal_y',
+ 'vertical'])
+Phantom += 0.05
+# 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.94
+ if vert > 1 :
+ Phantom.fill(x, vertical=i)
+ i += 1
+
+
+perc = 0.02
+# Set up empty image data
+noise = ImageData(numpy.random.normal(loc = 0.04 ,
+ scale = perc ,
+ size = Phantom.shape), geometry=ig,
+ dimension_labels=['horizontal_x',
+ 'horizontal_y',
+ 'vertical'])
+Phantom += noise
+
+# 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, set the width of a detector
+# pixel relative to an object pixe and the number of detector pixels.
+angles_num = 20
+det_w = 1.0
+det_num = N
+
+angles = numpy.linspace(0,numpy.pi,angles_num,endpoint=False,dtype=numpy.float32)*\
+ 180/numpy.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)
+
+# Set up Operator object combining the ImageGeometry and AcquisitionGeometry
+# wrapping calls to CCPi projector.
+A = CCPiProjectorSimple(ig, ag)
+
+# Forward and backprojection are available as methods direct and adjoint. Here
+# generate test data b and some noise
+
+b = A.direct(Phantom)
+
+
+#z = A.adjoint(b)
+
+
+# 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. Note that 100 iterations for
+# some of the methods is a very low number and 1000 or 10000 iterations may be
+# needed if one wants to obtain a converged solution.
+x_init = ImageData(geometry=ig,
+ dimension_labels=['horizontal_x','horizontal_y','vertical'])
+X_init = BlockDataContainer(x_init)
+B = BlockDataContainer(b,
+ ImageData(geometry=ig, dimension_labels=['horizontal_x','horizontal_y','vertical']))
+
+# setup a tomo identity
+Ibig = 1e5 * TomoIdentity(geometry=ig)
+Ismall = 1e-5 * TomoIdentity(geometry=ig)
+Iok = 1e1 * TomoIdentity(geometry=ig)
+
+# composite operator
+Kbig = BlockOperator(A, Ibig, shape=(2,1))
+Ksmall = BlockOperator(A, Ismall, shape=(2,1))
+Kok = BlockOperator(A, Iok, shape=(2,1))
+
+#out = K.direct(X_init)
+
+f = Norm2sq(Kbig,B)
+f.L = 0.00003
+
+fsmall = Norm2sq(Ksmall,B)
+fsmall.L = 0.00003
+
+fok = Norm2sq(Kok,B)
+fok.L = 0.00003
+
+simplef = Norm2sq(A, b)
+simplef.L = 0.00003
+
+gd = GradientDescent( x_init=x_init, objective_function=simplef,
+ rate=simplef.L)
+gd.max_iteration = 50
+
+Kbig.direct(X_init)
+Kbig.adjoint(B)
+cg = CGLS()
+cg.set_up(X_init, Kbig, B )
+cg.max_iteration = 10
+
+cgsmall = CGLS()
+cgsmall.set_up(X_init, Ksmall, B )
+cgsmall.max_iteration = 10
+
+
+cgs = CGLS()
+cgs.set_up(x_init, A, b )
+cgs.max_iteration = 10
+
+cgok = CGLS()
+cgok.set_up(X_init, Kok, B )
+cgok.max_iteration = 10
+# #
+#out.__isub__(B)
+#out2 = K.adjoint(out)
+
+#(2.0*self.c)*self.A.adjoint( self.A.direct(x) - self.b )
+
+for _ in gd:
+ print ("iteration {} {}".format(gd.iteration, gd.get_last_loss()))
+
+cg.run(10, lambda it,val: print ("iteration {} objective {}".format(it,val)))
+
+cgs.run(10, lambda it,val: print ("iteration {} objective {}".format(it,val)))
+
+cgsmall.run(10, lambda it,val: print ("iteration {} objective {}".format(it,val)))
+cgsmall.run(10, lambda it,val: print ("iteration {} objective {}".format(it,val)))
+cgok.run(10, verbose=True)
+# # for _ in cg:
+# print ("iteration {} {}".format(cg.iteration, cg.get_current_loss()))
+# #
+# # fig = plt.figure()
+# # plt.imshow(cg.get_output().get_item(0,0).subset(vertical=0).as_array())
+# # plt.title('Composite CGLS')
+# # plt.show()
+# #
+# # for _ in cgs:
+# print ("iteration {} {}".format(cgs.iteration, cgs.get_current_loss()))
+# #
+fig = plt.figure()
+plt.subplot(2,3,1)
+plt.imshow(Phantom.subset(vertical=0).as_array())
+plt.title('Simulated Phantom')
+plt.subplot(2,3,2)
+plt.imshow(gd.get_output().subset(vertical=0).as_array())
+plt.title('Simple Gradient Descent')
+plt.subplot(2,3,3)
+plt.imshow(cgs.get_output().subset(vertical=0).as_array())
+plt.title('Simple CGLS')
+plt.subplot(2,3,5)
+plt.imshow(cg.get_output().get_item(0).subset(vertical=0).as_array())
+plt.title('Composite CGLS\nbig lambda')
+plt.subplot(2,3,6)
+plt.imshow(cgsmall.get_output().get_item(0).subset(vertical=0).as_array())
+plt.title('Composite CGLS\nsmall lambda')
+plt.subplot(2,3,4)
+plt.imshow(cgok.get_output().get_item(0).subset(vertical=0).as_array())
+plt.title('Composite CGLS\nok lambda')
+plt.show()
diff --git a/Wrappers/Python/wip/CreatePhantom.py b/Wrappers/Python/wip/CreatePhantom.py
new file mode 100644
index 0000000..4bf6ea4
--- /dev/null
+++ b/Wrappers/Python/wip/CreatePhantom.py
@@ -0,0 +1,242 @@
+import numpy
+import tomophantom
+from tomophantom import TomoP3D
+from tomophantom.supp.artifacts import ArtifactsClass as Artifact
+import os
+
+import matplotlib.pyplot as plt
+
+from ccpi.optimisation.algorithms import CGLS
+from ccpi.plugins.ops import CCPiProjectorSimple
+from ccpi.optimisation.ops import PowerMethodNonsquare
+from ccpi.optimisation.ops import TomoIdentity
+from ccpi.optimisation.funcs import Norm2sq, Norm1
+from ccpi.framework import ImageGeometry, AcquisitionGeometry, ImageData, AcquisitionData
+from ccpi.optimisation.algorithms import GradientDescent
+from ccpi.framework import BlockDataContainer
+from ccpi.optimisation.operators import BlockOperator
+
+
+model = 13 # select a model number from tomophantom 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)
+
+# detector column count (horizontal)
+detector_horiz = int(numpy.sqrt(2)*N_size)
+# detector row count (vertical) (no reason for it to be > N)
+detector_vert = N_size
+# number of projection angles
+angles_num = int(0.5*numpy.pi*N_size)
+# angles are expressed in degrees
+angles = numpy.linspace(0.0, 179.9, angles_num, dtype='float32')
+
+
+acquisition_data_array = TomoP3D.ModelSino(model, N_size,
+ detector_horiz, detector_vert,
+ angles,
+ path_library3D)
+
+tomophantom_acquisition_axes_order = ['vertical', 'angle', 'horizontal']
+
+artifacts = Artifact(acquisition_data_array)
+
+
+tp_acq_data = AcquisitionData(artifacts.noise(0.2, 'Gaussian'),
+ dimension_labels=tomophantom_acquisition_axes_order)
+#print ("size", acquisition_data.shape)
+print ("horiz", detector_horiz)
+print ("vert", detector_vert)
+print ("angles", angles_num)
+
+tp_acq_geometry = AcquisitionGeometry(geom_type='parallel', dimension='3D',
+ angles=angles,
+ pixel_num_h=detector_horiz,
+ pixel_num_v=detector_vert,
+ channels=1,
+ )
+
+acq_data = tp_acq_geometry.allocate()
+#print (tp_acq_geometry)
+print ("AcquisitionData", acq_data.shape)
+print ("TomoPhantom", tp_acq_data.shape, tp_acq_data.dimension_labels)
+
+default_acquisition_axes_order = ['angle', 'vertical', 'horizontal']
+
+acq_data2 = tp_acq_data.subset(dimensions=default_acquisition_axes_order)
+print ("AcquisitionData", acq_data2.shape, acq_data2.dimension_labels)
+print ("AcquisitionData {} TomoPhantom {}".format(id(acq_data2.as_array()),
+ id(acquisition_data_array)))
+
+fig = plt.figure()
+plt.subplot(1,2,1)
+plt.imshow(acquisition_data_array[20])
+plt.title('Sinogram')
+plt.subplot(1,2,2)
+plt.imshow(tp_acq_data.as_array()[20])
+plt.title('Sinogram + noise')
+plt.show()
+
+# Set up Operator object combining the ImageGeometry and AcquisitionGeometry
+# wrapping calls to CCPi projector.
+
+ig = ImageGeometry(voxel_num_x=detector_horiz,
+ voxel_num_y=detector_horiz,
+ voxel_num_z=detector_vert)
+A = CCPiProjectorSimple(ig, tp_acq_geometry)
+# Forward and backprojection are available as methods direct and adjoint. Here
+# generate test data b and some noise
+
+#b = A.direct(Phantom)
+b = acq_data2
+
+#z = A.adjoint(b)
+
+
+# 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. Note that 100 iterations for
+# some of the methods is a very low number and 1000 or 10000 iterations may be
+# needed if one wants to obtain a converged solution.
+x_init = ImageData(geometry=ig,
+ dimension_labels=['horizontal_x','horizontal_y','vertical'])
+X_init = BlockDataContainer(x_init)
+B = BlockDataContainer(b,
+ ImageData(geometry=ig, dimension_labels=['horizontal_x','horizontal_y','vertical']))
+
+# setup a tomo identity
+Ibig = 4e1 * TomoIdentity(geometry=ig)
+Ismall = 1e-3 * TomoIdentity(geometry=ig)
+Iok = 7.6e0 * TomoIdentity(geometry=ig)
+
+# composite operator
+Kbig = BlockOperator(A, Ibig, shape=(2,1))
+Ksmall = BlockOperator(A, Ismall, shape=(2,1))
+Kok = BlockOperator(A, Iok, shape=(2,1))
+
+#out = K.direct(X_init)
+#x0 = x_init.copy()
+#x0.fill(numpy.random.randn(*x0.shape))
+#lipschitz = PowerMethodNonsquare(A, 5, x0)
+#print("lipschitz", lipschitz)
+
+#%%
+
+simplef = Norm2sq(A, b, memopt=False)
+#simplef.L = lipschitz[0]/3000.
+simplef.L = 0.00003
+
+f = Norm2sq(Kbig,B)
+f.L = 0.00003
+
+fsmall = Norm2sq(Ksmall,B)
+fsmall.L = 0.00003
+
+fok = Norm2sq(Kok,B)
+fok.L = 0.00003
+
+print("setup gradient descent")
+gd = GradientDescent( x_init=x_init, objective_function=simplef,
+ rate=simplef.L)
+gd.max_iteration = 5
+simplef2 = Norm2sq(A, b, memopt=True)
+#simplef.L = lipschitz[0]/3000.
+simplef2.L = 0.00003
+print("setup gradient descent")
+gd2 = GradientDescent( x_init=x_init, objective_function=simplef2,
+ rate=simplef2.L)
+gd2.max_iteration = 5
+
+Kbig.direct(X_init)
+Kbig.adjoint(B)
+print("setup CGLS")
+cg = CGLS()
+cg.set_up(X_init, Kbig, B )
+cg.max_iteration = 10
+
+print("setup CGLS")
+cgsmall = CGLS()
+cgsmall.set_up(X_init, Ksmall, B )
+cgsmall.max_iteration = 10
+
+
+print("setup CGLS")
+cgs = CGLS()
+cgs.set_up(x_init, A, b )
+cgs.max_iteration = 10
+
+print("setup CGLS")
+cgok = CGLS()
+cgok.set_up(X_init, Kok, B )
+cgok.max_iteration = 10
+# #
+#out.__isub__(B)
+#out2 = K.adjoint(out)
+
+#(2.0*self.c)*self.A.adjoint( self.A.direct(x) - self.b )
+
+
+for _ in gd:
+ print ("GradientDescent iteration {} {}".format(gd.iteration, gd.get_last_loss()))
+#gd2.run(5,verbose=True)
+print("CGLS block lambda big")
+cg.run(10, lambda it,val: print ("CGLS big iteration {} objective {}".format(it,val)))
+
+print("CGLS standard")
+cgs.run(10, lambda it,val: print ("CGLS standard iteration {} objective {}".format(it,val)))
+
+print("CGLS block lambda small")
+cgsmall.run(10, lambda it,val: print ("CGLS small iteration {} objective {}".format(it,val)))
+print("CGLS block lambdaok")
+cgok.run(10, verbose=True)
+# # for _ in cg:
+# print ("iteration {} {}".format(cg.iteration, cg.get_current_loss()))
+# #
+# # fig = plt.figure()
+# # plt.imshow(cg.get_output().get_item(0,0).subset(vertical=0).as_array())
+# # plt.title('Composite CGLS')
+# # plt.show()
+# #
+# # for _ in cgs:
+# print ("iteration {} {}".format(cgs.iteration, cgs.get_current_loss()))
+# #
+Phantom = ImageData(phantom_tm)
+
+theslice=40
+
+fig = plt.figure()
+plt.subplot(2,3,1)
+plt.imshow(numpy.flip(Phantom.subset(vertical=theslice).as_array(),axis=0), cmap='gray')
+plt.clim(0,0.7)
+plt.title('Simulated Phantom')
+plt.subplot(2,3,2)
+plt.imshow(gd.get_output().subset(vertical=theslice).as_array(), cmap='gray')
+plt.clim(0,0.7)
+plt.title('Simple Gradient Descent')
+plt.subplot(2,3,3)
+plt.imshow(cgs.get_output().subset(vertical=theslice).as_array(), cmap='gray')
+plt.clim(0,0.7)
+plt.title('Simple CGLS')
+plt.subplot(2,3,5)
+plt.imshow(cg.get_output().get_item(0).subset(vertical=theslice).as_array(), cmap='gray')
+plt.clim(0,0.7)
+plt.title('Composite CGLS\nbig lambda')
+plt.subplot(2,3,6)
+plt.imshow(cgsmall.get_output().get_item(0).subset(vertical=theslice).as_array(), cmap='gray')
+plt.clim(0,0.7)
+plt.title('Composite CGLS\nsmall lambda')
+plt.subplot(2,3,4)
+plt.imshow(cgok.get_output().get_item(0).subset(vertical=theslice).as_array(), cmap='gray')
+plt.clim(0,0.7)
+plt.title('Composite CGLS\nok lambda')
+plt.show()
+
+
+#Ibig = 7e1 * TomoIdentity(geometry=ig)
+#Kbig = BlockOperator(A, Ibig, shape=(2,1))
+#cg2 = CGLS(x_init=X_init, operator=Kbig, data=B)
+#cg2.max_iteration = 10
+#cg2.run(10, verbose=True)
diff --git a/Wrappers/Python/wip/fista_TV_denoising.py b/Wrappers/Python/wip/fista_TV_denoising.py
new file mode 100644
index 0000000..a9712fc
--- /dev/null
+++ b/Wrappers/Python/wip/fista_TV_denoising.py
@@ -0,0 +1,72 @@
+#!/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_TV_denoising.py b/Wrappers/Python/wip/pdhg_TV_denoising.py
new file mode 100755
index 0000000..e6d38e9
--- /dev/null
+++ b/Wrappers/Python/wip/pdhg_TV_denoising.py
@@ -0,0 +1,124 @@
+#!/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 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 = 2
+
+#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)
+
+# 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()
+
+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))
+
+
diff --git a/Wrappers/Python/wip/pdhg_TV_denoising3D.py b/Wrappers/Python/wip/pdhg_TV_denoising3D.py
new file mode 100644
index 0000000..06ecfa2
--- /dev/null
+++ b/Wrappers/Python/wip/pdhg_TV_denoising3D.py
@@ -0,0 +1,360 @@
+#!/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_precond.py b/Wrappers/Python/wip/pdhg_TV_denoising_precond.py
new file mode 100644
index 0000000..3fc9320
--- /dev/null
+++ b/Wrappers/Python/wip/pdhg_TV_denoising_precond.py
@@ -0,0 +1,156 @@
+#!/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 ZeroFun, L2NormSquared, \
+ MixedL21Norm, FunctionOperatorComposition, BlockFunction, ScaledFunction
+
+from skimage.util import random_noise
+
+
+
+# ############################################################################
+# 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', seed=10)
+noisy_data = ImageData(n1)
+
+
+#%%
+
+# 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
+# f = FunctionComposition_new(operator, mixed_L12Norm(alpha), \
+# L2NormSq(0.5, b = noisy_data) )
+
+ f1 = alpha * MixedL21Norm()
+ f2 = 0.5 * L2NormSquared(b = noisy_data)
+
+ f = BlockFunction(f1, f2 )
+ g = ZeroFun()
+
+else:
+
+ ###########################################################################
+ # No Composite #
+ ###########################################################################
+ operator = Gradient(ig)
+ f = alpha * FunctionOperatorComposition(operator, MixedL21Norm())
+ g = 0.5 * L2NormSquared(b = noisy_data)
+ ###########################################################################
+#%%
+
+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 = 1/normK
+ tau = 1/normK
+# tau = 1/(sigma*normK**2)
+
+#%%
+
+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()
+
+#aaa = res[0].as_array()
+#
+#plt.imshow(aaa)
+#plt.colorbar()
+#plt.show()
+#c2 = aaa
+#del aaa
+#%%
+
+#c2 = aaa
+##%%
+#%%
+#z = c1 - c2
+#plt.imshow(np.abs(z[0:95,0:95]))
+#plt.colorbar()
+
+#%%
+#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()
+#
+
+#%%
+#
diff --git a/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py b/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py
new file mode 100644
index 0000000..cec9770
--- /dev/null
+++ b/Wrappers/Python/wip/pdhg_TV_denoising_salt_pepper.py
@@ -0,0 +1,180 @@
+#!/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 ZeroFun, L1Norm, \
+ MixedL21Norm, FunctionOperatorComposition, BlockFunction
+
+
+from skimage.util import random_noise
+
+
+
+# ############################################################################
+# 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)
+
+ # 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)
+
+ f = BlockFunction(f1, f2 )
+ g = ZeroFun()
+
+else:
+
+ ###########################################################################
+ # No Composite #
+ ###########################################################################
+ operator = Gradient(ig)
+ f = alpha * FunctionOperatorComposition(operator, MixedL21Norm())
+ g = L1Norm(b = noisy_data)
+ ###########################################################################
+#%%
+
+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 = 1/normK
+ tau = 1/normK
+# tau = 1/(sigma*normK**2)
+
+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()
+
+#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')
+
+
+
+
diff --git a/Wrappers/Python/wip/pdhg_TV_tomography2D.py b/Wrappers/Python/wip/pdhg_TV_tomography2D.py
new file mode 100644
index 0000000..3fec34e
--- /dev/null
+++ b/Wrappers/Python/wip/pdhg_TV_tomography2D.py
@@ -0,0 +1,156 @@
+# -*- 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 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))
+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, 'gpu')
+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 = ZeroFunction()
+
+# 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}
+
+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))
+
+
+#%%
+#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_tomography2D_time.py b/Wrappers/Python/wip/pdhg_TV_tomography2D_time.py
new file mode 100644
index 0000000..dea8e5c
--- /dev/null
+++ b/Wrappers/Python/wip/pdhg_TV_tomography2D_time.py
@@ -0,0 +1,152 @@
+# -*- 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 ZeroFun, 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 = ZeroFun()
+
+# 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
new file mode 100644
index 0000000..fbe0d9b
--- /dev/null
+++ b/Wrappers/Python/wip/pdhg_tv_denoising_poisson.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Fri Feb 22 14:53:03 2019
+
+@author: evangelos
+"""
+
+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 ZeroFunction, KullbackLeibler, \
+ MixedL21Norm, BlockFunction
+
+
+from skimage.util import random_noise
+from timeit import default_timer as timer
+
+
+
+# ############################################################################
+# 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
+
+ig = ImageGeometry(voxel_num_x = N, voxel_num_y = N)
+ag = ig
+
+# Create noisy data. Add Gaussian noise
+n1 = random_noise(data, mode = 'poisson')
+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()
+
+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
+#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')
+
+
+
+
diff --git a/Wrappers/Python/wip/test_pdhg_gap.py b/Wrappers/Python/wip/test_pdhg_gap.py
new file mode 100644
index 0000000..6c7ccc9
--- /dev/null
+++ b/Wrappers/Python/wip/test_pdhg_gap.py
@@ -0,0 +1,140 @@
+#!/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()
+
+
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
new file mode 100644
index 0000000..e142d94
--- /dev/null
+++ b/Wrappers/Python/wip/test_pdhg_profile/profile_pdhg_TV_denoising.py
@@ -0,0 +1,273 @@
+#!/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()
+#
+#
+##%%
+##
diff --git a/Wrappers/Python/wip/test_profile.py b/Wrappers/Python/wip/test_profile.py
new file mode 100644
index 0000000..f14c0c3
--- /dev/null
+++ b/Wrappers/Python/wip/test_profile.py
@@ -0,0 +1,84 @@
+#!/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