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_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 __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
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 __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
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
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
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)
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)
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)))
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)