コード例 #1
0
    def set_res(self):
        """
        Calulates initial value for res if this is not given.
        :return: None
        """
        setattr(self, 'res', np.zeros(self.geo.nVoxel, dtype=np.float32))
        init = self.init
        verbose = self.verbose
        if init == 'multigrid':
            if verbose:
                print('init multigrid in progress...')
                print('default blocksize=1 for init_multigrid(OS_SART)')
            self.res = init_multigrid(self.proj,
                                      self.geo,
                                      self.angles,
                                      alg='SART')
            if verbose:
                print('init multigrid complete.')
        if init == 'FDK':
            self.res = FDK(self.proj, self.geo, self.angles)

        if isinstance(init, np.ndarray):
            if (self.geo.nVoxel == init.shape).all():

                self.res = init

            else:
                raise ValueError('wrong dimension of array for initialisation')
コード例 #2
0
    def set_res(self):
        """
        Calulates initial value for res if this is not given.
        :return: None
        """
        self.res = np.zeros(self.geo.nVoxel, dtype=np.float32)
        init = self.init
        verbose = self.verbose
        if init == "multigrid":
            if verbose:
                print("init multigrid in progress...")
                print("default blocksize=1 for init_multigrid(OS_SART)")
            self.res = init_multigrid(self.proj, self.geo, self.angles, alg="SART")
            if verbose:
                print("init multigrid complete.")
        if init == "FDK":
            self.res = FDK(self.proj, self.geo, self.angles)

        if isinstance(init, np.ndarray):
            if (self.geo.nVoxel == init.shape).all():

                self.res = init

            else:
                raise ValueError("wrong dimension of array for initialisation")
コード例 #3
0
    def __init__(self, proj, geo, angles, niter, **kwargs):

        if "blocksize" not in kwargs:
            kwargs.update(blocksize=1)
        IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs)
        if "alpha" not in kwargs:
            self.alpha = 0.002
        if "alpha_red" not in kwargs:
            self.alpha_red = 0.95
        if "rmax" not in kwargs:
            self.rmax = 0.95
        if "maxl2err" not in kwargs:
            self.epsilon = (
                im3DNORM(Ax(FDK(proj, geo, angles, gpuids=self.gpuids), geo, angles) - proj, 2)
                * 0.2
            )
        else:
            self.epsilon = kwargs["maxl2err"]
        if "tviter" not in kwargs:
            self.numiter_tv = 20
        else:
            self.numiter_tv = kwargs["tviter"]
        if "regularisation" not in kwargs:
            self.regularisation = "minimizeTV"
        self.beta = self.lmbda
        self.beta_red = self.lmbda_red
コード例 #4
0
ファイル: iterative_recon_alg.py プロジェクト: CERN/TIGRE
    def set_res(self):
        """
        Calulates initial value for res if this is not given.
        :return: None
        """
        setattr(self, 'res', np.zeros(self.geo.nVoxel, dtype=np.float32))
        init = self.init
        verbose = self.verbose
        if init == 'multigrid':
            if verbose:
                print('init multigrid in progress...')
                print('default blocksize=1 for init_multigrid(OS_SART)')
            self.res = init_multigrid(self.proj, self.geo, self.angles, alg='SART')
            if verbose:
                print('init multigrid complete.')
        if init == 'FDK':
            self.res = FDK(self.proj, self.geo, self.angles)

        if type(init) == np.ndarray:
            if (self.geo.nVoxel == init.shape).all():

                self.res = init

            else:
                raise ValueError('wrong dimension of array for initialisation')
コード例 #5
0
 def __init__(self, proj, geo, angles, niter, **kwargs):
     
     # if "blocksize" not in kwargs:
     #     kwargs.update(dict(blocksize=1))
     #kwargs.update(dict(regularisation="minimizeTV"))
     IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs)
     if "maxl2err" not in kwargs:
         self.epsilon = (
             im3DNORM(Ax(FDK(proj, geo, angles, gpuids=self.gpuids), geo, angles) - proj, 2)
             * 0.2
         )
     else:
         self.epsilon = kwargs["maxl2err"]
     self.numiter_tv = 20 if "tviter" not in kwargs else kwargs["tviter"]
     self.beta = self.lmbda
     self.beta_red = self.lmbda_red
コード例 #6
0
ファイル: pocs_algorithms.py プロジェクト: harshitAgr/TIGRE
 def __init__(self, proj, geo, angles, niter, **kwargs):
     IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs)
     if not kwargs.has_key('alpha'):
         self.alpha = 0.002
     if not kwargs.has_key('alpha_red'):
         self.alpha_red = 0.95
     if not kwargs.has_key('rmax'):
         self.rmax = 0.95
     if not kwargs.has_key('maxl2err'):
         self.epsilon = im3DNORM(FDK(proj, geo, angles), 2) * 0.2
     if not kwargs.has_key("numiter_tv"):
         self.numiter_tv = 20
     if not kwargs.has_key('regularisation'):
         self.regularisation = 'minimizeTV'
     self.beta = self.lmbda
     self.beta_red = self.lmbda_red
コード例 #7
0
    def __init__(self, proj, geo, angles, niter, **kwargs):

        if 'blocksize' not in kwargs:
            kwargs.update(blocksize=1)
        IterativeReconAlg.__init__(self, proj, geo, angles, niter, **kwargs)
        if 'alpha' not in kwargs:
            self.alpha = 0.002
        if 'alpha_red' not in kwargs:
            self.alpha_red = 0.95
        if 'rmax' not in kwargs:
            self.rmax = 0.95
        if 'maxl2err' not in kwargs:
            self.epsilon = im3DNORM(FDK(proj, geo, angles), 2) * 0.2
        if "numiter_tv" not in kwargs:
            self.numiter_tv = 20
        if 'regularisation' not in kwargs:
            self.regularisation = 'minimizeTV'
        self.beta = self.lmbda
        self.beta_red = self.lmbda_red
