Exemplo n.º 1
0
    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")
Exemplo n.º 2
0
    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")
Exemplo n.º 3
0
    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))
Exemplo n.º 4
0
    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))
Exemplo n.º 5
0
    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))
Exemplo n.º 7
0
    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")
Exemplo n.º 8
0
    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)
Exemplo n.º 9
0
    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")
Exemplo n.º 10
0
                            
    
    
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)
Exemplo n.º 12
0
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
Exemplo n.º 13
0
    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")
Exemplo n.º 14
0
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