def __init__(self, gm_domain, gm_range=None, bnd_cond=NEUMANN, **kwargs): self.num_threads = kwargs.get('num_threads', NUM_THREADS) self.gm_domain = gm_domain self.gm_range = gm_range #default is 'Neumann' self.bnd_cond = 0 if bnd_cond == PERIODIC: self.bnd_cond = 1 # Domain Geometry = Range Geometry if not stated if self.gm_range is None: self.gm_range = BlockGeometry( *[gm_domain for _ in range(len(gm_domain.shape))]) if len(gm_domain.shape) == 4: # Voxel size wrt to channel direction == 1.0 self.fd = cilacc.fdiff4D elif len(gm_domain.shape) == 3: self.fd = cilacc.fdiff3D elif len(gm_domain.shape) == 2: self.fd = cilacc.fdiff2D else: raise ValueError( 'Number of dimensions not supported, expected 2, 3 or 4, got {}' .format(len(gm_domain.shape))) self.voxel_size_order = list(self.gm_domain.spacing) super(Gradient_C, self).__init__(domain_geometry=self.gm_domain, range_geometry=self.gm_range) print("Initialised GradientOperator with C backend running with ", cilacc.openMPtest(self.num_threads), " threads")
def __init__(self, domain_geometry, bnd_cond=NEUMANN, **kwargs): # Number of threads self.num_threads = kwargs.get('num_threads', NUM_THREADS) # Split gradients, e.g., space and channels self.split = kwargs.get('split', False) # Consider pseudo 2D geometries with one slice, e.g., (1,voxel_num_y,voxel_num_x) self.is2D = False self.domain_shape = [] self.ind = [] self.voxel_size_order = [] for i, size in enumerate(list(domain_geometry.shape)): if size != 1: self.domain_shape.append(size) self.ind.append(i) self.voxel_size_order.append(domain_geometry.spacing[i]) self.is2D = True # Dimension of domain geometry self.ndim = len(self.domain_shape) #default is 'Neumann' self.bnd_cond = 0 if bnd_cond == PERIODIC: self.bnd_cond = 1 # Define range geometry if self.split is True and 'channel' in domain_geometry.dimension_labels: range_geometry = BlockGeometry( domain_geometry, BlockGeometry(*[domain_geometry for _ in range(self.ndim - 1)])) else: range_geometry = BlockGeometry( *[domain_geometry for _ in range(self.ndim)]) self.split = False if self.ndim == 4: self.fd = cilacc.fdiff4D elif self.ndim == 3: self.fd = cilacc.fdiff3D elif self.ndim == 2: self.fd = cilacc.fdiff2D else: raise ValueError( 'Number of dimensions not supported, expected 2, 3 or 4, got {}' .format(len(domain_geometry.shape))) super(Gradient_C, self).__init__(domain_geometry=domain_geometry, range_geometry=range_geometry) print("Initialised GradientOperator with C backend running with ", cilacc.openMPtest(self.num_threads), " threads")
def __init__(self, domain_geometry, bnd_cond='Neumann', **kwargs): '''creator :param domain_geometry: domain of the operator :param bnd_cond: boundary condition, either :code:`Neumann` or :code:`Periodic`. :type bnd_cond: str, optional, default :code:`Neumann` :param correlation: :code:`SpaceChannel` or :code:`Channel` :type correlation: str, optional, default :code:`Channel` ''' self.bnd_cond = bnd_cond self.correlation = kwargs.get( 'correlation', SymmetrisedGradientOperator.CORRELATION_SPACE) tmp_gm = len(domain_geometry.geometries) * domain_geometry.geometries # Define FD operator. We need one geometry from the BlockGeometry of the domain self.FD = FiniteDifferenceOperator(domain_geometry.get_item(0), direction=0, bnd_cond=self.bnd_cond) if domain_geometry.shape[0] == 2: self.order_ind = [0, 2, 1, 3] else: self.order_ind = [0, 3, 6, 1, 4, 7, 2, 5, 8] super(SymmetrisedGradientOperator, self).__init__(domain_geometry=domain_geometry, range_geometry=BlockGeometry(*tmp_gm))
def test_BlockGeometry_allocate_dtype(self): ig1 = ImageGeometry(3,3) ig2 = ImageGeometry(3,3, dtype=numpy.int16) bg = BlockGeometry(ig1,ig2) # print("The default dtype of the BlockImageGeometry is {}".format(bg.dtype)) self.assertEqual(bg.dtype, (numpy.float32, numpy.int16))
def range_geometry(self): '''returns the range of the BlockOperator''' tmp = [] for i in range(self.shape[0]): tmp.append(self.get_item(i, 0).range_geometry()) return BlockGeometry(*tmp)
def test_smoothL21Norm(self): ig = ImageGeometry(4, 5) bg = BlockGeometry(ig, ig) epsilon = 0.5 f1 = SmoothMixedL21Norm(epsilon) x = bg.allocate('random', seed=10) print("Check call for smooth MixedL21Norm") # check call res1 = f1(x) res2 = (x.pnorm(2)**2 + epsilon**2).sqrt().sum() # alternative tmp1 = x.copy() tmp1.containers += (epsilon, ) res3 = tmp1.pnorm(2).sum() numpy.testing.assert_almost_equal(res1, res2, decimal=5) numpy.testing.assert_almost_equal(res1, res3, decimal=5) print("Check gradient for smooth MixedL21Norm ... OK ") res1 = f1.gradient(x) res2 = x.divide((x.pnorm(2)**2 + epsilon**2).sqrt()) numpy.testing.assert_array_almost_equal( res1.get_item(0).as_array(), res2.get_item(0).as_array()) numpy.testing.assert_array_almost_equal( res1.get_item(1).as_array(), res2.get_item(1).as_array()) # check with MixedL21Norm, when epsilon close to 0 print("Check as epsilon goes to 0 ... OK") f1 = SmoothMixedL21Norm(1e-12) f2 = MixedL21Norm() res1 = f1(x) res2 = f2(x) numpy.testing.assert_almost_equal(f1(x), f2(x))
def __init__(self, domain_geometry, method='forward', bnd_cond='Neumann', **kwargs): '''creator :param gm_domain: domain of the operator :type gm_domain: :code:`AcquisitionGeometry` or :code:`ImageGeometry` :param bnd_cond: boundary condition, either :code:`Neumann` or :code:`Periodic`. :type bnd_cond: str, optional, default :code:`Neumann` :param correlation: optional, :code:`SpaceChannel` or :code:`Space` :type correlation: str, optional, default :code:`Space` ''' # Consider pseudo 2D geometries with one slice, e.g., (1,voxel_num_y,voxel_num_x) domain_shape = [] self.ind = [] for i, size in enumerate(list(domain_geometry.shape)): if size != 1: domain_shape.append(size) self.ind.append(i) # Dimension of domain geometry self.ndim = len(domain_shape) # Default correlation for the gradient coupling self.correlation = kwargs.get('correlation', CORRELATION_SPACE) self.bnd_cond = bnd_cond # Call FiniteDifference operator self.method = method self.FD = FiniteDifferenceOperator(domain_geometry, direction=0, method=self.method, bnd_cond=self.bnd_cond) if self.correlation == CORRELATION_SPACE and 'channel' in domain_geometry.dimension_labels: self.ndim -= 1 self.ind.remove(domain_geometry.dimension_labels.index('channel')) range_geometry = BlockGeometry( *[domain_geometry for _ in range(self.ndim)]) #get voxel spacing, if not use 1s try: self.voxel_size_order = list(domain_geometry.spacing) except: self.voxel_size_order = [1] * len(domain_geometry.shape) super(Gradient_numpy, self).__init__(domain_geometry=domain_geometry, range_geometry=range_geometry) print("Initialised GradientOperator with numpy backend")
def domain_geometry(self): '''returns the domain of the BlockOperator If the shape of the BlockOperator is (N,1) the domain is a ImageGeometry or AcquisitionGeometry. Otherwise it is a BlockGeometry. ''' if self.shape[1] == 1: # column BlockOperator return self.get_item(0, 0).domain_geometry() else: # get the geometries column wise # we need only the geometries from the first row # since it is compatible from __init__ tmp = [] for i in range(self.shape[1]): tmp.append(self.get_item(0, i).domain_geometry()) return BlockGeometry(*tmp)
def __init__(self, domain_geometry, method='forward', bnd_cond='Neumann', **kwargs): '''creator :param gm_domain: domain of the operator :type gm_domain: :code:`AcquisitionGeometry` or :code:`ImageGeometry` :param bnd_cond: boundary condition, either :code:`Neumann` or :code:`Periodic`. :type bnd_cond: str, optional, default :code:`Neumann` :param correlation: optional, :code:`SpaceChannel` or :code:`Space` :type correlation: str, optional, default :code:`Space` ''' self.size_dom_gm = len(domain_geometry.shape) self.correlation = kwargs.get('correlation', CORRELATION_SPACE) self.bnd_cond = bnd_cond # Call FiniteDiff operator self.method = method self.FD = FiniteDifferenceOperator(domain_geometry, direction=0, method=self.method, bnd_cond=self.bnd_cond) self.ndim = len(domain_geometry.shape) self.ind = list(range(self.ndim)) if self.correlation == CORRELATION_SPACE and 'channel' in domain_geometry.dimension_labels: self.ndim -= 1 self.ind.remove(domain_geometry.dimension_labels.index('channel')) range_geometry = BlockGeometry( *[domain_geometry for _ in range(self.ndim)]) #get voxel spacing, if not use 1s try: self.voxel_size_order = list(domain_geometry.spacing) except: self.voxel_size_order = [1] * len(domain_geometry.shape) super(Gradient_numpy, self).__init__(domain_geometry=domain_geometry, range_geometry=range_geometry) print("Initialised GradientOperator with numpy backend")
if __name__ == '__main__': M, N, K = 20,30,50 from cil.optimisation.functions import L2NormSquared, MixedL21Norm, L1Norm from cil.framework import ImageGeometry, BlockGeometry from cil.optimisation.operators import GradientOperator, IdentityOperator, BlockOperator import numpy import numpy as np ig = ImageGeometry(M, N) BG = BlockGeometry(ig, ig) u = ig.allocate('random_int') B = BlockOperator( GradientOperator(ig), IdentityOperator(ig) ) U = B.direct(u) b = ig.allocate('random_int') f1 = 10 * MixedL21Norm() f2 = 5 * L2NormSquared(b=b) f = BlockFunction(f1, f2) print(f.L) f = BlockFunction(f2, f2) print(f.L)
def test_ProjectionMap(self): # Check if direct is correct ig1 = ImageGeometry(3, 4) ig2 = ImageGeometry(5, 6) ig3 = ImageGeometry(5, 6, 4) # Create BlockGeometry bg = BlockGeometry(ig1, ig2, ig3) x = bg.allocate(10) # Extract containers x0, x1, x2 = x[0], x[1], x[2] for i in range(3): proj_map = ProjectionMap(bg, i) # res1 is in ImageData from the X_{i} "ImageGeometry" res1 = proj_map.direct(x) # res2 is in ImageData from the X_{i} "ImageGeometry" using out res2 = bg.geometries[i].allocate(0) proj_map.direct(x, out=res2) # Check with and without out numpy.testing.assert_array_almost_equal(res1.as_array(), res2.as_array()) # Depending on which index is used, check if x0, x1, x2 are the same with res2 if i == 0: numpy.testing.assert_array_almost_equal( x0.as_array(), res2.as_array()) elif i == 1: numpy.testing.assert_array_almost_equal( x1.as_array(), res2.as_array()) elif i == 2: numpy.testing.assert_array_almost_equal( x2.as_array(), res2.as_array()) else: pass # Check if adjoint is correct bg = BlockGeometry(ig1, ig2, ig3, ig1, ig2, ig3) x = ig1.allocate(20) index = 3 proj_map = ProjectionMap(bg, index) res1 = bg.allocate(0) proj_map.adjoint(x, out=res1) # check if all indices return arrays filled with 0, except the input index for i in range(len(bg.geometries)): if i != index: numpy.testing.assert_array_almost_equal( res1[i].as_array(), bg.geometries[i].allocate().as_array()) # Check error messages # Check if index is correct wrt length of Cartesian Product try: ig = ImageGeometry(3, 4) bg = BlockGeometry(ig, ig) index = 3 proj_map = ProjectionMap(bg, index) except ValueError as err: print(err) # Check error if an ImageGeometry is passed try: proj_map = ProjectionMap(ig, index) except ValueError as err: print(err)
class Gradient_C(LinearOperator): '''Finite Difference Operator: Computes first-order forward/backward differences on 2D, 3D, 4D ImageData under Neumann/Periodic boundary conditions''' def __init__(self, gm_domain, gm_range=None, bnd_cond=NEUMANN, **kwargs): self.num_threads = kwargs.get('num_threads', NUM_THREADS) self.gm_domain = gm_domain self.gm_range = gm_range #default is 'Neumann' self.bnd_cond = 0 if bnd_cond == PERIODIC: self.bnd_cond = 1 # Domain Geometry = Range Geometry if not stated if self.gm_range is None: self.gm_range = BlockGeometry( *[gm_domain for _ in range(len(gm_domain.shape))]) if len(gm_domain.shape) == 4: # Voxel size wrt to channel direction == 1.0 self.fd = cilacc.fdiff4D elif len(gm_domain.shape) == 3: self.fd = cilacc.fdiff3D elif len(gm_domain.shape) == 2: self.fd = cilacc.fdiff2D else: raise ValueError( 'Number of dimensions not supported, expected 2, 3 or 4, got {}' .format(len(gm_domain.shape))) self.voxel_size_order = list(self.gm_domain.spacing) super(Gradient_C, self).__init__(domain_geometry=self.gm_domain, range_geometry=self.gm_range) print("Initialised GradientOperator with C backend running with ", cilacc.openMPtest(self.num_threads), " threads") @staticmethod def datacontainer_as_c_pointer(x): ndx = x.as_array() return ndx, ndx.ctypes.data_as(c_float_p) @staticmethod def ndarray_as_c_pointer(ndx): return ndx.ctypes.data_as(c_float_p) def direct(self, x, out=None): ndx = np.asarray(x.as_array(), dtype=np.float32) #ndx , x_p = Gradient_C.datacontainer_as_c_pointer(x) x_p = Gradient_C.ndarray_as_c_pointer(ndx) return_val = False if out is None: out = self.gm_range.allocate(None) return_val = True ndout = [el.as_array() for el in out.containers] #pass list of all arguments #arg1 = [Gradient_C.datacontainer_as_c_pointer(out.get_item(i))[1] for i in range(self.gm_range.shape[0])] arg1 = [ Gradient_C.ndarray_as_c_pointer(ndout[i]) for i in range(self.gm_range.shape[0]) ] arg2 = [el for el in x.shape] args = arg1 + arg2 + [self.bnd_cond, 1, self.num_threads] self.fd(x_p, *args) for i in range(len(ndout)): out.get_item(i).fill(ndout[i]) if any(elem != 1.0 for elem in self.voxel_size_order): out /= self.voxel_size_order # out /= self.voxel_size_order if return_val is True: return out def adjoint(self, x, out=None): return_val = False if out is None: out = self.gm_domain.allocate(None) return_val = True ndout = out.as_array() # ndout, out_p = Gradient_C.datacontainer_as_c_pointer(out) out_p = Gradient_C.ndarray_as_c_pointer(ndout) if any(elem != 1.0 for elem in self.voxel_size_order): tmp = x / self.voxel_size_order else: tmp = x ndx = [el.as_array() for el in tmp.containers] #arg1 = [Gradient_C.datacontainer_as_c_pointer(tmp.get_item(i))[1] for i in range(self.gm_range.shape[0])] arg1 = [ Gradient_C.ndarray_as_c_pointer(ndx[i]) for i in range(self.gm_range.shape[0]) ] arg2 = [el for el in out.shape] args = arg1 + arg2 + [self.bnd_cond, 0, self.num_threads] self.fd(out_p, *args) out.fill(ndout) if return_val is True: return out
def __init__(self, domain_geometry, method='forward', bnd_cond='Neumann', **kwargs): '''creator :param gm_domain: domain of the operator :type gm_domain: :code:`AcquisitionGeometry` or :code:`ImageGeometry` :param bnd_cond: boundary condition, either :code:`Neumann` or :code:`Periodic`. :type bnd_cond: str, optional, default :code:`Neumann` :param correlation: optional, :code:`SpaceChannel` or :code:`Space` :type correlation: str, optional, default :code:`Space` ''' self.size_dom_gm = len(domain_geometry.shape) self.correlation = kwargs.get('correlation', CORRELATION_SPACE) self.bnd_cond = bnd_cond # Call FiniteDiff operator self.method = method self.FD = FiniteDifferenceOperator(domain_geometry, direction=0, method=self.method, bnd_cond=self.bnd_cond) if self.correlation == CORRELATION_SPACE: if domain_geometry.channels > 1: range_geometry = BlockGeometry(*[ domain_geometry for _ in range(domain_geometry.length - 1) ]) if self.size_dom_gm == 4: # 3D + Channel expected_order = [ ImageGeometry.CHANNEL, ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X ] else: # 2D + Channel expected_order = [ ImageGeometry.CHANNEL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X ] order = domain_geometry.get_order_by_label( domain_geometry.dimension_labels, expected_order) self.ind = order[1:] else: # no channel info range_geometry = BlockGeometry( *[domain_geometry for _ in range(domain_geometry.length)]) if self.size_dom_gm == 3: # 3D expected_order = [ ImageGeometry.VERTICAL, ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X ] # self.voxel_size_order = [domain_geometry.voxel_size_z, domain_geometry.voxel_size_y, domain_geometry.voxel_size_x ] else: # 2D expected_order = [ ImageGeometry.HORIZONTAL_Y, ImageGeometry.HORIZONTAL_X ] self.ind = domain_geometry.get_order_by_label( domain_geometry.dimension_labels, expected_order) elif self.correlation == CORRELATION_SPACECHANNEL: if domain_geometry.channels > 1: range_geometry = BlockGeometry( *[domain_geometry for _ in range(domain_geometry.length)]) self.ind = range(domain_geometry.length) else: raise ValueError('No channels to correlate') self.voxel_size_order = domain_geometry.spacing super(Gradient_numpy, self).__init__(domain_geometry=domain_geometry, range_geometry=range_geometry) print("Initialised GradientOperator with numpy backend")
class Gradient_C(LinearOperator): '''Finite Difference Operator: Computes first-order forward/backward differences on 2D, 3D, 4D ImageData under Neumann/Periodic boundary conditions''' def __init__(self, gm_domain, gm_range=None, bnd_cond=NEUMANN, **kwargs): self.num_threads = kwargs.get('num_threads', NUM_THREADS) self.split = kwargs.get('split', False) self.gm_domain = gm_domain self.gm_range = gm_range self.ndim = self.gm_domain.length #default is 'Neumann' self.bnd_cond = 0 if bnd_cond == PERIODIC: self.bnd_cond = 1 # Domain Geometry = Range Geometry if not stated if self.gm_range is None: if self.split is True and 'channel' in self.gm_domain.dimension_labels: self.gm_range = BlockGeometry( gm_domain, BlockGeometry( *[gm_domain for _ in range(len(gm_domain.shape) - 1)])) else: self.gm_range = BlockGeometry( *[gm_domain for _ in range(len(gm_domain.shape))]) self.split = False if len(gm_domain.shape) == 4: # Voxel size wrt to channel direction == 1.0 self.fd = cilacc.fdiff4D elif len(gm_domain.shape) == 3: self.fd = cilacc.fdiff3D elif len(gm_domain.shape) == 2: self.fd = cilacc.fdiff2D else: raise ValueError( 'Number of dimensions not supported, expected 2, 3 or 4, got {}' .format(len(gm_domain.shape))) self.voxel_size_order = list(self.gm_domain.spacing) super(Gradient_C, self).__init__(domain_geometry=self.gm_domain, range_geometry=self.gm_range) print("Initialised GradientOperator with C backend running with ", cilacc.openMPtest(self.num_threads), " threads") @staticmethod def datacontainer_as_c_pointer(x): ndx = x.as_array() return ndx, ndx.ctypes.data_as(c_float_p) @staticmethod def ndarray_as_c_pointer(ndx): return ndx.ctypes.data_as(c_float_p) def direct(self, x, out=None): ndx = np.asarray(x.as_array(), dtype=np.float32, order='C') x_p = Gradient_C.ndarray_as_c_pointer(ndx) return_val = False if out is None: out = self.gm_range.allocate(None) return_val = True if self.split is False: ndout = [el.as_array() for el in out.containers] else: ind = self.gm_domain.dimension_labels.index('channel') ndout = [el.as_array() for el in out.get_item(1).containers] ndout.insert( ind, out.get_item(0).as_array( )) #insert channels dc at correct point for channel data #pass list of all arguments arg1 = [ Gradient_C.ndarray_as_c_pointer(ndout[i]) for i in range(len(ndout)) ] arg2 = [el for el in x.shape] args = arg1 + arg2 + [self.bnd_cond, 1, self.num_threads] self.fd(x_p, *args) for i, el in enumerate(self.voxel_size_order): if el != 1: ndout[i] /= el #fill back out in corerct (non-traivial) order if self.split is False: for i in range(self.ndim): out.get_item(i).fill(ndout[i]) else: ind = self.gm_domain.dimension_labels.index('channel') out.get_item(0).fill(ndout[ind]) j = 0 for i in range(self.ndim): if i != ind: out.get_item(1).get_item(j).fill(ndout[i]) j += 1 if return_val is True: return out def adjoint(self, x, out=None): return_val = False if out is None: out = self.gm_domain.allocate(None) return_val = True ndout = np.asarray(out.as_array(), dtype=np.float32, order='C') out_p = Gradient_C.ndarray_as_c_pointer(ndout) if self.split is False: ndx = [el.as_array() for el in x.containers] else: ind = self.gm_domain.dimension_labels.index('channel') ndx = [el.as_array() for el in x.get_item(1).containers] ndx.insert(ind, x.get_item(0).as_array()) for i, el in enumerate(self.voxel_size_order): if el != 1: ndx[i] /= el arg1 = [ Gradient_C.ndarray_as_c_pointer(ndx[i]) for i in range(self.ndim) ] arg2 = [el for el in out.shape] args = arg1 + arg2 + [self.bnd_cond, 0, self.num_threads] self.fd(out_p, *args) out.fill(ndout) #reset input data for i, el in enumerate(self.voxel_size_order): if el != 1: ndx[i] *= el if return_val is True: return out