コード例 #8
0
class IterativeReconAlg(object):
    """
    Parameters
    ----------
    :param proj: (np.ndarray, dtype=np.float32)
    Input data, shape = (geo.nDector, nangles)

    :param geo: (tigre.geometry)
    Geometry of detector and image (see examples/Demo code)

    :param angles: (np.ndarray , dtype=np.float32)
    angles of projection, shape = (nangles,3)

    :param niter: (int)
    number of iterations for reconstruction algorithm

    :param kwargs: (dict)
    optional parameters

    Keyword Arguments
    -----------------
    :keyword blocksize: (int)
        number of angles to be included in each iteration
        of proj and backproj for OS_SART
    :keyword lmbda: (np.float64)
        Sets the value of the hyperparameter.

    :keyword lmbda_red: (np.float64)
        Reduction of lmbda every iteration
        lmbda=lmbda_red*lmbda. Default is 0.99

    :keyword init: (str)
        Describes different initialization techniques.
               None      : Initializes the image to zeros (default)
              "FDK"      : intializes image to FDK reconstrucition

    :keyword verbose:  (Boolean)
        Feedback print statements for algorithm progress
        default=True

    :keyword OrderStrategy : (str)
        Chooses the subset ordering strategy. Options are:
                 "ordered"        : uses them in the input order, but
                                    divided
                 "random"         : orders them randomply

    :keyword tviter: (int)
        For algorithms that make use of a tvdenoising step in their
        iterations. This includes:

            OS_SART_TV
            ASD_POCS
            AWASD_POCS
            FISTA

    :keyword tvlambda: (float)
        For algorithms that make use of a tvdenoising step in their
        iterations.

            OS_SART_TV
            FISTA

    Usage
    --------
    >>> import numpy as np
    >>> import tigre
    >>> import tigre.algorithms as algs
    >>> from tigre.demos.Test_data import data_loader
    >>> geo = tigre.geometry(mode='cone',default_geo=True,
    >>>                         nVoxel=np.array([64,64,64]))
    >>> angles = np.linspace(0,2*np.pi,100)
    >>> src_img = data_loader.load_head_phantom(geo.nVoxel)
    >>> proj = tigre.Ax(src_img,geo,angles)
    >>> output = algs.iterativereconalg(proj,geo,angles,niter=50
    >>>                                 blocksize=20)

    tigre.demos.run() to launch ipython notebook file with examples.


    --------------------------------------------------------------------
    This file is part of the TIGRE Toolbox

    Copyright (c) 2015, University of Bath and
                        CERN-European Organization for Nuclear Research
                        All rights reserved.

    License:            Open Source under BSD.
                        See the full license at
                        https://github.com/CERN/TIGRE/license.txt

    Contact:            [email protected]
    Codes:              https://github.com/CERN/TIGRE/
    --------------------------------------------------------------------
    Coded by:          MATLAB (original code): Ander Biguri
                       PYTHON : Reuben Lindroos

     """
    def __init__(self, proj, geo, angles, niter, **kwargs):

        self.proj = proj
        self.angles = angles
        self.geo = geo
        self.niter = niter

        options = dict(blocksize=20,
                       lmbda=1,
                       lmbda_red=0.99,
                       OrderStrategy=None,
                       Quameasopts=None,
                       init=None,
                       verbose=True,
                       noneg=True,
                       computel2=False,
                       dataminimizing='art_data_minimizing',
                       name='Iterative Reconstruction',
                       sup_kw_warning=False)
        allowed_keywords = [
            'V', 'W', 'log_parameters', 'angleblocks', 'angle_index', 'alpha',
            'alpha_red', 'rmax', 'maxl2err', 'delta', 'regularisation',
            'tviter', 'tvlambda', 'hyper'
        ]
        self.__dict__.update(options)
        self.__dict__.update(**kwargs)
        for kw in kwargs.keys():
            if kw not in options and (kw not in allowed_keywords):
                if self.verbose:
                    if not kwargs.get('sup_kw_warning'):
                        # Note: might not want this warning (typo checking).
                        print(
                            "Warning: " + kw +
                            " not recognised as default parameter for instance of IterativeReconAlg."
                        )
        if self.angles.ndim == 1:
            a1 = self.angles
            a2 = np.zeros(self.angles.shape[0], dtype=np.float32)
            setattr(self, 'angles', np.vstack((a1, a2, a2)).T)
        if not all([hasattr(self, 'angleindex'),
                    hasattr(self, 'angleblocks')]):
            self.set_angle_index()
        if not hasattr(self, 'W'):
            self.set_w()
        if not hasattr(self, 'V'):
            self.set_v()
        if not hasattr(self, 'res'):
            self.set_res()
        setattr(self, 'lq', [])  # quameasoptslist
        setattr(self, 'l2l', [])  # l2list

    def set_w(self):
        """
        Calculates value of W if this is not given.
        :return: None
        """
        geox = copy.deepcopy(self.geo)
        geox.sVoxel[1:] = geox.sVoxel[
            1:] * 1.1  # a bit larger to avoid zeros in projections
        geox.sVoxel[0] = max(geox.sDetector[0], geox.sVoxel[0])

        geox.nVoxel = np.array([2, 2, 2])
        geox.dVoxel = geox.sVoxel / geox.nVoxel
        W = Ax(np.ones(geox.nVoxel, dtype=np.float32), geox, self.angles,
               "Siddon")
        W[W <= min(self.geo.dVoxel / 4)] = np.inf
        W = 1. / W
        setattr(self, 'W', W)

    def set_v(self):
        """
        Computes value of V parameter if this is not given.
        :return: None
        """
        geo = self.geo
        V = np.ones((self.angleblocks.shape[0], geo.nVoxel[1], geo.nVoxel[2]),
                    dtype=np.float32)

        for i in range(self.angleblocks.shape[0]):
            if geo.mode != 'parallel':

                geox = copy.deepcopy(self.geo)
                geox.angles = self.angleblocks[i]
                # shrink the volume size to avoid zeros in backprojection
                geox.sVoxel = geox.sVoxel * np.max(
                    geox.sVoxel[1:] / np.linalg.norm(geox.sVoxel[1:])) * 0.9
                geox.dVoxel = geox.sVoxel / geox.nVoxel
                proj_one = np.ones((len(
                    self.angleblocks[i]), geo.nDetector[0], geo.nDetector[1]),
                                   dtype=np.float32)
                V[i] = Atb(proj_one, geox, self.angleblocks[i],
                           'FDK').mean(axis=0)

            else:
                V[i] *= len(self.angleblocks[i])

        setattr(self, 'V', V)

    def set_res(self):
        """
        Calulates initial value for res if this is not given.
        :return: None
        """
        setattr(self, 'res', np.zeros(self.geo.nVoxel, dtype=np.float32))
        init = self.init
        verbose = self.verbose
        if init == 'multigrid':
            if verbose:
                print('init multigrid in progress...')
                print('default blocksize=1 for init_multigrid(OS_SART)')
            self.res = init_multigrid(self.proj,
                                      self.geo,
                                      self.angles,
                                      alg='SART')
            if verbose:
                print('init multigrid complete.')
        if init == 'FDK':
            self.res = FDK(self.proj, self.geo, self.angles)

        if isinstance(init, np.ndarray):
            if (self.geo.nVoxel == init.shape).all():

                self.res = init

            else:
                raise ValueError('wrong dimension of array for initialisation')

    def set_angle_index(self):
        """
        sets angle_index and angleblock if this is not given.
        :return: None
        """
        angleblocks, angle_index = order_subsets(self.angles, self.blocksize,
                                                 self.OrderStrategy)
        setattr(self, 'angleblocks', angleblocks)
        setattr(self, 'angle_index', angle_index)

    def run_main_iter(self):
        """
        Goes through the main iteration for the given configuration.
        :return: None
        """
        Quameasopts = self.Quameasopts

        for i in range(self.niter):

            res_prev = None
            if Quameasopts is not None:
                res_prev = copy.deepcopy(self.res)
            if self.verbose:
                if i == 0:
                    print(
                        str(self.name).upper() + ' ' +
                        "algorithm in progress.")
                    toc = default_timer()
                if i == 1:
                    tic = default_timer()
                    print('Esitmated time until completetion (s): ' +
                          str((self.niter - 1) * (tic - toc)))
            getattr(self, self.dataminimizing)()
            self.error_measurement(res_prev, i)

    def art_data_minimizing(self):

        geo = copy.deepcopy(self.geo)
        for j in range(len(self.angleblocks)):
            if self.blocksize == 1:
                angle = np.array([self.angleblocks[j]], dtype=np.float32)
            else:
                angle = self.angleblocks[j]

            if geo.offOrigin.shape[0] == self.angles.shape[0]:
                geo.offOrigin = self.geo.offOrigin[j]
            if geo.offDetector.shape[0] == self.angles.shape[0]:
                geo.offOrin = self.geo.offDetector[j]
            if geo.rotDetector.shape[0] == self.angles.shape[0]:
                geo.rotDetector = self.geo.rotDetector[j]
            if hasattr(geo.DSD, 'shape') and len((geo.DSD.shape)):
                if geo.DSD.shape[0] == self.angles.shape[0]:
                    geo.DSD = self.geo.DSD[j]
            if hasattr(geo.DSO, 'shape') and len((geo.DSD.shape)):
                if geo.DSO.shape[0] == self.angles.shape[0]:
                    geo.DSO = self.geo.DSO[j]

            self.update_image(geo, angle, j)

            if self.noneg:
                self.res = self.res.clip(min=0)

    def minimizeTV(self, res_prev, dtvg):
        return minTV(res_prev, dtvg, self.numiter_tv)

    def minimizeAwTV(self, res_prev, dtvg):
        return AwminTV(res_prev, dtvg, self.numiter_tv, self.delta)

    def error_measurement(self, res_prev, iter):
        if self.Quameasopts is not None and iter > 0:
            self.lq.append(MQ(self.res, res_prev, self.Quameasopts))
        if self.computel2:
            # compute l2 borm for b-Ax
            errornow = im3DNORM(
                self.proj - Ax(self.res, self.geo, self.angles, 'Siddon'), 2)
            self.l2l.append(errornow)

    def update_image(self, geo, angle, iteration):
        """
        VERBOSE:
         for j in range(angleblocks):
             angle = np.array([alpha[j]], dtype=np.float32)
             proj_err = proj[angle_index[j]] - Ax(res, geo, angle, 'Siddon')
             weighted_err = W[angle_index[j]] * proj_err
             backprj = Atb(weighted_err, geo, angle, 'FDK')
             weighted_backprj = 1 / V[angle_index[j]] * backprj
             res += weighted_backprj
             res[res<0]=0

        :return: None
        """
        ang_index = self.angle_index[iteration].astype(np.int)
        self.res += self.lmbda * 1. / self.V[iteration] * Atb(
            self.W[ang_index] *
            (self.proj[ang_index] - Ax(self.res, geo, angle, 'Siddon')), geo,
            angle, 'FDK')

    def getres(self):
        return self.res

    def geterrors(self):
        return self.l2l, self.lq

    def __str__(self):
        parameters = []
        for item in self.__dict__:
            if item == 'geo':
                pass
            elif hasattr(self.__dict__.get(item), 'shape'):
                if self.__dict__.get(item).ravel().shape[0] > 100:
                    parameters.append(item + ' shape: ' +
                                      str(self.__dict__.get(item).shape))
            else:
                parameters.append(item + ': ' + str(self.__dict__.get(item)))

        return '\n'.join(parameters)
コード例 #9
0
class IterativeReconAlg(object):
    """
    Parameters
    ----------
    :param proj: (np.ndarray, dtype=np.float32)
    Input data, shape = (geo.nDector, nangles)

    :param geo: (tigre.geometry)
    Geometry of detector and image (see examples/Demo code)

    :param angles: (np.ndarray , dtype=np.float32)
    angles of projection, shape = (nangles,3)

    :param niter: (int)
    number of iterations for reconstruction algorithm

    :param kwargs: (dict)
    optional parameters

    Keyword Arguments
    -----------------
    :keyword blocksize: (int)
        number of angles to be included in each iteration
        of proj and backproj for OS_SART
    :keyword lmbda: (np.float64)
        Sets the value of the hyperparameter.

    :keyword lmbda_red: (np.float64)
        Reduction of lambda every iteration
        lambda=lambdared*lambda. Default is 0.99

    :keyword init: (str)
        Describes different initialization techniques.
              "none"     : Initializes the image to zeros (default)
              "FDK"      : intializes image to FDK reconstrucition
              "multigrid": Initializes image by solving the problem in
                           small scale and increasing it when relative
                           convergence is reached.
              "image"    : Initialization using a user specified
                           image. Not recommended unless you really
                           know what you are doing.

    :keyword InitImg: (np.ndarray)
        Not yet implemented. Image for the "image" initialization.

    :keyword verbose:  (Boolean)
        Feedback print statements for algorithm progress
        default=True

    :keyword Quameasopts: (list)
        Asks the algorithm for a set of quality measurement
        parameters. Input should contain a list or tuple of strings of
        quality measurement names. Examples:
            RMSE, CC, UQI, MSSIM

    :keyword OrderStrategy : (str)
        Chooses the subset ordering strategy. Options are:
                 "ordered"        : uses them in the input order, but divided
                 "random"         : orders them randomply
                 "angularDistance": chooses the next subset with the
                                    biggest angular distance with the ones used
    Examples
    --------
    tigre.demos.run() to launch ipython notebook file with examples.

     """

    def __init__(self, proj, geo, angles, niter, **kwargs):

        self.proj = proj
        self.angles = angles
        self.geo = geo
        self.niter = niter

        options = dict(blocksize=20, lmbda=1, lmbda_red=0.99,
                       OrderStrategy=None, Quameasopts=None,
                       init=None,verbose=True, noneg=True,
                       computel2=False, dataminimizing='art_data_minimizing',
                       name='Iterative Reconstruction', sup_kw_warning = False)
        allowed_keywords = ['V','W','log_parameters','angleblocks','angle_index','delta','regularisation']
        self.__dict__.update(options)
        self.__dict__.update(**kwargs)
        for kw in kwargs.keys():
            if not options.has_key(kw) and (kw not in allowed_keywords):
                if self.verbose:
                    if not kwargs.get('sup_kw_warning'):
                        # Note: might not want this warning (typo checking).
                        print("Warning: " + kw + " not recognised as default parameter for instance of IterativeReconAlg.")
        if self.angles.ndim == 1:
            a1 = self.angles
            a2 = np.zeros(self.angles.shape[0], dtype=np.float32)
            setattr(self, 'angles', np.vstack((a1, a2, a2)).T)
        if not hasattr(self, 'W'):
            self.set_w()
        if not hasattr(self, 'V'):
            self.set_v()
        if not hasattr(self, 'res'):
            self.set_res()
        if not all([hasattr(self, 'angleindex'), hasattr(self, 'angleblocks')]):
            self.set_angle_index()
        setattr(self, 'lq', [])  # quameasoptslist
        setattr(self, 'l2l', [])  # l2list

    def set_w(self):
        """
        Calculates value of W if this is not given.
        :return: None
        """
        geox = copy.deepcopy(self.geo)
        geox.sVoxel[0:] = self.geo.DSD - self.geo.DSO
        geox.sVoxel[2] = max(geox.sDetector[1], geox.sVoxel[2])
        geox.nVoxel = np.array([2, 2, 2])
        geox.dVoxel = geox.sVoxel / geox.nVoxel
        W = Ax(np.ones(geox.nVoxel, dtype=np.float32), geox, self.angles, "ray-voxel")
        W[W < min(self.geo.dVoxel / 4)] = np.inf
        W = 1./W
        setattr(self, 'W', W)

    def set_v(self):
        """
        Computes value of V parameter if this is not given.
        :return: None
        """
        geo = self.geo
        if geo.mode != 'parallel':

            start = geo.sVoxel[1] / 2 - geo.dVoxel[1] / 2 + geo.offOrigin[1]
            stop = -geo.sVoxel[1] / 2 + geo.dVoxel[1] / 2 + geo.offOrigin[1]
            step = -geo.dVoxel[1]

            xv = np.arange(start, stop + step, step)

            start = geo.sVoxel[2] / 2 - geo.dVoxel[2] / 2 + geo.offOrigin[2]
            stop = -geo.sVoxel[2] / 2 + geo.dVoxel[2] / 2 + geo.offOrigin[2]
            step = -geo.dVoxel[2]

            yv = -1 * np.arange(start, stop + step, step)

            (yy, xx) = np.meshgrid(yv, xv)
            xx = np.expand_dims(xx, axis=2)
            yy = np.expand_dims(yy, axis=2)
            A = (self.angles[:, 0] + np.pi / 2)
            V = (geo.DSO / (geo.DSO + (yy * np.sin(-A)) - (xx * np.cos(-A)))) ** 2
            V = np.array(V, dtype=np.float32)
            setattr(self, 'V', V)

        else:
            V = np.ones([ geo.nVoxel[1], geo.nVoxel[2], self.angles.shape[0]], dtype=np.float32)
            setattr(self, 'V', V)

    def set_res(self):
        """
        Calulates initial value for res if this is not given.
        :return: None
        """
        setattr(self, 'res', np.zeros(self.geo.nVoxel, dtype=np.float32))
        init = self.init
        verbose = self.verbose
        if init == 'multigrid':
            if verbose:
                print('init multigrid in progress...')
                print('default blocksize=1 for init_multigrid(OS_SART)')
            self.res = init_multigrid(self.proj, self.geo, self.angles, alg='SART')
            if verbose:
                print('init multigrid complete.')
        if init == 'FDK':
            self.res = FDK(self.proj, self.geo, self.angles)

        if type(init) == np.ndarray:
            if (self.geo.nVoxel == init.shape).all():

                self.res = init

            else:
                raise ValueError('wrong dimension of array for initialisation')

    def set_angle_index(self):
        """
        sets angle_index and angleblock if this is not given.
        :return: None
        """
        angleblocks, angle_index = order_subsets(self.angles, self.blocksize, self.OrderStrategy)
        setattr(self, 'angleblocks', angleblocks)
        setattr(self, 'angle_index', angle_index)

    def run_main_iter(self):
        """
        Goes through the main iteration for the given configuration.
        :return: None
        """
        Quameasopts = self.Quameasopts

        for i in range(self.niter):

            res_prev = None
            if Quameasopts is not None:
                res_prev = copy.deepcopy(self.res)
            if self.verbose:
                if i == 0:
                    print(str(self.name).upper() + ' ' + "algorithm in progress.")
                    toc = time.clock()
                if i == 1:
                    tic = time.clock()
                    print('Esitmated time until completetion (s): ' + str((self.niter - 1) * (tic - toc)))
            getattr(self, self.dataminimizing)()
            self.error_measurement(res_prev, i)
    def art_data_minimizing(self):
        """
        VERBOSE:
        >>> for j in range(angleblocks):
        >>>     angle = np.array([alpha[j]], dtype=np.float32)
        >>>     proj_err = proj[angle_index[j]] - Ax(res, geo, angle, 'ray-voxel')
        >>>     weighted_err = W[angle_index[j]] * proj_err
        >>>     backprj = Atb(weighted_err, geo, angle, 'FDK')
        >>>     weighted_backprj = 1 / V[angle_index[j]] * backprj
        >>>     res += weighted_backprj
        >>>     res[res<0]=0

        :return: None
        """
        geo = copy.deepcopy(self.geo)
        for j in range(len(self.angleblocks)):
            if self.blocksize == 1:
                angle = np.array([self.angleblocks[j]], dtype=np.float32)
            else:
                angle = self.angleblocks[j]

            if geo.offOrigin.shape[0] ==self.angles.shape[0]:
               geo.offOrigin = self.geo.offOrigin[j]
            if geo.offDetector.shape[0] == self.angles.shape[0]:
                geo.offOrin = self.geo.offDetector[j]
            if geo.rotDetector.shape[0] ==self.angles.shape[0]:
                geo.rotDetector=self.geo.rotDetector[j]
            if hasattr(geo.DSD,'shape'):
                if geo.DSD.shape[0] ==self.angles.shape[0]:
                    geo.DSD = self.geo.DSD[j]
            if hasattr(geo.DSO,'shape'):
                if geo.DSO.shape[0] ==self.angles.shape[0]:
                    geo.DSO = self.geo.DSO[j]


            self.res += self.lmbda * 1/self.third_dim_sum(self.V[:,:,self.angle_index[j]]) * Atb(self.W[self.angle_index[j]] * (self.proj[self.angle_index[j]]
                                     - Ax(self.res, geo, angle, 'interpolated')),geo, angle, 'FDK')
            if self.noneg:
                self.res = self.res.clip(min=0)
    def third_dim_sum(self,V):
        if V.ndim == 3:
            return np.sum(V, axis=2, dtype=np.float32)
        else:
            return V

    def minimizeTV(self,res_prev,dtvg):
        return minTV(res_prev,dtvg,self.numiter_tv)

    def minimizeAwTV(self,res_prev,dtvg):
        return AwminTV(res_prev,dtvg,self.numiter_tv,self.delta)

    def error_measurement(self, res_prev, iter):
        if self.Quameasopts is not None and iter > 0:
            self.lq.append(MQ(self.res, res_prev, self.Quameasopts))
        if self.computel2:
            # compute l2 borm for b-Ax
            errornow = im3DNORM(self.proj - Ax(self.res, self.geo, self.angles, 'ray-voxel'), 2)
            self.l2l.append(errornow)

    def getres(self):
        return self.res

    def getl2(self):
        return self.l2l

    def __str__(self):
        parameters = []
        for item in self.__dict__:
            if item == 'geo':
                pass
                #parameters.append('--------------- GEOMETRY ----------------')
                #parameters.append(self.geo.__str__())
                #parameters.append('----------------END GEOMETRY ------------')
            elif hasattr(self.__dict__.get(item), 'shape'):
                if self.__dict__.get(item).ravel().shape[0] > 100:
                    parameters.append(item + ' shape: ' + str(self.__dict__.get(item).shape))
            else:
                parameters.append(item + ': ' + str(self.__dict__.get(item)))

        return '\n'.join(parameters)
コード例 #10
0
ファイル: iterative_recon_alg.py プロジェクト: skaser/TIGRE
class IterativeReconAlg(object):
    """
    Parameters
    ----------
    :param proj: (np.ndarray, dtype=np.float32)
    Input data, shape = (geo.nDector, nangles)

    :param geo: (tigre.geometry)
    Geometry of detector and image (see examples/Demo code)

    :param angles: (np.ndarray , dtype=np.float32)
    angles of projection, shape = (nangles,3)

    :param niter: (int)
    number of iterations for reconstruction algorithm

    :param kwargs: (dict)
    optional parameters

    Keyword Arguments
    -----------------
    :keyword blocksize: (int)
        number of angles to be included in each iteration
        of proj and backproj for OS_SART
    :keyword lmbda: (np.float64)
        Sets the value of the hyperparameter.

    :keyword lmbda_red: (np.float64)
        Reduction of lmbda every iteration
        lmbda=lmbda_red*lmbda. Default is 0.99

    :keyword init: (str)
        Describes different initialization techniques.
               None      : Initializes the image to zeros (default)
              "FDK"      : intializes image to FDK reconstrucition

    :keyword verbose:  (Boolean)
        Feedback print statements for algorithm progress
        default=True

    :keyword OrderStrategy : (str)
        Chooses the subset ordering strategy. Options are:
                 "ordered"        : uses them in the input order, but
                                    divided
                 "random"         : orders them randomply

    :keyword tviter: (int)
        For algorithms that make use of a tvdenoising step in their
        iterations. This includes:

            OS_SART_TV
            ASD_POCS
            AWASD_POCS
            FISTA

    :keyword tvlambda: (float)
        For algorithms that make use of a tvdenoising step in their
        iterations.

            OS_SART_TV
            FISTA

    Usage
    --------
    >>> import numpy as np
    >>> import tigre
    >>> import tigre.algorithms as algs
    >>> from tigre.demos.Test_data import data_loader
    >>> geo = tigre.geometry(mode='cone',default_geo=True,
    >>>                         nVoxel=np.array([64,64,64]))
    >>> angles = np.linspace(0,2*np.pi,100)
    >>> src_img = data_loader.load_head_phantom(geo.nVoxel)
    >>> proj = tigre.Ax(src_img,geo,angles)
    >>> output = algs.iterativereconalg(proj,geo,angles,niter=50
    >>>                                 blocksize=20)

    tigre.demos.run() to launch ipython notebook file with examples.


    --------------------------------------------------------------------
    This file is part of the TIGRE Toolbox

    Copyright (c) 2015, University of Bath and
                        CERN-European Organization for Nuclear Research
                        All rights reserved.

    License:            Open Source under BSD.
                        See the full license at
                        https://github.com/CERN/TIGRE/license.txt

    Contact:            [email protected]
    Codes:              https://github.com/CERN/TIGRE/
    --------------------------------------------------------------------
    Coded by:          MATLAB (original code): Ander Biguri
                       PYTHON : Reuben Lindroos
    """
    def __init__(self, proj, geo, angles, niter, **kwargs):

        self.proj = proj
        self.angles = angles
        self.geo = geo
        self.niter = niter

        self.geo.check_geo(angles)

        options = dict(
            blocksize=20,
            lmbda=1,
            lmbda_red=1,
            OrderStrategy=None,
            Quameasopts=None,
            init=None,
            verbose=True,
            noneg=True,
            computel2=False,
            dataminimizing="art_data_minimizing",
            name="Iterative Reconstruction",
            sup_kw_warning=False,
            gpuids=None,
        )
        allowed_keywords = [
            "V",
            "W",
            "log_parameters",
            "angleblocks",
            "angle_index",
            "alpha",
            "alpha_red",
            "rmax",
            "maxl2err",
            "delta",
            "regularisation",
            "tviter",
            "tvlambda",
            "hyper",
        ]
        self.__dict__.update(options)
        self.__dict__.update(**kwargs)
        for kw in kwargs.keys():
            if kw not in options and (kw not in allowed_keywords):
                if self.verbose:
                    if not kwargs.get("sup_kw_warning"):
                        # Note: might not want this warning (typo checking).
                        print(
                            "Warning: " + kw +
                            " not recognised as default parameter for instance of IterativeReconAlg."  # noqa: E501
                        )
        if self.angles.ndim == 1:
            a1 = self.angles
            a2 = np.zeros(self.angles.shape[0], dtype=np.float32)
            self.angles = np.vstack((a1, a2, a2)).T
        if not all([hasattr(self, "angleindex"),
                    hasattr(self, "angleblocks")]):
            self.set_angle_index()
        if not hasattr(self, "W"):
            self.set_w()
        if not hasattr(self, "V"):
            self.set_v()
        if not hasattr(self, "res"):
            self.set_res()
        if self.verbose:
            self.tic = 0  # preparation for _estimate_time_until_completion()
        # make it list
        if self.Quameasopts is not None:
            self.Quameasopts = ([self.Quameasopts] if isinstance(
                self.Quameasopts, str) else self.Quameasopts)
            setattr(self, "lq", np.zeros([len(self.Quameasopts),
                                          niter]))  # quameasoptslist
        else:
            setattr(self, "lq", np.zeros([0, niter]))  # quameasoptslist
        setattr(self, "l2l", np.zeros([1, niter]))  # l2list

    def set_w(self):
        """
        Calculates value of W if this is not given.
        :return: None
        """
        geox = copy.deepcopy(self.geo)
        geox.sVoxel[1:] = geox.sVoxel[
            1:] * 1.1  # a bit larger to avoid zeros in projections
        geox.sVoxel[0] = max(geox.sDetector[0], geox.sVoxel[0])

        geox.nVoxel = np.array([2, 2, 2])
        geox.dVoxel = geox.sVoxel / geox.nVoxel
        W = Ax(np.ones(geox.nVoxel, dtype=np.float32),
               geox,
               self.angles,
               "Siddon",
               gpuids=self.gpuids)
        W[W <= min(self.geo.dVoxel / 2)] = np.inf
        W = 1.0 / W
        setattr(self, "W", W)

    def set_v(self):
        """
        Computes value of V parameter if this is not given.
        :return: None
        """
        block_count = len(self.angleblocks)
        geo = self.geo
        V = np.ones((block_count, geo.nVoxel[1], geo.nVoxel[2]),
                    dtype=np.float32)

        for i in range(block_count):
            if geo.mode != "parallel":

                geox = copy.deepcopy(self.geo)
                geox.angles = self.angleblocks[i]

                geox.DSD = geo.DSD[self.angle_index[i]]
                geox.DSO = geo.DSO[self.angle_index[i]]
                geox.offOrigin = geo.offOrigin[self.angle_index[i], :]
                geox.offDetector = geo.offDetector[self.angle_index[i], :]
                geox.rotDetector = geo.rotDetector[self.angle_index[i], :]
                geox.COR = geo.COR[self.angle_index[i]]

                # shrink the volume size to avoid zeros in backprojection
                geox.sVoxel = (geox.sVoxel * np.max(
                    geox.sVoxel[1:] / np.linalg.norm(geox.sVoxel[1:])) * 0.9)
                geox.dVoxel = geox.sVoxel / geox.nVoxel
                proj_one = np.ones((len(
                    self.angleblocks[i]), geo.nDetector[0], geo.nDetector[1]),
                                   dtype=np.float32)
                V[i] = Atb(proj_one,
                           geox,
                           self.angleblocks[i],
                           "FDK",
                           gpuids=self.gpuids).mean(axis=0)

            else:
                V[i] *= len(self.angleblocks[i])

        self.V = V

    def set_res(self):
        """
        Calulates initial value for res if this is not given.
        :return: None
        """
        self.res = np.zeros(self.geo.nVoxel, dtype=np.float32)
        init = self.init
        verbose = self.verbose
        if init == "multigrid":
            if verbose:
                print("init multigrid in progress...")
                print("default blocksize=1 for init_multigrid(OS_SART)")
            self.res = init_multigrid(self.proj,
                                      self.geo,
                                      self.angles,
                                      alg="SART")
            if verbose:
                print("init multigrid complete.")
        if init == "FDK":
            self.res = FDK(self.proj, self.geo, self.angles)

        if isinstance(init, np.ndarray):
            if (self.geo.nVoxel == init.shape).all():

                self.res = init

            else:
                raise ValueError("wrong dimension of array for initialisation")

    def set_angle_index(self):
        """
        sets angle_index and angleblock if this is not given.
        :return: None
        """
        self.angleblocks, self.angle_index = order_subsets(
            self.angles, self.blocksize, self.OrderStrategy)

    def run_main_iter(self):
        """
        Goes through the main iteration for the given configuration.
        :return: None
        """
        Quameasopts = self.Quameasopts

        for i in range(self.niter):

            res_prev = None
            if Quameasopts is not None:
                res_prev = copy.deepcopy(self.res)
            if self.verbose:
                self._estimate_time_until_completion(i)

            getattr(self, self.dataminimizing)()
            self.error_measurement(res_prev, i)

    def art_data_minimizing(self):
        geo = copy.deepcopy(self.geo)

        for j in range(len(self.angleblocks)):

            if self.blocksize == 1:
                angle = np.array([self.angleblocks[j]], dtype=np.float32)
                angle_indices = np.array([self.angle_index[j]], dtype=np.int32)

            else:
                angle = self.angleblocks[j]
                angle_indices = self.angle_index[j]
                # slice parameters if needed
            geo.offOrigin = self.geo.offOrigin[angle_indices]
            geo.offDetector = self.geo.offDetector[angle_indices]
            geo.rotDetector = self.geo.rotDetector[angle_indices]
            geo.DSD = self.geo.DSD[angle_indices]
            geo.DSO = self.geo.DSO[angle_indices]

            self.update_image(geo, angle, j)

            if self.noneg:
                self.res = self.res.clip(min=0)

    def minimizeTV(self, res_prev, dtvg):
        if self.gpuids is None:
            self.gpuids = GpuIds()
        return minTV(res_prev, dtvg, self.numiter_tv, self.gpuids)

    def minimizeAwTV(self, res_prev, dtvg):
        if self.gpuids is None:
            self.gpuids = GpuIds()
        return AwminTV(res_prev, dtvg, self.numiter_tv, self.delta,
                       self.gpuids)

    def error_measurement(self, res_prev, iter):
        if self.Quameasopts is not None:
            self.lq[:, iter] = MQ(self.res, res_prev, self.Quameasopts)
        if self.computel2:
            # compute l2 borm for b-Ax
            errornow = im3DNORM(
                self.proj - Ax(self.res,
                               self.geo,
                               self.angles,
                               "Siddon",
                               gpuids=self.gpuids), 2)
            self.l2l[0, iter] = errornow

    def update_image(self, geo, angle, iteration):
        """
        VERBOSE:
         for j in range(angleblocks):
             angle = np.array([alpha[j]], dtype=np.float32)
             proj_err = proj[angle_index[j]] - Ax(res, geo, angle, 'Siddon')
             weighted_err = W[angle_index[j]] * proj_err
             backprj = Atb(weighted_err, geo, angle, 'FDK')
             weighted_backprj = 1 / V[angle_index[j]] * backprj
             res += weighted_backprj
             res[res<0]=0

        :return: None
        """

        ang_index = self.angle_index[iteration].astype(np.int)

        self.res += (self.lmbda * 1.0 / self.V[iteration] * Atb(
            self.W[ang_index] *
            (self.proj[ang_index] -
             Ax(self.res, geo, angle, "Siddon", gpuids=self.gpuids)),
            geo,
            angle,
            "FDK",
            gpuids=self.gpuids,
        ))

    def getres(self):
        return self.res

    def geterrors(self):
        if self.computel2:
            return np.concatenate((self.l2l, self.lq), axis=0)
        else:
            return self.lq

    def __str__(self):
        parameters = []
        for item in self.__dict__:
            if item == "geo":
                pass
            elif hasattr(self.__dict__.get(item), "shape"):
                if self.__dict__.get(item).ravel().shape[0] > 100:
                    parameters.append(item + " shape: " +
                                      str(self.__dict__.get(item).shape))
            else:
                parameters.append(item + ": " + str(self.__dict__.get(item)))

        return "\n".join(parameters)

    def _estimate_time_until_completion(self, iter):
        if iter == 0:
            print(str(self.name).upper() + " " + "algorithm in progress.")
            self.tic = default_timer()
        if iter == 1:
            toc = default_timer()

            remaining_time = (self.niter - 1) * (toc - self.tic)
            seconds = int(remaining_time)
            print("Estimated time until completion : " +
                  time.strftime("%H:%M:%S", time.gmtime(seconds)))
コード例 #11
0
ファイル: iterative_recon_alg.py プロジェクト: CERN/TIGRE
class IterativeReconAlg(object):
    """
    Parameters
    ----------
    :param proj: (np.ndarray, dtype=np.float32)
    Input data, shape = (geo.nDector, nangles)

    :param geo: (tigre.geometry)
    Geometry of detector and image (see examples/Demo code)

    :param angles: (np.ndarray , dtype=np.float32)
    angles of projection, shape = (nangles,3)

    :param niter: (int)
    number of iterations for reconstruction algorithm

    :param kwargs: (dict)
    optional parameters

    Keyword Arguments
    -----------------
    :keyword blocksize: (int)
        number of angles to be included in each iteration
        of proj and backproj for OS_SART
    :keyword lmbda: (np.float64)
        Sets the value of the hyperparameter.

    :keyword lmbda_red: (np.float64)
        Reduction of lambda every iteration
        lambda=lambdared*lambda. Default is 0.99

    :keyword init: (str)
        Describes different initialization techniques.
              "none"     : Initializes the image to zeros (default)
              "FDK"      : intializes image to FDK reconstrucition
              "multigrid": Initializes image by solving the problem in
                           small scale and increasing it when relative
                           convergence is reached.
              "image"    : Initialization using a user specified
                           image. Not recommended unless you really
                           know what you are doing.

    :keyword InitImg: (np.ndarray)
        Not yet implemented. Image for the "image" initialization.

    :keyword verbose:  (Boolean)
        Feedback print statements for algorithm progress
        default=True

    :keyword Quameasopts: (list)
        Asks the algorithm for a set of quality measurement
        parameters. Input should contain a list or tuple of strings of
        quality measurement names. Examples:
            RMSE, CC, UQI, MSSIM

    :keyword OrderStrategy : (str)
        Chooses the subset ordering strategy. Options are:
                 "ordered"        : uses them in the input order, but
                                    divided
                 "random"         : orders them randomply
                 "angularDistance": chooses the next subset with the
                                    biggest angular distance with the
                                    ones used
    Usage
    --------
    >>> import numpy as np
    >>> import tigre
    >>> import tigre.algorithms as algs
    >>> from tigre.demos.Test_data import data_loader
    >>> geo = tigre.geometry(mode='cone',default_geo=True,
    >>>                         nVoxel=np.array([64,64,64]))
    >>> angles = np.linspace(0,2*np.pi,100)
    >>> src_img = data_loader.load_head_phantom(geo.nVoxel)
    >>> proj = tigre.Ax(src_img,geo,angles)
    >>> output = algs.iterativereconalg(proj,geo,angles,niter=50
    >>>                                 blocksize=20)

    tigre.demos.run() to launch ipython notebook file with examples.

     """

    def __init__(self, proj, geo, angles, niter, **kwargs):

        self.proj = proj
        self.angles = angles
        self.geo = geo
        self.niter = niter

        options = dict(blocksize=20, lmbda=1, lmbda_red=0.99,
                       OrderStrategy=None, Quameasopts=None,
                       init=None,verbose=True, noneg=True,
                       computel2=False, dataminimizing='art_data_minimizing',
                       name='Iterative Reconstruction', sup_kw_warning = False)
        allowed_keywords = ['V','W','log_parameters','angleblocks','angle_index','delta','regularisation']
        self.__dict__.update(options)
        self.__dict__.update(**kwargs)
        for kw in kwargs.keys():
            if not options.has_key(kw) and (kw not in allowed_keywords):
                if self.verbose:
                    if not kwargs.get('sup_kw_warning'):
                        # Note: might not want this warning (typo checking).
                        print("Warning: " + kw + " not recognised as default parameter for instance of IterativeReconAlg.")
        if self.angles.ndim == 1:
            a1 = self.angles
            a2 = np.zeros(self.angles.shape[0], dtype=np.float32)
            setattr(self, 'angles', np.vstack((a1, a2, a2)).T)
        if not all([hasattr(self, 'angleindex'), hasattr(self, 'angleblocks')]):
            self.set_angle_index()
        if not hasattr(self, 'W'):
            self.set_w()
        if not hasattr(self, 'V'):
            self.set_v()
        if not hasattr(self, 'res'):
            self.set_res()
        setattr(self, 'lq', [])  # quameasoptslist
        setattr(self, 'l2l', [])  # l2list

    def set_w(self):
        """
        Calculates value of W if this is not given.
        :return: None
        """
        geox = copy.deepcopy(self.geo)
        geox.sVoxel[0:] = self.geo.DSD - self.geo.DSO
        geox.sVoxel[2] = max(geox.sDetector[1], geox.sVoxel[2])
        geox.nVoxel = np.array([2, 2, 2])
        geox.dVoxel = geox.sVoxel / geox.nVoxel
        W = Ax(np.ones(geox.nVoxel, dtype=np.float32), geox, self.angles, "ray-voxel")
        W[W <= min(self.geo.dVoxel / 4)] = np.inf
        W = 1./W
        setattr(self, 'W', W)

    def set_v(self):
        """
        Computes value of V parameter if this is not given.
        :return: None
        """
        geo = self.geo
        if geo.mode != 'parallel':

            start = geo.sVoxel[1] / 2 - geo.dVoxel[1] / 2 + geo.offOrigin[1]
            stop = -geo.sVoxel[1] / 2 + geo.dVoxel[1] / 2 + geo.offOrigin[1]
            step = -geo.dVoxel[1]

            xv = np.arange(start, stop + step, step)

            start = geo.sVoxel[2] / 2 - geo.dVoxel[2] / 2 + geo.offOrigin[2]
            stop = -geo.sVoxel[2] / 2 + geo.dVoxel[2] / 2 + geo.offOrigin[2]
            step = -geo.dVoxel[2]

            yv = -1 * np.arange(start, stop + step, step)

            (yy, xx) = np.meshgrid(yv, xv)
            A = (self.angles[:, 0] + np.pi / 2)

            V = np.empty((self.angles.shape[0],geo.nVoxel[1], geo.nVoxel[2]))
            for i in range(self.angles.shape[0]):
                if hasattr(geo.DSO,'shape') and len(geo.DSO.shape)>=1:
                    DSO = geo.DSO[i]
                else:
                    DSO = geo.DSO
                V[i] = (DSO / (DSO + (yy * np.sin(-A[i])) - (xx * np.cos(-A[i])))) ** 2

        else:
            V = np.ones((self.angles.shape[0]), dtype=np.float32)
        if self.blocksize>1:
            v_list = [np.sum(V[self.angle_index[i]],axis=0) for i in range(len(self.angleblocks))]
            V = np.stack(v_list,0)



        V = np.array(V,dtype=np.float32)
        setattr(self, 'V', V)

    def set_res(self):
        """
        Calulates initial value for res if this is not given.
        :return: None
        """
        setattr(self, 'res', np.zeros(self.geo.nVoxel, dtype=np.float32))
        init = self.init
        verbose = self.verbose
        if init == 'multigrid':
            if verbose:
                print('init multigrid in progress...')
                print('default blocksize=1 for init_multigrid(OS_SART)')
            self.res = init_multigrid(self.proj, self.geo, self.angles, alg='SART')
            if verbose:
                print('init multigrid complete.')
        if init == 'FDK':
            self.res = FDK(self.proj, self.geo, self.angles)

        if type(init) == np.ndarray:
            if (self.geo.nVoxel == init.shape).all():

                self.res = init

            else:
                raise ValueError('wrong dimension of array for initialisation')

    def set_angle_index(self):
        """
        sets angle_index and angleblock if this is not given.
        :return: None
        """
        angleblocks, angle_index = order_subsets(self.angles, self.blocksize, self.OrderStrategy)
        setattr(self, 'angleblocks', angleblocks)
        setattr(self, 'angle_index', angle_index)

    def run_main_iter(self):
        """
        Goes through the main iteration for the given configuration.
        :return: None
        """
        Quameasopts = self.Quameasopts

        for i in range(self.niter):

            res_prev = None
            if Quameasopts is not None:
                res_prev = copy.deepcopy(self.res)
            if self.verbose:
                if i == 0:
                    print(str(self.name).upper() + ' ' + "algorithm in progress.")
                    toc = time.clock()
                if i == 1:
                    tic = time.clock()
                    print('Esitmated time until completetion (s): ' + str((self.niter - 1) * (tic - toc)))
            getattr(self, self.dataminimizing)()
            self.error_measurement(res_prev, i)
    def art_data_minimizing(self):
        """
        VERBOSE:
        >>> for j in range(angleblocks):
        >>>     angle = np.array([alpha[j]], dtype=np.float32)
        >>>     proj_err = proj[angle_index[j]] - Ax(res, geo, angle, 'ray-voxel')
        >>>     weighted_err = W[angle_index[j]] * proj_err
        >>>     backprj = Atb(weighted_err, geo, angle, 'FDK')
        >>>     weighted_backprj = 1 / V[angle_index[j]] * backprj
        >>>     res += weighted_backprj
        >>>     res[res<0]=0

        :return: None
        """
        geo = copy.deepcopy(self.geo)
        for j in range(len(self.angleblocks)):
            if self.blocksize == 1:
                angle = np.array([self.angleblocks[j]], dtype=np.float32)
            else:
                angle = self.angleblocks[j]

            if geo.offOrigin.shape[0] ==self.angles.shape[0]:
               geo.offOrigin = self.geo.offOrigin[j]
            if geo.offDetector.shape[0] == self.angles.shape[0]:
                geo.offOrin = self.geo.offDetector[j]
            if geo.rotDetector.shape[0] ==self.angles.shape[0]:
                geo.rotDetector=self.geo.rotDetector[j]
            if hasattr(geo.DSD,'shape') and len((geo.DSD.shape)):
                if geo.DSD.shape[0] ==self.angles.shape[0]:
                    geo.DSD = self.geo.DSD[j]
            if hasattr(geo.DSO,'shape') and len((geo.DSD.shape)):
                if geo.DSO.shape[0] ==self.angles.shape[0]:
                    geo.DSO = self.geo.DSO[j]

            self.gradient_descent(geo,angle,j)

            if self.noneg:
                self.res = self.res.clip(min=0)
    def third_dim_sum(self,V):
        if V.ndim == 3:
            return np.sum(V, axis=2, dtype=np.float32)
        else:
            return V

    def minimizeTV(self,res_prev,dtvg):
        return minTV(res_prev,dtvg,self.numiter_tv)

    def minimizeAwTV(self,res_prev,dtvg):
        return AwminTV(res_prev,dtvg,self.numiter_tv,self.delta)

    def error_measurement(self, res_prev, iter):
        if self.Quameasopts is not None and iter > 0:
            self.lq.append(MQ(self.res, res_prev, self.Quameasopts))
        if self.computel2:
            # compute l2 borm for b-Ax
            errornow = im3DNORM(self.proj - Ax(self.res, self.geo, self.angles, 'ray-voxel'), 2)
            self.l2l.append(errornow)

    def gradient_descent(self, geo, angle, iteration):
        self.res += self.lmbda * 1. / self.V[iteration] * Atb(self.W[self.angle_index[iteration]] * (self.proj[self.angle_index[iteration]]
                                                                                                     - Ax(self.res, geo, angle, 'interpolated')), geo, angle, 'FDK')

    def getres(self):
        return self.res

    def getl2(self):
        return self.l2l

    def __str__(self):
        parameters = []
        for item in self.__dict__:
            if item == 'geo':
                pass
            elif hasattr(self.__dict__.get(item), 'shape'):
                if self.__dict__.get(item).ravel().shape[0] > 100:
                    parameters.append(item + ' shape: ' + str(self.__dict__.get(item).shape))
            else:
                parameters.append(item + ': ' + str(self.__dict__.get(item)))

        return '\n'.join(parameters)